Compare commits

..

2 Commits

432 changed files with 8008 additions and 32582 deletions

View File

@ -39,7 +39,7 @@ volumes:
services:
- name: test-mysql-unit
image: mariadb:11
image: mariadb:10
environment:
MYSQL_ROOT_PASSWORD: vikunjatest
MYSQL_DATABASE: vikunjatest
@ -47,7 +47,7 @@ services:
- name: tmp-mysql-unit
path: /var/lib/mysql
- name: test-mysql-integration
image: mariadb:11
image: mariadb:10
environment:
MYSQL_ROOT_PASSWORD: vikunjatest
MYSQL_DATABASE: vikunjatest
@ -55,7 +55,7 @@ services:
- name: tmp-mysql-integration
path: /var/lib/mysql
- name: test-mysql-migration
image: mariadb:11
image: mariadb:10
environment:
MYSQL_ROOT_PASSWORD: vikunjatest
MYSQL_DATABASE: vikunjatest
@ -63,7 +63,7 @@ services:
- name: tmp-mysql-migration
path: /var/lib/mysql
- name: test-postgres-unit
image: postgres:16
image: postgres:14
environment:
POSTGRES_PASSWORD: vikunjatest
POSTGRES_DB: vikunjatest
@ -73,7 +73,7 @@ services:
commands:
- docker-entrypoint.sh -c fsync=off -c full_page_writes=off # turns of wal
- name: test-postgres-integration
image: postgres:16
image: postgres:14
environment:
POSTGRES_PASSWORD: vikunjatest
POSTGRES_DB: vikunjatest
@ -83,7 +83,7 @@ services:
commands:
- docker-entrypoint.sh -c fsync=off -c full_page_writes=off # turns of wal
- name: test-postgres-migration
image: postgres:16
image: postgres:14
environment:
POSTGRES_PASSWORD: vikunjatest
POSTGRES_DB: vikunjatest
@ -121,25 +121,38 @@ steps:
when:
event: [ push, tag, pull_request ]
- name: build
- name: prepare-build
image: vikunja/golang-build:latest
pull: always
environment:
GOPROXY: 'https://goproxy.kolaente.de'
depends_on: [ mage ]
commands:
- ./mage-static do-the-swag
when:
event: [ push, tag, pull_request ]
- name: build
image: vikunja/golang-build:latest
pull: always
environment:
GOPROXY: 'https://goproxy.kolaente.de'
depends_on: [ prepare-build ]
commands:
- ./mage-static build:build
when:
event: [ push, tag, pull_request ]
- name: lint
image: golangci/golangci-lint:v1.54.2
image: golang:1.19-alpine
pull: always
environment:
GOPROXY: 'https://goproxy.kolaente.de'
depends_on: [ build ]
commands:
- export "GOROOT=$(go env GOROOT)"
- apk --no-cache add build-base git
- wget -O - -q https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.52.1
- ./mage-static check:golangci
when:
event: [ push, tag, pull_request ]
@ -217,7 +230,7 @@ steps:
GOPROXY: 'https://goproxy.kolaente.de'
commands:
- ./mage-static test:unit
depends_on: [ fetch-tags, mage ]
depends_on: [ fetch-tags, prepare-build ]
when:
event: [ push, tag, pull_request ]
@ -234,7 +247,7 @@ steps:
path: /db
commands:
- ./mage-static test:unit
depends_on: [ fetch-tags, mage ]
depends_on: [ fetch-tags, prepare-build ]
when:
event: [ push, tag, pull_request ]
@ -251,7 +264,7 @@ steps:
VIKUNJA_DATABASE_DATABASE: vikunjatest
commands:
- ./mage-static test:unit
depends_on: [ fetch-tags, mage ]
depends_on: [ fetch-tags, prepare-build ]
when:
event: [ push, tag, pull_request ]
@ -269,7 +282,7 @@ steps:
VIKUNJA_DATABASE_SSLMODE: disable
commands:
- ./mage-static test:unit
depends_on: [ fetch-tags, mage ]
depends_on: [ fetch-tags, prepare-build ]
when:
event: [ push, tag, pull_request ]
@ -280,7 +293,7 @@ steps:
GOPROXY: 'https://goproxy.kolaente.de'
commands:
- ./mage-static test:integration
depends_on: [ fetch-tags, mage ]
depends_on: [ fetch-tags, prepare-build ]
when:
event: [ push, tag, pull_request ]
@ -297,7 +310,7 @@ steps:
path: /db
commands:
- ./mage-static test:integration
depends_on: [ fetch-tags, mage ]
depends_on: [ fetch-tags, prepare-build ]
when:
event: [ push, tag, pull_request ]
@ -314,7 +327,7 @@ steps:
VIKUNJA_DATABASE_DATABASE: vikunjatest
commands:
- ./mage-static test:integration
depends_on: [ fetch-tags, mage ]
depends_on: [ fetch-tags, prepare-build ]
when:
event: [ push, tag, pull_request ]
@ -332,54 +345,10 @@ steps:
VIKUNJA_DATABASE_SSLMODE: disable
commands:
- ./mage-static test:integration
depends_on: [ fetch-tags, mage ]
depends_on: [ fetch-tags, prepare-build ]
when:
event: [ push, tag, pull_request ]
---
kind: pipeline
type: docker
name: generate-swagger-docs
depends_on:
- testing
workspace:
base: /go
path: src/code.vikunja.io/api
trigger:
branch:
include:
- main
event:
include:
- push
steps:
- name: generate-swagger-docs
image: vikunja/golang-build:latest
pull: always
environment:
GOPROXY: 'https://goproxy.kolaente.de'
commands:
- mage do-the-swag
- name: push
pull: always
image: appleboy/drone-git-push
depends_on:
- generate-swagger-docs
settings:
author_email: "frederik@vikunja.io"
author_name: Frederick [Bot]
branch: main
commit: true
commit_message: "[skip ci] Updated swagger docs"
remote: "ssh://git@kolaente.dev:9022/vikunja/api.git"
ssh_key:
from_secret: git_push_ssh_key
---
########
# Build a release when tagging
@ -427,6 +396,7 @@ steps:
- export PATH=$PATH:$GOPATH/bin
- go install github.com/magefile/mage
- ./mage-static release:dirs
- ./mage-static do-the-swag
depends_on: [ fetch-tags, mage ]
- name: static-build-windows
@ -436,7 +406,6 @@ steps:
# This path does not exist. However, when we set the gopath to /go, the build fails. Not sure why.
# Leaving this here until we know how to resolve this properly.
GOPATH: /srv/app
GOPROXY: https://goproxy.kolaente.de
commands:
- export PATH=$PATH:$GOPATH/bin
- go install github.com/magefile/mage
@ -450,7 +419,6 @@ steps:
# This path does not exist. However, when we set the gopath to /go, the build fails. Not sure why.
# Leaving this here until we know how to resolve this properly.
GOPATH: /srv/app
GOPROXY: https://goproxy.kolaente.de
commands:
- export PATH=$PATH:$GOPATH/bin
- go install github.com/magefile/mage
@ -464,7 +432,6 @@ steps:
# This path does not exist. However, when we set the gopath to /go, the build fails. Not sure why.
# Leaving this here until we know how to resolve this properly.
GOPATH: /srv/app
GOPROXY: https://goproxy.kolaente.de
commands:
- export PATH=$PATH:$GOPATH/bin
- go install github.com/magefile/mage
@ -551,7 +518,7 @@ steps:
# Build os packages and push it to our bucket
- name: build-os-packages-unstable
image: goreleaser/nfpm:v2.33.1
image: goreleaser/nfpm:v2.28.0
pull: always
commands:
- apk add git go
@ -567,7 +534,7 @@ steps:
depends_on: [ after-build-compress ]
- name: build-os-packages-version
image: goreleaser/nfpm:v2.33.1
image: goreleaser/nfpm:v2.28.0
pull: always
commands:
- apk add git go
@ -652,7 +619,7 @@ steps:
- tar -xzf vikunja-theme.tar.gz
- name: build
image: klakegg/hugo:0.111.3
image: klakegg/hugo:0.107.0
pull: always
commands:
- cd docs
@ -776,6 +743,6 @@ steps:
- failure
---
kind: signature
hmac: 3ad78b828f36d4473527b8c6ee0985a5bf6f290fe73b3f7381a41d8c4937ffaa
hmac: 7bade2fc44072cf5730f7f15b5be18058ce47d216853c8c39c692f967c640e20
...

View File

@ -1,23 +0,0 @@
name: 'Repo Lockdown'
on:
pull_request_target:
types: opened
permissions:
issues: write
pull-requests: write
jobs:
action:
runs-on: ubuntu-latest
steps:
- uses: dessant/repo-lockdown@v3
with:
pr-comment: 'Hi! Thank you for your contribution.
This repo is only a mirror and unfortunately we can''t accept PRs made here. Please re-submit your changes to [our Gitea instance](https://kolaente.dev/vikunja/api/pulls).
Also check out the [contribution guidelines](https://vikunja.io/docs/development/#pull-requests).
Thank you for your understanding.'

2
.gitignore vendored
View File

@ -27,4 +27,4 @@ vikunja-dump*
vendor/
os-packages/
mage_output_file.go
mage-static
pkg/swagger/*

View File

@ -80,7 +80,6 @@ issues:
linters:
- goheader
- misspell
- gosmopolitan
- text: "Missed string"
linters:
- goheader
@ -90,19 +89,6 @@ issues:
- path: pkg/models/favorites\.go
linters:
- nilerr
- path: pkg/models/project\.go
text: "string `parent_project_id` has 3 occurrences, make it a constant"
- path: pkg/models/events\.go
linters:
- musttag
- path: pkg/models/task_collection.go
text: 'append result not assigned to the same slice'
- text: 'string `label_id` has 3 occurrences, make it a constant'
linters:
- goconst
- text: 'string `labels` has 3 occurrences, make it a constant'
linters:
- goconst
- text: 'string `off` has 6 occurrences, make it a constant'
linters:
- goconst

View File

@ -7,275 +7,6 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
All releases can be found on https://code.vikunja.io/api/releases.
## [0.21.0] - 2023-07-07
### Bug Fixes
* *(CalDAV)* Naming
* *(api)* License (#1457)
* *(build)* Make sure the docker image can access go tools
* *(caldav)* Do not create label if it exists by title (#1444)
* *(caldav)* Incoming tasks do not get correct time zone (#1455)
* *(ci)* Pipeline dependency
* *(cli)* Rename user project command
* *(docker)* Don't chown everything in Vikunja's default root folder
* *(docs)* Added Keycloak OpenID example (#1521)
* *(docs)* Clarify error codes in swagger docs
* *(docs)* Link to usage/api
* *(docs)* Semver link (#1470)
* *(filter)* Don't try to get the real subscription for a saved filter project
* *(filters)* Return all filters with all projects, not grouped under a pseudo project
* *(filters)* Sorting tasks from filters
* *(image)* Json type of struct property (#1469)
* *(import)* Don't try to load a nonexistant attachment file
* *(lint)* Disable misspell linter on redoc
* *(migration)* Don't try to fetch task details of tasks whose projects are deleted
* *(migration)* Enable insert from structure work recursively
* *(migration)* Make file migration work with new structure
* *(migration)* Remove unused is_deleted flag from Todoist api response
* *(migration)* Remove wunderlist leftovers
* *(migration)* Remove wunderlist leftovers
* *(migration)* Remove wunderlist leftovers
* *(migration)* Rename TickTick migration
* *(migration)* Revert wrongly changed url
* *(migration)* Use correct struct
* *(project)* Don't allow un-archiving a project when its parent project is archived
* *(project)* Don't check for namespaces in overdue reminders
* *(project)* Duplicate project into parent project
* *(project)* Recursively get all users from all parent projects
* *(project)* Remove comments, clarifications, notifications about namespaces
* *(project)* Remove namespaces checks
* *(project)* Remove namespaces from creating projects
* *(project)* Remove namespaces from getting projects
* *(projects)* Delete project in the correct order
* *(projects)* Don't allow making a project child of itself
* *(projects)* Don't check if new projects are archived
* *(projects)* Don't fail to fetch a task if there's a broken subscription record associated to it
* *(projects)* Don't return child projects twice
* *(projects)* Don't try to share for nonexisting namespace
* *(projects)* Permission check now works
* *(projects)* Properly check if a user or link share is allowed to create a new project
* *(projects)* Recalculate project's position after dragging when position would be 0
* *(projects)* Reset pagination limit when fetching subprojects
* *(projects)* Return subprojects which were shared from another user
* *(saved filters)* Don't let query parameters override saved sorting parameters
* *(spelling)* In config sample (#1489)
* *(task)* Don't build partial task identifier
* *(task)* Don't try to return a project identifier if there is no project
* *(tasks)* Don't check for namespaces in filters
* *(tasks)* Get all tasks from parent projects
* *(tasks)* Make sure task deleted notification actually has information about the deleted task
* *(tasks)* Read all tests
* *(tasks)* Return a correct task identifier if the list does not have a good one set
* *(tasks)* Sql for overdue reminders
* *(tasks)* Task relation test
* *(test)* Adjust fixture bucket and list ids
* *(test)* Adjust fixture id
* *(test)* Fixtures
* *(test)* Use correct filter id
* *(tests)* Adjust parent projects
* *(tests)* Make the tests compile again
* *(tests)* Permission tests for parent projects
* *(tests)* Subscription test fixtures
* *(tests)* Task collection fixtures
* *(tests)* Task permissions from parents
* Accept for migrations ([8edbca3](8edbca39cf9d771645d6feb05ee94eebc6403cbf))
* Add missing error code ([f2d943f](f2d943f5c4f1b13ef565692b893da05c6669c6d0))
* Add missing license header ([f4e12da](f4e12dab273474c0eb27f59c00faa828bb86522c))
* Align "ID" param for Delete and Update method of Task model ([b6d5605](b6d5605ef6b2799f939d016b1572b3d43e857d4d))
* Align "otherTaskID" param for Delete method of TaskRelation model ([ac377a7](ac377a7a5d708ef7543d99f716ceaa1ee8502649))
* Align namespaceID param ([7ada82e](7ada82ea926556ae39d106dc85d5a05f3c1c8cd3))
* Align task ID param ([f76bb2b](f76bb2b4a9c8a3b53bc73d0913ba94bba350f5da))
* Check if usernames contain spaces when creating a new user ([672fb35](672fb35bcbb47e4c0331813aa837fee28f372471))
* Compile errors ([a21bff3](a21bff3ffb8497d6e1b6c3bb50d9a9b2469f4eb0))
* Correctly pass unix socket to xorm ([7ad256f](7ad256f6cd3e15aeafce2bc29c28c458c3abdc0a))
* Docs auth openID method ([4f7d69a](4f7d69a108a2836e90b3c7ffe7f05247d80bfb85))
* Don't get favorite task projects filter multiple times ([a51bbd1](a51bbd1159fb1ada5980a5b27972ccf1404641af))
* Don't send bad request errors to sentry ([c0c523f](c0c523f0a8c83eb164febbc508ac98142d572d7a))
* Don't try to load subscriptions for nonexistent projects ([b519462](b5194624e021360ccdec20cb58bba57c23028c3f))
* Fetch all tasks for all projects ([353279c](353279cbff8fd6fa6b1bb81a8726a7a5a1b6b623))
* ILIKE helper ([dff4e01](dff4e01327907d42bf0b20a20912e5e9c69dd23e))
* Lint ([50c922b](50c922b7d1135b8f75478b89502fe0bb4c39547f))
* Lint ([ad06903](ad0690369f39dab3683ac5ef7664bd765fa1cb18))
* Lint ([e17b63b](e17b63b9201889946e91e7e295f31a80055c6ae4))
* Lint ([ef779e8](ef779e8730af169101bf1ebffb8d2522e5c6b7bc))
* Lint ([f0dcce7](f0dcce702f03f237ecde107a7ba62f61e2c3e313))
* Lint config ([9111db2](9111db2a16df6a4eec9e3cc2021bc6fdcace9ead))
* Lint errors ([ebc3dd2](ebc3dd2b3e72f56880320480829aead1bf554f67))
* Make it compile again ([d79c393](d79c393e5b4e880b8b09ce5944e8247ae07c4d58))
* Make sure Vikunja is buildable without swagger docs present ([47e4223](47e42238ef47ad6e4e90284593aae278e77c8631))
* Make sure projects are correctly sorted ([db3c7aa](db3c7aa8b04e828fafdf10bcfd5bde8cf19e6f10))
* Provide a proper error message when viewing a link share with an invalid token ([aa43127](aa43127e52aeb7412b13b4aaab091442dad534db))
* Reminder fixture ([4b00f22](4b00f224d92f0c6933f6cba14433538d64545eca))
* Remove old saved openid provider settings from cache when starting Vikunja ([9bf535d](9bf535d06f5b9bb455979b0bf3b6f0942daa1c9e))
* Rename after rebase ([e93a5ff](e93a5ff11fee7adac2897b3251db7abbbad4bcc5))
* Rename incorrectly named ProjectUsers method ([7e53a21](7e53a214070ee9b48fdffffcc42de9250c323e96))
* Rename project receiver variable ([f1cbe50](f1cbe50605b46e506c3233cc8da4b325f5727c87))
* Spelling ([fc2cc4a](fc2cc4a1555ca7e63ff902cde62380035a60ebb8))
* Test fixtures ([06f1d2e](06f1d2e91237195f8e720d4dd55b491b91e6547d))
* Test import ([fb818ea](fb818ea1867f8db813ff52622695fd206c21452e))
* Trello import tests ([61a3380](61a3380a9482312eac56f4cfd436517205f601aa))
* Typo ([4c698dc](4c698dc7c71418239e24b1756604371dcb6a2f74))
* Typo in email template ([2dad404](2dad4042170677af3db7be85cbe978ce6be721aa))
* Update redoc ([8916de0](8916de03666482c2319689e950d30a6fb737f239))
* Update xgo in dockerfile to 1.20.2 ([33f0d0f](33f0d0f85a7fdfd509bc8a4aad26df95c064468c))
* Upgrade jwt v5 ([359d051](359d0512cc7e73cdde9d4dd145332591c6743d11))
* Use rewrite when hosting frontend files via the api ([b56e45d](b56e45d74389d38c747887d3cb2a2b295bb549c7))
* Users_lists name in migration ([0a3fdc0](0a3fdc0344790f059140d8e482b028ffecdb3e4b))
* Using mysql via a socket ([0a6bbc2](0a6bbc2efd6bb4468c72cff2a70cd29350a50b75))
### Dependencies
* *(deps)* Update module github.com/imdario/mergo to v0.3.14
* *(deps)* Update github.com/arran4/golang-ical digest to 19abf92
* *(deps)* Update goreleaser/nfpm docker tag to v2.27.1 (#1438)
* *(deps)* Update module github.com/swaggo/swag to v1.8.11
* *(deps)* Update module github.com/imdario/mergo to v0.3.15 (#1443)
* *(deps)* Update golangci-lint to 1.52.1
* *(deps)* Update module github.com/wneessen/go-mail to v0.3.9
* *(deps)* Update github.com/gocarina/gocsv digest to 9a18a84
* *(deps)* Update module github.com/swaggo/swag to v1.8.12
* *(deps)* Update module github.com/getsentry/sentry-go to v0.20.0
* *(deps)* Update module github.com/redis/go-redis/v9 to v9.0.3
* *(deps)* Update goreleaser/nfpm docker tag to v2.28.0 (#1475)
* *(deps)* Update src.techknowlogick.com/xgo digest to bff48e4 (#1474)
* *(deps)* Update module golang.org/x/sys to v0.7.0
* *(deps)* Update github.com/gocarina/gocsv digest to 6445c2b
* *(deps)* Update module golang.org/x/term to v0.7.0
* *(deps)* Update module github.com/spf13/cobra to v1.7.0
* *(deps)* Update module golang.org/x/image to v0.7.0
* *(deps)* Update module golang.org/x/oauth2 to v0.7.0
* *(deps)* Update module golang.org/x/crypto to v0.8.0
* *(deps)* Update module github.com/prometheus/client_golang to v1.15.0
* *(deps)* Update module github.com/lib/pq to v1.10.8
* *(deps)* Update module github.com/go-sql-driver/mysql to v1.7.1
* *(deps)* Update module github.com/lib/pq to v1.10.9
* *(deps)* Update src.techknowlogick.com/xgo digest to e65295a
* *(deps)* Update github.com/arran4/golang-ical digest to f69e132
* *(deps)* Update module github.com/redis/go-redis/v9 to v9.0.4
* *(deps)* Update module github.com/go-testfixtures/testfixtures/v3 to v3.9.0
* *(deps)* Update module github.com/prometheus/client_golang to v1.15.1
* *(deps)* Update module golang.org/x/term to v0.8.0
* *(deps)* Update src.techknowlogick.com/xgo digest to 52d704d
* *(deps)* Update module github.com/swaggo/swag to v1.16.1
* *(deps)* Update module golang.org/x/sync to v0.2.0
* *(deps)* Update module github.com/getsentry/sentry-go to v0.21.0
* *(deps)* Update module golang.org/x/oauth2 to v0.8.0
* *(deps)* Update module golang.org/x/crypto to v0.9.0
* *(deps)* Update alpine docker tag to v3.18
* *(deps)* Update github.com/gocarina/gocsv digest to 7f30c79
* *(deps)* Update module github.com/magefile/mage to v1.15.0
* *(deps)* Update github.com/gocarina/gocsv digest to 9ddd7fd
* *(deps)* Update module github.com/coreos/go-oidc/v3 to v3.6.0
* *(deps)* Update module github.com/stretchr/testify to v1.8.3
* *(deps)* Update module github.com/labstack/echo-jwt/v4 to v4.2.0
* *(deps)* Update goreleaser/nfpm docker tag to v2.29.0 (#1528)
* *(deps)* Update module github.com/ulule/limiter/v3 to v3.11.2
* *(deps)* Update module github.com/redis/go-redis/v9 to v9.0.5
* *(deps)* Update module github.com/imdario/mergo to v0.3.16
* *(deps)* Update module github.com/stretchr/testify to v1.8.4
* *(deps)* Update module github.com/spf13/viper to v1.16.0
* *(deps)* Update github.com/vectordotdev/go-datemath digest to 640a500 (#1532)
* *(deps)* Update module github.com/mattn/go-sqlite3 to v1.14.17
* *(deps)* Update klakegg/hugo docker tag to v0.110.0 (#1538)
* *(deps)* Update golangci
* *(deps)* Update klakegg/hugo docker tag to v0.111.0 (#1539)
* *(deps)* Update klakegg/hugo docker tag to v0.111.3 (#1542)
* *(deps)* Update src.techknowlogick.com/xgo digest to 494bc06
* *(deps)* Update goreleaser/nfpm docker tag to v2.30.1 (#1540)
* *(deps)* Update module golang.org/x/sys to v0.9.0
* *(deps)* Update module golang.org/x/term to v0.9.0
* *(deps)* Update module golang.org/x/image to v0.8.0
* *(deps)* Update module golang.org/x/crypto to v0.10.0
* *(deps)* Update module golang.org/x/oauth2 to v0.9.0
* *(deps)* Update module golang.org/x/sync to v0.3.0
* *(deps)* Update github.com/gocarina/gocsv digest to 2696de6
* *(deps)* Update module github.com/prometheus/client_golang to v1.16.0
* *(deps)* Update module github.com/getsentry/sentry-go to v0.22.0
* *(deps)* Update github.com/gocarina/gocsv digest to 99d496c
* *(deps)* Update module github.com/imdario/mergo to v1 (#1559)
* *(deps)* Update github.com/dustinkirkland/golang-petname digest to e794b93
* *(deps)* Update module golang.org/x/sys to v0.10.0
* *(deps)* Update module golang.org/x/image to v0.9.0
* *(deps)* Update module golang.org/x/term to v0.10.0
* *(deps)* Update module golang.org/x/crypto to v0.11.0
* *(deps)* Update module golang.org/x/oauth2 to v0.10.0
### Documentation
* Add docs for installing with sqlite in docker (#70) ([a16fd67](a16fd67b51c02e09ef6709bee9ad2b341d80cd73))
* Add information about our Helm Chart ([22f89c1](22f89c1ccc3a281a75db9e42702604f88eb0568b))
* Fix menu links ([1f13b5d](1f13b5d7b4041042ea3b26ac2a850784b11ac377))
* Remove all traces of namespaces ([3b0935d](3b0935d033c6b5060f18e955acf4a647eb10721b))
* Remove outdated information ([327bb3b](327bb3bed99e0a4c5664251e3af15accf1a13062))
* Update error references to list ([259cf7d](259cf7d25bbb7a289fe9569c81c6f7d3855543bf))
* Update prometheus docs for clarity (#1458)
* Update references to list ([8dc6c95](8dc6c95333b38eb83c8053c628d05599e79dd27e))
### Features
* *(caldav)* Sync Reminders / VALARM (#1415)
* *(docs)* Change order of sections in nav (#1471)
* *(docs)* Various improvements
* *(kanban)* Return the total task count per bucket
* *(migration)* Ignore namespace changes
* *(migration)* Use new structure for migration
* *(projects)* Add parent project, migrate namespaces
* *(projects)* Check all parent projects for permissions
* *(projects)* Check parent project when checking archived status
* *(projects)* Cleanup namespace leftovers
* *(projects)* Don't allow deleting or archiving the default project
* *(projects)* Get all projects recursively
* *(projects)* Remove namespaces
* *(projects)* Return a favorites pseudo project when the user has favorite tasks
* *(subscriptions)* Make sure all subscriptions are inherited properly
* *(users)* Don't hide user email if it was the search request* Rename lists to projects ([349e6a5](349e6a59050a0beba82a7f626c2f72f6b8c88dde))
* Add logging options to mailer settings ([9590b82](9590b82c11852666524eeab562988226574a1b1c))
* Add relative Reminders (#1427) ([3f5252d](3f5252dc24a3dea89b2e049ccb1f9d0a59a89a88))
* Add token example ([4417223](441722372af3349b677dc013b1863e678b0e7158))
* Allow saving frontend settings via api ([04e2c51](04e2c51fac24a045abe1a85c8b661b6bc628686c))
* Allow to find users with access to a project more freely ([a7231e1](a7231e197e3d86d3ef27fad89ae60863d25b5df0))
* Check for cycles when creating or updating a project's parent ([9011894](9011894a2975d9d112dc3db453739e13261c0716))
* Generate swagger docs at build time ([efa24ce](efa24cec44865c5a8ab42a106deeb331ad1bed91))
* Improve relation kinds docs ([b826c13](b826c13f385b24ed1b33b8890cc5cdd5fe8b8f22))
* Make the new inbox project the default ([0110f93](0110f933134af0460d9fed9d652148c98e94b6cd))
* Migrate lists to projects in db identifiers ([2fba7bd](2fba7bdf02983e5cf7def09803def4cbf830f53b))
* Remove ChildProjects project property ([edcb806](edcb806421c2181a8b85aed5b53e8da6350b9630))
* Remove namespaces, make projects infinitely nestable (#1362) ([82beb3b](82beb3bf671ca0670b714160f0b4d9c186dfe120))
* Rename all list files ([8f4abd2](8f4abd2fe86e7a23d80bc5ebc4fc1ae75e1b78fb))
* Rename lists to projects ([47c2da7](47c2da7f1856e95956cdb968fa95295d3441a9f6))
* Rename lists to projects ([96a0f5e](96a0f5e169c9e8f8d20e3fe1d9de5eecead53ac9))
* Rename lists to projects ([fc73c84](fc73c84bf2b9a7cbd2f6cbd2a83ea9ccc3fd58fd))
* Rename lists to projects everywhere (#1318) ([869d4a3](869d4a336cb122df894acf040e02b6b2ba786fdb))
### Miscellaneous Tasks
* *(changelog)* Fix spelling
* *(docs)* Add info about `/buckets` sorting
* *(docs)* Move login and register routes to auth category in api docs
* *(docs)* Update error docs
* *(docs)* Update list -> project
* *(docs/translation)* Remove mention of weblate
* *(export)* Remove unused events
* *(project)* Fmt
* *(projects)* use a slice again ([3e8d1b3](3e8d1b3667ccfb2960650a4506771ec3c9b3a970))
* *(test)* Show table content when db assertion failed
* Cleanup ([7a9611c](7a9611c2daa41ec2da135a2a4e804551e4ab8ff2))
* Disable false-positive linter for generated docs ([076e857](076e857507a4cf59e0b0399a2e51a8d8baa03065))
* Fix comment url ([5856f21](5856f21f31fe7b81e7ffd203f70460785955411c))
* Fix spelling ([cd90db3](cd90db3117a7fa40175ecebd3ca37cc94a46e1ee))
* Generate swagger docs ([55410ea](55410ea73d50f5bc124eaf411c77125024b6fefa))
* Go mod tidy ([93056da](93056da792dafa70f91f7d114669997b3f93f7f1))
* Go mod tidy ([e5dde31](e5dde315fb6a7163546b9f88ebafacc886744db3))
* Remove cache options ([d83e3a0](d83e3a0a037b9a4d40ce22c8c51932eb23963ac2))
* Remove reminderDates after frontend is migrated to reminders (#1448) ([4a4ba04](4a4ba041e0f3e9c71dd4844d5191c9cbe4e4e3b7))
* Rename files (fix typo) ([6aadaaa](6aadaaaffc1fff4a94e35e8fa3f6eab397cbc3ce))
## [0.20.4] - 2023-03-12
### Bug Fixes
@ -2175,3 +1906,4 @@ Misc bugfixes and improvements to the build process
## [0.2] - 2018-10-17
## [0.1] - 2018-09-20

View File

@ -1,3 +0,0 @@
# Contribution Guidelines
Please check out the guidelines on https://vikunja.io/docs/development/

View File

@ -3,7 +3,7 @@
# │─││ │││ │ │
# ┘─┘┘─┘┘┘─┘┘─┘
FROM --platform=$BUILDPLATFORM techknowlogick/xgo:go-1.21.x AS builder
FROM --platform=$BUILDPLATFORM techknowlogick/xgo:go-1.20.x AS builder
RUN go install github.com/magefile/mage@latest && \
mv /go/bin/mage /usr/local/go/bin
@ -13,7 +13,6 @@ COPY . ./
ARG TARGETOS TARGETARCH TARGETVARIANT
ENV GOPROXY https://goproxy.kolaente.de
RUN export PATH=$PATH:$GOPATH/bin && \
mage build:clean && \
mage release:xgo "${TARGETOS}/${TARGETARCH}/${TARGETVARIANT}"
@ -25,7 +24,7 @@ RUN export PATH=$PATH:$GOPATH/bin && \
# The actual image
# Note: I wanted to use the scratch image here, but unfortunatly the go-sqlite bindings require cgo and
# because of this, the container would not start when I compiled the image without cgo.
FROM alpine:3.18 AS runner
FROM alpine:3.16 AS runner
LABEL maintainer="maintainers@vikunja.io"
WORKDIR /app/vikunja
ENTRYPOINT [ "/sbin/tini", "-g", "--", "/entrypoint.sh" ]

View File

@ -2,7 +2,7 @@
[![Build Status](https://drone.kolaente.de/api/badges/vikunja/api/status.svg)](https://drone.kolaente.de/vikunja/api)
[![License: AGPL v3](https://img.shields.io/badge/License-AGPL%20v3-blue.svg)](LICENSE)
[![Download](https://img.shields.io/badge/download-v0.21.0-brightgreen.svg)](https://dl.vikunja.io)
[![Download](https://img.shields.io/badge/download-v0.20.4-brightgreen.svg)](https://dl.vikunja.io)
[![Docker Pulls](https://img.shields.io/docker/pulls/vikunja/api.svg)](https://hub.docker.com/r/vikunja/api/)
[![Swagger Docs](https://img.shields.io/badge/swagger-docs-brightgreen.svg)](https://try.vikunja.io/api/v1/docs)
[![Go Report Card](https://goreportcard.com/badge/kolaente.dev/vikunja/api)](https://goreportcard.com/report/kolaente.dev/vikunja/api)
@ -26,7 +26,13 @@ If you find any security-related issues you don't want to disclose publicly, ple
## Features
See [the features page](https://vikunja.io/features/) on our website for a more exaustive list or
* Create TODO lists with tasks
* Reminder for tasks
* Namespaces: A "group" which bundles multiple lists
* Share lists and namespaces with teams and users with granular permissions
* Plenty of details for tasks
See [the features page](https://vikunja.io/en/features/) on our website for a more exaustive list or
try it on [try.vikunja.io](https://try.vikunja.io)!
## Docs
@ -52,4 +58,4 @@ Fork -> Push -> Pull-Request. Also see the [dev docs](https://vikunja.io/docs/de
## License
This project is licensed under the AGPLv3 License. See the [LICENSE](LICENSE) file for the full license text.
This project is licensed under the AGPLv3 License. See the [LICENSE](LICENSE) file for the full license text.

View File

@ -0,0 +1,12 @@
#!/usr/bin/env bash
curl -X POST http://localhost:3456/api/v1/register -H 'Content-Type: application/json' -d '{"username":"demo","password":"demo","email":"demo@vikunja.io"}'
BEARER=`curl -X POST -H 'Content-Type: application/json' -d '{"username": "demo", "password":"demo"}' localhost:3456/api/v1/login | jq -r '.token'`
echo "Bearer: $BEARER"
curl -X POST localhost:3456/api/v1/tokenTest -H "Authorization: Bearer $BEARER"
curl -X PUT localhost:3456/api/v1/namespaces/1/lists -H 'Content-Type: application/json' -H "Authorization: Bearer $BEARER" -d '{"title":"lorem"}'
curl -X PUT localhost:3456/api/v1/lists/1 -H 'Content-Type: application/json' -H "Authorization: Bearer $BEARER" -d '{"text":"lorem"}'
curl -X PUT -H "Authorization: Bearer $BEARER" localhost:3456/api/v1/tasks/1/attachments -F 'files=@/home/konrad/Pictures/Wallpaper/greg-rakozy-_Q4mepyyjMw-unsplash.jpg'

29
REST-Tests/auth.http Normal file
View File

@ -0,0 +1,29 @@
### Authorization by token, part 1. Retrieve and save token.
POST http://localhost:8080/api/v1/login
Content-Type: application/json
{
"username": "user3",
"password": "1234"
}
> {% client.global.set("auth_token", response.body.token); %}
### Register
POST http://localhost:8080/api/v1/register
Content-Type: application/json
{
"username": "user",
"password": "1234",
"email": "5@knt.li"
}
###
# Token test
POST http://localhost:8080/api/v1/tokenTest
Authorization: Bearer {{auth_token}}
Content-Type: application/json
###

70
REST-Tests/labels.http Normal file
View File

@ -0,0 +1,70 @@
# Get all labels
GET http://localhost:8080/api/v1/labels
Authorization: Bearer {{auth_token}}
###
# Add a new label
PUT http://localhost:8080/api/v1/labels
Authorization: Bearer {{auth_token}}
Content-Type: application/json
{
"title": "test5"
}
###
# Delete a label
DELETE http://localhost:8080/api/v1/labels/6
Authorization: Bearer {{auth_token}}
###
# Update a label
POST http://localhost:8080/api/v1/labels/1
Authorization: Bearer {{auth_token}}
Content-Type: application/json
{
"title": "testschinkenbrot",
"description": "käsebrot"
}
###
# Get one label
GET http://localhost:8080/api/v1/labels/1
Authorization: Bearer {{auth_token}}
###
# Get all labels on a task
GET http://localhost:8080/api/v1/tasks/3565/labels
Authorization: Bearer {{auth_token}}
###
# Add a new label to a task
PUT http://localhost:8080/api/v1/tasks/35236365/labels
Authorization: Bearer {{auth_token}}
Content-Type: application/json
{
"label_id": 1
}
###
# Delete a label from a task
DELETE http://localhost:8080/api/v1/tasks/3565/labels/1
Authorization: Bearer {{auth_token}}
###
# Add a new label to a task
POST http://localhost:8080/api/v1/tasks/3565/labels/bulk
Authorization: Bearer {{auth_token}}
Content-Type: application/json
{
"labels": [
{"id": 1},
{"id": 2},
{"id": 3}
]
}
###

177
REST-Tests/lists.http Normal file
View File

@ -0,0 +1,177 @@
# Get all lists
GET http://localhost:8080/api/v1/namespaces/35/lists
Authorization: Bearer {{auth_token}}
###
# Get one list
GET http://localhost:8080/api/v1/lists/3
Authorization: Bearer {{auth_token}}
###
# Add a new list
PUT http://localhost:8080/api/v1/namespaces/35/lists
Authorization: Bearer {{auth_token}}
Content-Type: application/json
{
"title": "test"
}
###
# Add a new item
PUT http://localhost:8080/api/v1/lists/1
Authorization: Bearer {{auth_token}}
Content-Type: application/json
{
"text": "Task",
"description": "Schinken"
}
###
# Delete a task from a list
DELETE http://localhost:8080/api/v1/lists/14
Authorization: Bearer {{auth_token}}
###
# Get all teams who have access to that list
GET http://localhost:8080/api/v1/lists/28/teams
Authorization: Bearer {{auth_token}}
###
# Give a team access to that list
PUT http://localhost:8080/api/v1/lists/1/teams
Authorization: Bearer {{auth_token}}
Content-Type: application/json
{"team_id":2, "right": 1}
###
# Update a teams access to that list
POST http://localhost:8080/api/v1/lists/1/teams/2
Authorization: Bearer {{auth_token}}
Content-Type: application/json
{"right": 0}
###
# Delete a team from a list
DELETE http://localhost:8080/api/v1/lists/10235/teams/1
Authorization: Bearer {{auth_token}}
###
# Delete a team from a list
DELETE http://localhost:8080/api/v1/lists/10235/teams/1
Authorization: Bearer {{auth_token}}
###
# Get all users who have access to that list
GET http://localhost:8080/api/v1/lists/28/users
Authorization: Bearer {{auth_token}}
###
# Give a user access to that list
PUT http://localhost:8080/api/v1/lists/3/users
Authorization: Bearer {{auth_token}}
Content-Type: application/json
{"userID":"user4", "right":1}
###
# Update a users access to that list
POST http://localhost:8080/api/v1/lists/30/users/3
Authorization: Bearer {{auth_token}}
Content-Type: application/json
{"right":2}
###
# Delete a user from a list
DELETE http://localhost:8080/api/v1/lists/28/users/3
Authorization: Bearer {{auth_token}}
###
# Get all pending tasks
GET http://localhost:8080/api/v1/tasks/all
Authorization: Bearer {{auth_token}}
###
# Get all pending tasks with priorities
GET http://localhost:8080/api/v1/tasks/all?sort=priorityasc
Authorization: Bearer {{auth_token}}
###
# Get all pending tasks in a range
GET http://localhost:8080/api/v1/tasks/all/dueadateasc/1546784000/1548784000
Authorization: Bearer {{auth_token}}
###
# Get all pending tasks in caldav
GET http://localhost:8080/api/v1/tasks/caldav
#Authorization: Bearer {{auth_token}}
###
# Update a task
POST http://localhost:8080/api/v1/tasks/3565
Authorization: Bearer {{auth_token}}
Content-Type: application/json
{
"priority": 0
}
###
# Bulk update multiple tasks at once
POST http://localhost:8080/api/v1/tasks/bulk
Authorization: Bearer {{auth_token}}
Content-Type: application/json
{
"task_ids": [3518,3519,3521],
"text":"bulkupdated"
}
###
# Get all assignees
GET http://localhost:8080/api/v1/tasks/3565/assignees
Authorization: Bearer {{auth_token}}
###
# Add a bunch of assignees
PUT http://localhost:8080/api/v1/tasks/3565/assignees/bulk
Authorization: Bearer {{auth_token}}
Content-Type: application/json
{
"assignees": [
{"id": 17}
]
}
###
# Get all users who have access to a list
GET http://localhost:8080/api/v1/lists/3/users
Authorization: Bearer {{auth_token}}
###

View File

@ -0,0 +1,71 @@
# Get all namespaces
GET http://localhost:8080/api/v1/namespaces
Authorization: Bearer {{auth_token}}
###
# Get one namespaces
GET http://localhost:8080/api/v1/namespaces/-1
Authorization: Bearer {{auth_token}}
###
# Get all users who have access to that namespace
GET http://localhost:8080/api/v1/namespaces/12/users
Authorization: Bearer {{auth_token}}
###
# Give a user access to that namespace
PUT http://localhost:8080/api/v1/namespaces/1/users
Authorization: Bearer {{auth_token}}
Content-Type: application/json
{"user_id":3, "right": 0}
###
# Update a users access to that namespace
POST http://localhost:8080/api/v1/namespaces/1/users/3
Authorization: Bearer {{auth_token}}
Content-Type: application/json
{"right": 2}
###
# Delete a user from a namespace
DELETE http://localhost:8080/api/v1/namespaces/1/users/2
Authorization: Bearer {{auth_token}}
###
# Get all teams who have access to that namespace
GET http://localhost:8080/api/v1/namespaces/1/teams
Authorization: Bearer {{auth_token}}
###
# Give a team access to that namespace
PUT http://localhost:8080/api/v1/namespaces/1/teams
Authorization: Bearer {{auth_token}}
Content-Type: application/json
{"team_id":3, "right": 0}
###
# Update a teams access to that namespace
POST http://localhost:8080/api/v1/namespaces/1/teams/1
Authorization: Bearer {{auth_token}}
Content-Type: application/json
{"right": 0}
###
# Delete a team from a namespace
DELETE http://localhost:8080/api/v1/namespaces/1/teams/2
Authorization: Bearer {{auth_token}}
###

29
REST-Tests/teams.http Normal file
View File

@ -0,0 +1,29 @@
# Get all teams
GET http://localhost:8080/api/v1/teams
Authorization: Bearer {{auth_token}}
###
# Get one team
GET http://localhost:8080/api/v1/teams/28
Authorization: Bearer {{auth_token}}
###
# Add a new member to that team
PUT http://localhost:8080/api/v1/teams/28/members
Authorization: Bearer {{auth_token}}
Content-Type: application/json
{
"user_id": 2
}
###
# Delete a member from a team
DELETE http://localhost:8080/api/v1/teams/28/members/2
Authorization: Bearer {{auth_token}}
###

53
REST-Tests/users.http Normal file
View File

@ -0,0 +1,53 @@
# Get all users
GET http://localhost:8080/api/v1/user
Authorization: Bearer {{auth_token}}
######
# Search for a user
GET http://localhost:8080/api/v1/users?s=3
Authorization: Bearer {{auth_token}}
###
## Update password
POST http://localhost:8080/api/v1/user/password
Authorization: Bearer {{auth_token}}
Content-Type: application/json
{
"old_password": "1234",
"new_password": "1234"
}
### Request a password to reset a password
POST http://localhost:8080/api/v1/user/password/token
Content-Type: application/json
Accept: application/json
{
"email": "k@knt.li"
}
### Request a token to reset a password
POST http://localhost:8080/api/v1/user/password/reset
Content-Type: application/json
Accept: application/json
{
"token": "eAsZzakgqARnjzXHqsHqZtSUKuiOhoJjHANhgTxUIDBSalhbtdpAdLeywGXzVDBuRQGNpHdMxoHXhLVSlzpJsFvuoJgMdkhRhkNhaQXfufuZCdtUlerZHSJQLgYMUryHIxIREcmZLtWoZVrYyARkCvkyFhcGtoCwQOEjAOEZMQQuxTVoGYfAqcfNggQnerUcXCiRIgRtkusXSnltomhaeyRwAbrckXFeXxUjslgplSGqSTOqJTYuhrSzAVTwNvuYyvuXLaZoNnJEyeVDWlRydnxfgUQjQZOKwCBRWVQPKpZhlslLUyUAMsRQkHITkruQCjDnOGCCRsSNplbNCEuDmMfpWYHSQAcQIDZtbQWkxzpfmHDMQvvKPPrxEnrTErlvTfKDKICFYPQxXNpNE",
"new_password": "1234"
}
### Confirm a users email address
POST http://localhost:8080/api/v1/user/confirm
Content-Type: application/json
Accept: application/json
{
"token": ""
}
###

View File

@ -1,5 +1,5 @@
Vikunja is a to-do list application to facilitate your life.
Copyright 2018-present Vikunja and contributors. All rights reserved.
Copyright 2018-2021 Vikunja and contributors. All rights reserved.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public Licensee as published by

View File

@ -59,9 +59,6 @@ service:
# The maximum size clients will be able to request for user avatars.
# If clients request a size bigger than this, it will be changed on the fly.
maxavatarsize: 1024
# If set to true, the frontend will show a big red warning not to use this instance for real data as it will be cleared out.
# You probably don't need to set this value, it was created specifically for usage on [try](https://try.vikunja.io).
demomode: false
database:
# Database type to use. Supported types are mysql, postgres and sqlite.
@ -94,17 +91,15 @@ database:
# Enable SSL/TLS for mysql connections. Options: false, true, skip-verify, preferred
tls: false
typesense:
# Whether to enable the Typesense integration. If true, all tasks will be synced to the configured Typesense
# instance and all search and filtering will run through Typesense instead of only through the database.
# Typesense allows fast fulltext search including fuzzy matching support. It may return different results than
# what you'd get with a database-only search.
cache:
# If cache is enabled or not
enabled: false
# The url to the Typesense instance you want to use. Can be hosted locally or in Typesense Cloud as long
# as Vikunja is able to reach it.
url: ''
# The Typesense API key you want to use.
apikey: ''
# Cache type. Possible values are "keyvalue", "memory" or "redis".
# When choosing "keyvalue" this setting follows the one configured in the "keyvalue" section.
# When choosing "redis" you will need to configure the redis connection separately.
type: keyvalue
# When using memory this defines the maximum size an element can take
maxelementsize: 1000
redis:
# Whether to enable redis or not
@ -342,17 +337,7 @@ defaultsettings:
default_project_id: 0
# Start of the week for the user. `0` is sunday, `1` is monday and so on.
week_start: 0
# The language of the user interface. Must be an ISO 639-1 language code followed by an ISO 3166-1 alpha-2 country code. Check https://kolaente.dev/vikunja/frontend/src/branch/main/src/i18n/lang for a list of possible languages. Will default to the browser language the user uses when signing up.
# The language of the user interface. Must be an ISO 639-1 language code. Will default to the browser language the user uses when signing up.
language: <unset>
# The time zone of each individual user. This will affect when users get reminders and overdue task emails.
timezone: <time zone set at service.timezone>
webhooks:
# Whether to enable support for webhooks
enabled: true
# The timout in seconds until a webhook request fails when no response has been received.
timoutseconds: 30
# The URL of [a mole instance](https://github.com/frain-dev/mole) to use to proxy outgoing webhook requests. You should use this and configure appropriately if you're not the only one using your Vikunja instance. More info about why: https://webhooks.fyi/best-practices/webhook-providers#implement-security-on-egress-communication. Must be used in combination with `webhooks.password` (see below).
proxyurl:
# The proxy password to use when authenticating against the proxy.
proxypassword:

View File

@ -17,9 +17,7 @@ menu:
## General
To contribute to Vikunja, fork the project and work on the main branch.
Once you feel like your changes are ready, open a PR in the respective repo [on our Gitea instance](https://kolaente.dev/vikunja).
We cannot accept PRs on mirror sites.
Once you feel like your changes are ready, open a PR in the respective repo.
A maintainer will take a look and give you feedback. Once everyone is happy, the PR gets merged and released.
If you plan to do a bigger change, it is better to open an issue for discussion first.
@ -28,7 +26,7 @@ If you plan to do a bigger change, it is better to open an issue for discussion
The code for the api is located at [code.vikunja.io/api](https://code.vikunja.io/api).
You'll need at least Go 1.21 to build Vikunja's api.
We use go modules to manage third-party libraries for Vikunja, so you'll need at least go `1.17` to use these.
A lot of developing tasks are automated using a Magefile, so make sure to [take a look at it]({{< ref "mage.md">}}).
@ -40,51 +38,11 @@ Make sure to check the other doc articles for specific development tasks like [t
The code for the frontend is located at [code.vikunja.io/frontend](https://code.vikunja.io/frontend).
More instructions can be found in the repo's README.
You need to have [pnpm](https://pnpm.io/) and Node.JS in version 18 or higher installed.
You need to have [pnpm](https://pnpm.io/) and nodejs in version 16 or 18 installed.
## Pull Requests
All Pull Requests must be made [on our Gitea instance](https://kolaente.dev/vikunja).
We cannot accept PRs on mirror sites.
Please try to make your pull request easy to review.
For that, please read the [*Best Practices for Faster Reviews*](https://github.com/kubernetes/community/blob/261cb0fd089b64002c91e8eddceebf032462ccd6/contributors/guide/pull-requests.md#best-practices-for-faster-reviews) guide.
It has lots of useful tips for any project you may want to contribute to.
Some of the key points:
- Make small pull requests.
The smaller, the faster to review and the more likely it will be merged soon.
- Don't make changes unrelated to your PR.
Maybe there are typos on some comments, maybe refactoring would be welcome on a function…
but if that is not related to your PR, please make *another* PR for that.
- Split big pull requests into multiple small ones.
An incremental change will be faster to review than a huge PR.
- Allow edits by maintainers. This way, the maintainers will take care of merging the PR later on instead of you.
### PR title and summary
In the PR title, describe the problem you are fixing, not how you are fixing it.
Use the first comment as a summary of your PR.
In the PR summary, you can describe exactly how you are fixing this problem.
Keep this summary up-to-date as the PR evolves.
If your PR changes the UI, you must add **after** screenshots in the PR summary.
If your PR closes an issue, you must note that in a way that both GitHub and Gitea understand, i.e. by appending a paragraph like
```text
Fixes/Closes/Resolves #<ISSUE_NR_X>.
Fixes/Closes/Resolves #<ISSUE_NR_Y>.
```
to your summary.
Each issue that will be closed must stand on a separate line.
If your PR is related to a discussion in the forum, you must add a link to the forum discussion.
### Git flow
## Git flow
The `main` branch is the latest and bleeding edge branch with all changes. Unstable releases are automatically created from this branch.
New Pull-Requests should be made against the `main` branch.
A release gets tagged from the main branch with the version name as tag name.
@ -94,4 +52,4 @@ Backports and point-releases should go to a `release/version` branch, based on t
We're using [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) because they greatly simplify generating release notes.
It is not required to use them when creating a PR, but appreciated.
It is not required to use them when creating a PR, but appreciated.

View File

@ -100,10 +100,9 @@ You should also document the routes with [swagger annotations]({{< ref "swagger-
## Insertion helper method
There is a method available in the `migration` package which takes a fully nested Vikunja structure and creates it with all relations.
This means you start by adding a project, then add projects inside that project, then tasks in the lists and so on.
In general, it is reccommended to have one root project with all projects of the other service as child projects.
This means you start by adding a namespace, then add projects inside that namespace, then tasks in the lists and so on.
The root structure must be present as `[]*models.ProjectWithTasksAndBuckets`. It allows to represent all of Vikunja's hierarchy as a single data structure.
The root structure must be present as `[]*models.NamespaceWithProjectsAndTasks`. It allows to represent all of Vikunja's hierarchy as a single data structure.
Then call the method like so:

View File

@ -25,7 +25,7 @@ As an example, this is the definition of a project with all comments:
type Project struct {
// The unique, numeric id of this project.
ID int64 `xorm:"bigint autoincr not null unique pk" json:"id" param:"project"`
// The title of the project. You'll see this in the overview.
// The title of the project. You'll see this in the namespace overview.
Title string `xorm:"varchar(250) not null" json:"title" valid:"required,runelength(1|250)" minLength:"1" maxLength:"250"`
// The description of the project.
Description string `xorm:"longtext null" json:"description"`
@ -34,14 +34,13 @@ type Project struct {
// The hex color of this project
HexColor string `xorm:"varchar(6) null" json:"hex_color" valid:"runelength(0|6)" maxLength:"6"`
OwnerID int64 `xorm:"bigint INDEX not null" json:"-"`
ParentProjectID int64 `xorm:"bigint INDEX null" json:"parent_project_id"`
ParentProject *Project `xorm:"-" json:"-"`
OwnerID int64 `xorm:"bigint INDEX not null" json:"-"`
NamespaceID int64 `xorm:"bigint INDEX not null" json:"namespace_id" param:"namespace"`
// The user who created this project.
Owner *user.User `xorm:"-" json:"owner" valid:"-"`
// Whether a project is archived.
// Whether or not a project is archived.
IsArchived bool `xorm:"not null default false" json:"is_archived" query:"is_archived"`
// The id of the file this project has set as background
@ -51,7 +50,7 @@ type Project struct {
// Contains a very small version of the project background to use as a blurry preview until the actual background is loaded. Check out https://blurha.sh/ to learn how it works.
BackgroundBlurHash string `xorm:"varchar(50) null" json:"background_blur_hash"`
// True if a project is a favorite. Favorite projects show up in a separate parent project. This value depends on the user making the call to the api.
// True if a project is a favorite. Favorite projects show up in a separate namespace. This value depends on the user making the call to the api.
IsFavorite bool `xorm:"-" json:"is_favorite"`
// The subscription status for the user reading this project. You can only read this property, use the subscription endpoints to modify it.

View File

@ -67,6 +67,7 @@ Beispiel: „Benutzer:in“
| Englisches Original | Verwendung in deutscher Übersetzung |
| ------------------- | -------------------- |
| Bucket | Spalte |
| Namespace | Namespace |
| Link Share | Linkfreigabe |
| Username | Anmeldename |

View File

@ -19,7 +19,7 @@ To completely build Vikunja from source, you need to build the api and frontend.
The Vikunja API has no other dependencies than go itself.
That means compiling it boils down to these steps:
1. Make sure [Go](https://golang.org/doc/install) is properly installed on your system. You'll need at least Go `1.21`.
1. Make sure [Go](https://golang.org/doc/install) is properly installed on your system. You'll need at least Go `1.19`.
2. Make sure [Mage](https://magefile.org) is properly installed on your system.
3. Clone the repo with `git clone https://code.vikunja.io/api` and switch into the directory.
4. Run `mage build` in the source of this repo. This will build a binary in the root of the repo which will be able to run on your system.

View File

@ -333,18 +333,6 @@ Full path: `service.maxavatarsize`
Environment path: `VIKUNJA_SERVICE_MAXAVATARSIZE`
### demomode
If set to true, the frontend will show a big red warning not to use this instance for real data as it will be cleared out.
You probably don't need to set this value, it was created specifically for usage on [try](https://try.vikunja.io).
Default: `false`
Full path: `service.demomode`
Environment path: `VIKUNJA_SERVICE_DEMOMODE`
---
## database
@ -508,45 +496,43 @@ Environment path: `VIKUNJA_DATABASE_TLS`
---
## typesense
## cache
### enabled
Whether to enable the Typesense integration. If true, all tasks will be synced to the configured Typesense
instance and all search and filtering will run through Typesense instead of only through the database.
Typesense allows fast fulltext search including fuzzy matching support. It may return different results than
what you'd get with a database-only search.
If cache is enabled or not
Default: `false`
Full path: `typesense.enabled`
Full path: `cache.enabled`
Environment path: `VIKUNJA_TYPESENSE_ENABLED`
Environment path: `VIKUNJA_CACHE_ENABLED`
### url
### type
The url to the Typesense instance you want to use. Can be hosted locally or in Typesense Cloud as long
as Vikunja is able to reach it.
Cache type. Possible values are "keyvalue", "memory" or "redis".
When choosing "keyvalue" this setting follows the one configured in the "keyvalue" section.
When choosing "redis" you will need to configure the redis connection separately.
Default: `<empty>`
Default: `keyvalue`
Full path: `typesense.url`
Full path: `cache.type`
Environment path: `VIKUNJA_TYPESENSE_URL`
Environment path: `VIKUNJA_CACHE_TYPE`
### apikey
### maxelementsize
The Typesense API key you want to use.
When using memory this defines the maximum size an element can take
Default: `<empty>`
Default: `1000`
Full path: `typesense.apikey`
Full path: `cache.maxelementsize`
Environment path: `VIKUNJA_TYPESENSE_APIKEY`
Environment path: `VIKUNJA_CACHE_MAXELEMENTSIZE`
---
@ -1307,7 +1293,7 @@ Environment path: `VIKUNJA_DEFAULTSETTINGS_WEEK_START`
### language
The language of the user interface. Must be an ISO 639-1 language code followed by an ISO 3166-1 alpha-2 country code. Check https://kolaente.dev/vikunja/frontend/src/branch/main/src/i18n/lang for a list of possible languages. Will default to the browser language the user uses when signing up.
The language of the user interface. Must be an ISO 639-1 language code. Will default to the browser language the user uses when signing up.
Default: `<unset>`
@ -1327,53 +1313,3 @@ Full path: `defaultsettings.timezone`
Environment path: `VIKUNJA_DEFAULTSETTINGS_TIMEZONE`
---
## webhooks
### enabled
Whether to enable support for webhooks
Default: `true`
Full path: `webhooks.enabled`
Environment path: `VIKUNJA_WEBHOOKS_ENABLED`
### timoutseconds
The timout in seconds until a webhook request fails when no response has been received.
Default: `30`
Full path: `webhooks.timoutseconds`
Environment path: `VIKUNJA_WEBHOOKS_TIMOUTSECONDS`
### proxyurl
The URL of [a mole instance](https://github.com/frain-dev/mole) to use to proxy outgoing webhook requests. You should use this and configure appropriately if you're not the only one using your Vikunja instance. More info about why: https://webhooks.fyi/best-practices/webhook-providers#implement-security-on-egress-communication. Must be used in combination with `webhooks.password` (see below).
Default: `<empty>`
Full path: `webhooks.proxyurl`
Environment path: `VIKUNJA_WEBHOOKS_PROXYURL`
### proxypassword
The proxy password to use when authenticating against the proxy.
Default: `<empty>`
Full path: `webhooks.proxypassword`
Environment path: `VIKUNJA_WEBHOOKS_PROXYPASSWORD`

View File

@ -66,25 +66,3 @@ Google config:
- Configure an authorized redirect URI of `https://vikunja.mydomain.com/auth/openid/google`
Note that there currently seems to be no way to stop creation of new users, even when `enableregistration` is `false` in the configuration. This means that this approach works well only with an "Internal Organization" app for Google Workspace, which limits the allowed users to organizational accounts only. External / public applications will potentially allow every Google user to register.
## Keycloak
Vikunja Config:
```yaml
openid:
enabled: true
redirecturl: https://vikunja.mydomain.com/auth/openid/ <---- slash at the end is important
providers:
- name: Keycloak
authurl: https://keycloak.mydomain.com/realms/<relam-name>
logouturl: https://keycloak.mydomain.com/realms/<relam-name>/protocol/openid-connect/logout
clientid: <vikunja-id>
clientsecret: <vikunja secret>
```
Keycloak Config:
- Navigate to the keycloak instance
- Create a new client with the type `OpenID Connect` and a unique ID.
- Set `Client authentication` to On
- Set `Root Url` to `https://vikunja.mydomain.com`
- Set `Valid redirect URIs` to `/auth/openid/keycloak`
- Create the client the navigate to the credentials tab and copy the `Client secret`

View File

@ -1,316 +1,127 @@
---
date: "2019-02-12:00:00+02:00"
title: "Reverse Proxy"
draft: false
type: "doc"
menu:
sidebar:
parent: "setup"
---
# Setup behind a reverse proxy which also serves the frontend
These examples assume you have an instance of the backend running on your server listening on port `3456`.
If you've changed this setting, you need to update the server configurations accordingly.
{{< table_of_contents >}}
## NGINX
Below are two example configurations which you can put in your `nginx.conf`:
You may need to adjust `server_name` and `root` accordingly.
### with gzip enabled (recommended)
{{< highlight conf >}}
gzip on;
gzip_disable "msie6";
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_min_length 256;
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/vnd.ms-fontobject application/x-font-ttf font/opentype image/svg+xml;
server {
listen 80;
server_name localhost;
location / {
root /path/to/vikunja/static/frontend/files;
try_files $uri $uri/ /;
index index.html index.htm;
}
location ~* ^/(api|dav|\.well-known)/ {
proxy_pass http://localhost:3456;
client_max_body_size 20M;
}
}
{{< /highlight >}}
<div class="notification is-warning">
<b>NOTE:</b> If you change the max upload size in Vikunja's settings, you'll need to also change the <code>client_max_body_size</code> in the nginx proxy config.
</div>
### without gzip
{{< highlight conf >}}
server {
listen 80;
server_name localhost;
location / {
root /path/to/vikunja/static/frontend/files;
try_files $uri $uri/ /;
index index.html index.htm;
}
location ~* ^/(api|dav|\.well-known)/ {
proxy_pass http://localhost:3456;
client_max_body_size 20M;
}
}
{{< /highlight >}}
<div class="notification is-warning">
<b>NOTE:</b> If you change the max upload size in Vikunja's settings, you'll need to also change the <code>client_max_body_size</code> in the nginx proxy config.
</div>
## NGINX Proxy Manager (NPM)
### Method 1
Following the [Docker Walkthrough]({{< ref "docker-start-to-finish.md" >}}) guide, you should be able to get Vikunja to work via HTTP connection to your server IP.
From there, all you have to do is adjust the following things:
#### In `docker-compose.yml`
Under `api:`,
1. Change `VIKUNJA_SERVICE_FRONTENDURL:` to your desired domain with `https://` and `/`.
2. Expose your desired port on host under `ports:`.
example:
```yaml
api:
image: vikunja/api
environment:
VIKUNJA_DATABASE_HOST: db
VIKUNJA_DATABASE_PASSWORD: secret
VIKUNJA_DATABASE_TYPE: mysql
VIKUNJA_DATABASE_USER: vikunja
VIKUNJA_DATABASE_DATABASE: vikunja
VIKUNJA_SERVICE_JWTSECRET: <your-random-secret>
VIKUNJA_SERVICE_FRONTENDURL: https://vikunja.your-domain.com/ # change vikunja.your-domain.com to your desired domain/subdomain.
ports:
- 3456:3456 # Change 3456 on the left to the port of your choice.
volumes:
- ./files:/app/vikunja/files
depends_on:
- db
restart: unless-stopped
```
Under `frontend:`,
1. Add `VIKUNJA_API_URL:` under `environment:` and input your desired `API` domain with `https://` and `/api/v1/`. The `API` domain should be different from the one in `VIKUNJA_SERVICE_FRONTENDURL:`.
example:
```yaml
frontend:
image: vikunja/frontend
environment:
VIKUNJA_API_URL: https://api.your-domain.com/api/v1/ # change api.your-domain.com to your desired domain/subdomain, it should be different from your frontend domain
restart: unless-stopped
```
Under `proxy:`,
1. Since we'll be using Nginx Proxy Manager, it should by default uses the port `80` and thus you should change `ports:` to expose another port not occupied by any service.
example:
```yaml
proxy:
image: nginx
ports:
- 1078:80 # change the number infront (host port) to whatever you desire, but make sure it's not 80 which will be used by Nginx Proxy Manager
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
- api
- frontend
restart: unless-stopped
```
#### In your DNS provider
Add two `A` records that points to your server IP.
1. `vikunja` for accessing the frontend
2. `api` for accessing the api
You are of course free to change them to whatever domain/subdomain you desire and modify the `docker-compose.yml` accordingly but the two should be different.
(Tested on Cloudflare DNS. Settings are different for different DNS provider, in this case the end result should bei `vikunja.your-domain.com` and `api.your-domain.com` respectively.)
#### In Nginx Proxy Manager
Add two Proxy Host as you normally would, and you don't have to add anything extra in Advanced.
##### Frontend
Under `Details`:
```
Domain Names:
vikunja.your-domain.com
Scheme:
http
Forward Hostname/IP:
your-server-ip
Forward Port:
1078
Cached Assets:
Optional.
Block Common Exploits:
Toggled.
Websockets Support:
Toggled.
```
Under `SSL`:
```
SSL Certificate:
However you prefer.
Force SSL:
Toggled.
HTTP/2 Support:
Toggled.
HSTS Enabled:
Toggled.
HSTS Subdomains:
Toggled.
Use a DNS Challenge:
Not toggled.
Email Address for Let's Encrypt:
your-email@email.com
```
##### API
Under `Details`:
```
Domain Names:
api.your-domain.com
Scheme:
http
Forward Hostname/IP:
your-server-ip
Forward Port:
3456
Cached Assets:
Optional.
Block Common Exploits:
Toggled.
Websockets Support:
Toggled.
```
Under `SSL`:
```
SSL Certificate:
However you prefer.
Force SSL:
Toggled.
HTTP/2 Support:
Toggled.
HSTS Enabled:
Toggled.
HSTS Subdomains:
Toggled.
Use a DNS Challenge:
Not toggled.
Email Address for Let's Encrypt:
your-email@email.com
```
Your Vikunja service should now work and your HTTPS frontend should be able to reach the API after `docker-compose`.
### Method 2
1. Create a standard Proxy Host for the Vikunja Frontend within NPM and point it to the URL you plan to use. The next several steps will enable the Proxy Host to successfully navigate to the API (on port 3456).
2. Verify that the page will pull up in your browser. (Do not bother trying to log in. It won't work. Trust me.)
3. Now, we'll work with the NPM container, so you need to identify the container name for your NPM installation. e.g. NGINX-PM
4. From the command line, enter `sudo docker exec -it [NGINX-PM container name] /bin/bash` and navigate to the proxy hosts folder where the `.conf` files are stashed. Probably `/data/nginx/proxy_host`. (This folder is a persistent folder created in the NPM container and mounted by NPM.)
5. Locate the `.conf` file where the server_name inside the file matches your Vikunja Proxy Host. Once found, add the following code, unchanged, just above the existing location block in that file. (They are listed by number, not name.)
```nginx
location ~* ^/(api|dav|\.well-known)/ {
proxy_pass http://api:3456;
client_max_body_size 20M;
}
```
6. After saving the edited file, return to NPM's UI browser window and refresh the page to verify your Proxy Host for Vikunja is still online.
7. Now, switch over to your Vikunja browser window and hit refresh. If you configured your URL correctly in original Vikunja container, you should be all set and the browser will correctly show Vikunja. If not, you'll need to adjust the address in the top of the login subscreen to match your proxy address.
## Apache
Put the following config in `cat /etc/apache2/sites-available/vikunja.conf`:
{{< highlight aconf >}}
<VirtualHost *:80>
ServerName localhost
<Proxy *>
Order Deny,Allow
Allow from all
</Proxy>
ProxyPass /api http://localhost:3456/api
ProxyPassReverse /api http://localhost:3456/api
ProxyPass /dav http://localhost:3456/dav
ProxyPassReverse /dav http://localhost:3456/dav
ProxyPass /.well-known http://localhost:3456/.well-known
ProxyPassReverse /.well-known http://localhost:3456/.well-known
DocumentRoot /var/www/html
RewriteEngine On
RewriteRule ^\/?(favicon\.ico|assets|audio|fonts|images|manifest\.webmanifest|robots\.txt|sw\.js|workbox-.*|api|dav|\.well-known) - [L]
RewriteRule ^(.*)$ /index.html [QSA,L]
</VirtualHost>
{{< /highlight >}}
**Note:** The apache modules `proxy`, `proxy_http` and `rewrite` must be enabled for this.
For more details see the [frontend apache configuration]({{< ref "install-frontend.md#apache">}}).
## Caddy
{{< highlight conf >}}
vikunja.domainname.tld {
@paths {
path /api/* /.well-known/* /dav/*
}
handle @paths {
reverse_proxy 127.0.0.1:3456
}
handle {
encode zstd gzip
root * /var/www/html/vikunja
try_files {path} index.html
file_server
}
}
{{< /highlight >}}
---
date: "2019-02-12:00:00+02:00"
title: "Reverse Proxy"
draft: false
type: "doc"
menu:
sidebar:
parent: "setup"
---
# Setup behind a reverse proxy which also serves the frontend
These examples assume you have an instance of the backend running on your server listening on port `3456`.
If you've changed this setting, you need to update the server configurations accordingly.
{{< table_of_contents >}}
## NGINX
Below are two example configurations which you can put in your `nginx.conf`:
You may need to adjust `server_name` and `root` accordingly.
### with gzip enabled (recommended)
{{< highlight conf >}}
gzip on;
gzip_disable "msie6";
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_min_length 256;
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/vnd.ms-fontobject application/x-font-ttf font/opentype image/svg+xml;
server {
listen 80;
server_name localhost;
location / {
root /path/to/vikunja/static/frontend/files;
try_files $uri $uri/ /;
index index.html index.htm;
}
location ~* ^/(api|dav|\.well-known)/ {
proxy_pass http://localhost:3456;
client_max_body_size 20M;
}
}
{{< /highlight >}}
<div class="notification is-warning">
<b>NOTE:</b> If you change the max upload size in Vikunja's settings, you'll need to also change the <code>client_max_body_size</code> in the nginx proxy config.
</div>
### without gzip
{{< highlight conf >}}
server {
listen 80;
server_name localhost;
location / {
root /path/to/vikunja/static/frontend/files;
try_files $uri $uri/ /;
index index.html index.htm;
}
location ~* ^/(api|dav|\.well-known)/ {
proxy_pass http://localhost:3456;
client_max_body_size 20M;
}
}
{{< /highlight >}}
<div class="notification is-warning">
<b>NOTE:</b> If you change the max upload size in Vikunja's settings, you'll need to also change the <code>client_max_body_size</code> in the nginx proxy config.
</div>
## NGINX Proxy Manager (NPM)
1. Create a standard Proxy Host for the Vikunja Frontend within NPM and point it to the URL you plan to use. The next several steps will enable the Proxy Host to successfully navigate to the API (on port 3456).
2. Verify that the page will pull up in your browser. (Do not bother trying to log in. It won't work. Trust me.)
3. Now, we'll work with the NPM container, so you need to identify the container name for your NPM installation. e.g. NGINX-PM
4. From the command line, enter `sudo docker exec -it [NGINX-PM container name] /bin/bash` and navigate to the proxy hosts folder where the `.conf` files are stashed. Probably `/data/nginx/proxy_host`. (This folder is a persistent folder created in the NPM container and mounted by NPM.)
5. Locate the `.conf` file where the server_name inside the file matches your Vikunja Proxy Host. Once found, add the following code, unchanged, just above the existing location block in that file. (They are listed by number, not name.)
```nginx
location ~* ^/(api|dav|\.well-known)/ {
proxy_pass http://api:3456;
client_max_body_size 20M;
}
```
6. After saving the edited file, return to NPM's UI browser window and refresh the page to verify your Proxy Host for Vikunja is still online.
7. Now, switch over to your Vikunja browser window and hit refresh. If you configured your URL correctly in original Vikunja container, you should be all set and the browser will correctly show Vikunja. If not, you'll need to adjust the address in the top of the login subscreen to match your proxy address.
## Apache
Put the following config in `cat /etc/apache2/sites-available/vikunja.conf`:
{{< highlight aconf >}}
<VirtualHost *:80>
ServerName localhost
<Proxy *>
Order Deny,Allow
Allow from all
</Proxy>
ProxyPass /api http://localhost:3456/api
ProxyPassReverse /api http://localhost:3456/api
ProxyPass /dav http://localhost:3456/dav
ProxyPassReverse /dav http://localhost:3456/dav
ProxyPass /.well-known http://localhost:3456/.well-known
ProxyPassReverse /.well-known http://localhost:3456/.well-known
DocumentRoot /var/www/html
RewriteEngine On
RewriteRule ^\/?(favicon\.ico|assets|audio|fonts|images|manifest\.webmanifest|robots\.txt|sw\.js|workbox-.*|api|dav|\.well-known) - [L]
RewriteRule ^(.*)$ /index.html [QSA,L]
</VirtualHost>
{{< /highlight >}}
**Note:** The apache modules `proxy`, `proxy_http` and `rewrite` must be enabled for this.
For more details see the [frontend apache configuration]({{< ref "install-frontend.md#apache">}}).

View File

@ -1,23 +0,0 @@
---
title: "Typesense"
date: 2023-09-29T12:23:55+02:00
draft: false
menu:
sidebar:
parent: "setup"
---
# Use Typesense for enhanced search capabilities
Vikunja supports using [Typesense](https://typesense.org/) for a better search experience.
Typesense allows fast fulltext search including fuzzy matching support.
It may return different results than what you'd get with a database-only search, but generally, the results are more relevant to what you're looking for.
This document explains how to set up and use Typesense with Vikunja.
## Setup
1. First, install Typesense on your system. Refer to [their documentation](https://typesense.org/docs/guide/install-typesense.html) for specific instructions.
2. Once Typesense is available on your system and reachable by Vikunja, add the relevant configuration keys to your Vikunja config. [Check out the docs article about this]({{< ref "config.md#typesense">}}).
3. Index all tasks currently in Vikunja. To do that, run the `vikunja index` command with the api binary. This may take a while, depending on the size of your instance.
4. Restart the api. From now on, all task changes will be automatically indexed in Typesense.

View File

@ -26,8 +26,8 @@ Urls are:
* `/principals/<username>/`: Returns urls for project discovery. *Use this url to initially make connections to new clients.*
* `/projects/`: Used to manage projects
* `/projects/<Project ID>/`: Used to manage a single project
* `/projects/<Project ID>/<Task UID>`: Used to manage a task on a project
* `/projects/<List ID>/`: Used to manage a single project
* `/projects/<List ID>/<Task UID>`: Used to manage a task on a project
## Supported properties

View File

@ -54,19 +54,16 @@ This document describes the different errors Vikunja can return.
## Project
| ErrorCode | HTTP Status Code | Description |
|-----------|------------------|-------------------------------------------------------------------------------------------------------------------------------------|
| 3001 | 404 | The project does not exist. |
| 3004 | 403 | The user needs to have read permissions on that project to perform that action. |
| 3005 | 400 | The project title cannot be empty. |
| 3006 | 404 | The project share does not exist. |
| 3007 | 400 | A project with this identifier already exists. |
| ErrorCode | HTTP Status Code | Description |
|-----------|------------------|-------------------------------------------------------------------------------------------------------------------------------|
| 3001 | 404 | The project does not exist. |
| 3004 | 403 | The user needs to have read permissions on that project to perform that action. |
| 3005 | 400 | The project title cannot be empty. |
| 3006 | 404 | The project share does not exist. |
| 3007 | 400 | A project with this identifier already exists. |
| 3008 | 412 | The project is archived and can therefore only be accessed read only. This is also true for all tasks associated with this project. |
| 3009 | 412 | The project cannot belong to a dynamically generated parent project like "Favorites". |
| 3010 | 412 | This project cannot be a child of itself. |
| 3011 | 412 | This project cannot have a cyclic relationship to a parent project. |
| 3012 | 412 | This project cannot be deleted because a user has set it as their default project. |
| 3013 | 412 | This project cannot be archived because a user has set it as their default project. |
| 3009 | 412 | The project cannot belong to a dynamically generated namespace like "Favorites". |
| 3010 | 412 | The project must belong to a namespace. |
## Task
@ -95,15 +92,27 @@ This document describes the different errors Vikunja can return.
| 4021 | 400 | This user is already assigned to that task. |
| 4022 | 400 | The task has a relative reminder which does not specify relative to what. |
## Namespace
| ErrorCode | HTTP Status Code | Description |
|-----------|------------------|-------------|
| 5001 | 404 | The namespace does not exist. |
| 5003 | 403 | The user does not have access to the specified namespace. |
| 5006 | 400 | The namespace name cannot be empty. |
| 5009 | 403 | The user needs to have namespace read access to perform that action. |
| 5010 | 403 | This team does not have access to that namespace. |
| 5011 | 409 | This user has already access to that namespace. |
| 5012 | 412 | The namespace is archived and can therefore only be accessed read only. |
## Team
| ErrorCode | HTTP Status Code | Description |
|-----------|------------------|----------------------------------------------------------------------|
| 6001 | 400 | The team name cannot be empty. |
| 6002 | 404 | The team does not exist. |
| 6004 | 409 | The team already has access to that project. |
| 6005 | 409 | The user is already a member of that team. |
| 6006 | 400 | Cannot delete the last team member. |
| ErrorCode | HTTP Status Code | Description |
|-----------|------------------|-------------|
| 6001 | 400 | The team name cannot be empty. |
| 6002 | 404 | The team does not exist. |
| 6004 | 409 | The team already has access to that namespace or project. |
| 6005 | 409 | The user is already a member of that team. |
| 6006 | 400 | Cannot delete the last team member. |
| 6007 | 403 | The team does not have access to the project to perform that action. |
## User Project Access

View File

@ -1,43 +0,0 @@
---
title: "n8n"
date: 2023-10-24T19:31:35+02:00
draft: false
menu:
sidebar:
parent: "usage"
---
# Using Vikunja with n8n
Vikunja maintains a [community node](https://github.com/go-vikunja/n8n-vikunja-nodes) for [n8n](https://n8n.io),
allowing you to easily integrate Vikunja with all kinds of other tools and services.
{{< table_of_contents >}}
## Installation
To install the node in your n8n installation:
1. In your n8n instance, go to **Settings > Community Nodes**.
2. Select Install.
3. Enter `n8n-nodes-vikunja` as the npm Package Name
4. Agree to the risks of using community nodes: select I understand the risks of installing unverified code from a
public source.
5. Select Install. n8n installs the node, and returns to the Community Nodes list in Settings.
6. Vikunja actions and triggers are now available in n8n.
[Official n8n docs about the installation](https://docs.n8n.io/integrations/community-nodes/installation/)
## Authentication
To authenticate your automation against Vikunja:
1. In Vikunja, go to **Settings > API Tokens** and create a new token. Use all scopes for the kind of task you want to
do. \
*Note:* If you want to use the webhook trigger node, the api token should have permissions to create, read and delete
webhooks.
2. Now in n8n, go to **Credentials** and then click on **Add Credential**.
3. Search for `Vikunja API` and click *Continue*
4. Enter the API key you created in step 1.
5. Enter the API URL of your Vikunja instance, with `/api/v1` suffix.
6. When you now create a Vikunja node, select the created credentials.

View File

@ -8,20 +8,20 @@ menu:
parent: "usage"
---
# Project rights for teams and users
# Project and namespace rights for teams and users
Whenever you share a project with a user or team, you can specify a `rights` parameter.
Whenever you share a project or namespace with a user or team, you can specify a `rights` parameter.
This parameter controls the rights that team or user is going to have (or has, if you request the current sharing status).
Rights are being specified using integers.
The following values are possible:
| Right (int) | Meaning |
|-------------|-------------------------------------------------------------------------------------------------|
| 0 (Default) | Read only. Anything which is shared with this right cannot be edited. |
| 1 | Read and write. Projects shared with this right can be read and written to by the team or user. |
| 2 | Admin. Can do anything like read and write, but can additionally manage sharing options. |
| Right (int) | Meaning |
|-------------|---------------------------------------------------------------------------------------------------------------|
| 0 (Default) | Read only. Anything which is shared with this right cannot be edited. |
| 1 | Read and write. Namespaces or projects shared with this right can be read and written to by the team or user. |
| 2 | Admin. Can do anything like read and write, but can additionally manage sharing options. |
## Team admins

View File

@ -1,58 +0,0 @@
---
title: "Webhooks"
date: 2023-10-17T19:51:32+02:00
draft: false
type: doc
menu:
sidebar:
parent: "usage"
---
# Webhooks
Starting with version 0.22.0, Vikunja allows you to define webhooks to notify other services of events happening within Vikunja.
{{< table_of_contents >}}
## How to create webhooks
To create a webhook, in the project options select "Webhooks". The form will allow you to create and modify webhooks.
Check out [the api docs](https://try.vikunja.io/api/v1/docs#tag/webhooks) for information about how to create webhooks programatically.
## Available events and their payload
All events registered as webhook events in [the event listeners definition](https://kolaente.dev/vikunja/api/src/branch/main/pkg/models/listeners.go#L69) can be used as webhook target.
A webhook payload will look similar to this:
```json
{
"event_name": "task.created",
"time": "2023-10-17T19:39:32.924194436+02:00",
"data": {}
}
```
The `data` property will contain the raw event data as it was registered in the `listeners.go` file.
The `time` property holds the time when the webhook payload data was sent.
It always uses the ISO 8601 format with date, time and time zone offset.
## Security considerations
### Signing
Vikunja allows you to provide a secret when creating the webhook.
If you set a secret, all outgoing webhook requests will contain an `X-Vikunja-Signature` header with an HMAC signature over the webhook json payload.
Check out [webhooks.fyi](https://webhooks.fyi/security/hmac) for more information about how to validate the HMAC signature.
### Hosting webhook infrastructure
Vikunja has support to use [mole](https://github.com/frain-dev/mole) as a proxy for outgoing webhook requests.
This allows you to prevent SSRF attacts on your own infrastructure.
You should use this and [configure it appropriately]({{< ref "../setup/config.md">}}#webhooks) if you're not the only one using your Vikunja instance.
Check out [webhooks.fyi](https://webhooks.fyi/best-practices/webhook-providers#implement-security-on-egress-communication) for more information about the attack vector and reasoning to prevent this.

162
go.mod
View File

@ -18,103 +18,90 @@ module code.vikunja.io/api
require (
code.vikunja.io/web v0.0.0-20210706160506-d85def955bd3
dario.cat/mergo v1.0.0
github.com/ThreeDotsLabs/watermill v1.3.5
gitea.com/xorm/xorm-redis-cache v0.2.0
github.com/ThreeDotsLabs/watermill v1.2.0
github.com/adlio/trello v1.10.0
github.com/arran4/golang-ical v0.1.0
github.com/arran4/golang-ical v0.0.0-20230318005454-19abf92700cc
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef
github.com/bbrks/go-blurhash v1.1.1
github.com/c2h5oh/datasize v0.0.0-20220606134207-859f65c6625b
github.com/coreos/go-oidc/v3 v3.7.0
github.com/coreos/go-oidc/v3 v3.5.0
github.com/cweill/gotests v1.6.0
github.com/d4l3k/messagediff v1.2.1
github.com/disintegration/imaging v1.6.2
github.com/dustinkirkland/golang-petname v0.0.0-20231002161417-6a283f1aaaf2
github.com/gabriel-vasile/mimetype v1.4.3
github.com/getsentry/sentry-go v0.25.0
github.com/go-sql-driver/mysql v1.7.1
github.com/go-testfixtures/testfixtures/v3 v3.9.0
github.com/gocarina/gocsv v0.0.0-20230616125104-99d496ca653d
github.com/dustinkirkland/golang-petname v0.0.0-20191129215211-8e5a1ed0cff0
github.com/gabriel-vasile/mimetype v1.4.2
github.com/getsentry/sentry-go v0.20.0
github.com/go-sql-driver/mysql v1.7.0
github.com/go-testfixtures/testfixtures/v3 v3.8.1
github.com/gocarina/gocsv v0.0.0-20230406101422-6445c2b15027
github.com/golang-jwt/jwt/v5 v5.0.0
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
github.com/google/uuid v1.4.0
github.com/hashicorp/go-version v1.6.0
github.com/iancoleman/strcase v0.3.0
github.com/jinzhu/copier v0.4.0
github.com/jszwedko/go-datemath v0.1.1-0.20230526204004-640a500621d6
github.com/labstack/echo-jwt/v4 v4.2.0
github.com/labstack/echo/v4 v4.11.2
github.com/google/uuid v1.3.0
github.com/iancoleman/strcase v0.2.0
github.com/imdario/mergo v0.3.15
github.com/jinzhu/copier v0.3.5
github.com/labstack/echo-jwt/v4 v4.1.0
github.com/labstack/echo/v4 v4.10.2
github.com/labstack/gommon v0.4.0
github.com/lib/pq v1.10.9
github.com/magefile/mage v1.15.0
github.com/mattn/go-sqlite3 v1.14.17
github.com/lib/pq v1.10.8
github.com/magefile/mage v1.14.0
github.com/mattn/go-sqlite3 v1.14.16
github.com/olekukonko/tablewriter v0.0.5
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
github.com/pquerna/otp v1.4.0
github.com/prometheus/client_golang v1.17.0
github.com/redis/go-redis/v9 v9.2.1
github.com/prometheus/client_golang v1.15.0
github.com/redis/go-redis/v9 v9.0.3
github.com/robfig/cron/v3 v3.0.1
github.com/samedi/caldav-go v3.0.0+incompatible
github.com/spf13/afero v1.10.0
github.com/spf13/afero v1.9.5
github.com/spf13/cobra v1.7.0
github.com/spf13/viper v1.17.0
github.com/stretchr/testify v1.8.4
github.com/swaggo/swag v1.16.2
github.com/spf13/viper v1.15.0
github.com/stretchr/testify v1.8.2
github.com/swaggo/swag v1.8.12
github.com/tkuchiki/go-timezone v0.2.2
github.com/typesense/typesense-go v0.8.0
github.com/ulule/limiter/v3 v3.11.2
github.com/wneessen/go-mail v0.4.0
github.com/yuin/goldmark v1.5.6
golang.org/x/crypto v0.14.0
golang.org/x/image v0.13.0
golang.org/x/oauth2 v0.13.0
golang.org/x/sync v0.4.0
golang.org/x/sys v0.13.0
golang.org/x/term v0.13.0
github.com/ulule/limiter/v3 v3.11.1
github.com/vectordotdev/go-datemath v0.1.1-0.20220323213446-f3954d0b18ae
github.com/wneessen/go-mail v0.3.9
github.com/yuin/goldmark v1.5.4
golang.org/x/crypto v0.8.0
golang.org/x/image v0.7.0
golang.org/x/oauth2 v0.7.0
golang.org/x/sync v0.1.0
golang.org/x/sys v0.7.0
golang.org/x/term v0.7.0
gopkg.in/d4l3k/messagediff.v1 v1.2.1
gopkg.in/yaml.v3 v3.0.1
src.techknowlogick.com/xgo v1.7.1-0.20231019133136-ecfba3dfed5d
src.techknowlogick.com/xormigrate v1.7.0
xorm.io/builder v0.3.13
xorm.io/xorm v1.3.4
src.techknowlogick.com/xgo v1.7.1-0.20230404174715-bff48e481f81
src.techknowlogick.com/xormigrate v1.5.0
xorm.io/builder v0.3.12
xorm.io/xorm v1.3.2
)
require (
github.com/ClickHouse/ch-go v0.55.0 // indirect
github.com/ClickHouse/clickhouse-go/v2 v2.9.1 // indirect
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/andybalholm/brotli v1.0.5 // indirect
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
github.com/beevik/etree v1.1.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
github.com/bytedance/sonic v1.10.0 // indirect
github.com/cenkalti/backoff/v3 v3.2.2 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
github.com/chenzhuoyu/iasm v0.9.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/deepmap/oapi-codegen v1.13.4 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/gin-gonic/gin v1.9.1 // indirect
github.com/go-chi/chi/v5 v5.0.10 // indirect
github.com/go-faster/city v1.0.1 // indirect
github.com/go-faster/errors v0.6.1 // indirect
github.com/garyburd/redigo v1.6.0 // indirect
github.com/ghodss/yaml v1.0.0 // indirect
github.com/go-chi/chi/v5 v5.0.8 // indirect
github.com/go-jose/go-jose/v3 v3.0.0 // indirect
github.com/go-openapi/jsonpointer v0.20.0 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.19.6 // indirect
github.com/go-openapi/spec v0.20.4 // indirect
github.com/go-openapi/swag v0.22.4 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.15.1 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/go-openapi/swag v0.19.15 // indirect
github.com/goccy/go-json v0.9.11 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/golang-jwt/jwt/v4 v4.4.3 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
@ -123,16 +110,13 @@ require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.17.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
github.com/laurent22/ical-go v0.1.1-0.20181107184520-7e5d6ade8eef // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/lithammer/shortuuid/v3 v3.0.7 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
@ -140,51 +124,33 @@ require (
github.com/oklog/ulid v1.3.1 // indirect
github.com/onsi/ginkgo v1.16.4 // indirect
github.com/onsi/gomega v1.16.0 // indirect
github.com/paulmach/orb v0.9.0 // indirect
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
github.com/pierrec/lz4/v4 v4.1.17 // indirect
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect
github.com/prometheus/common v0.44.0 // indirect
github.com/prometheus/procfs v0.11.1 // indirect
github.com/rivo/uniseg v0.4.4 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.42.0 // indirect
github.com/prometheus/procfs v0.9.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sagikazarmark/locafero v0.3.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/segmentio/asm v1.2.0 // indirect
github.com/shopspring/decimal v1.3.1 // indirect
github.com/sony/gobreaker v0.5.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/cast v1.5.1 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/subosito/gotenv v1.4.2 // indirect
github.com/syndtr/goleveldb v1.0.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
github.com/urfave/cli/v2 v2.3.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/yosssi/gohtml v0.0.0-20201013000340-ee4748c638f4 // indirect
go.opentelemetry.io/otel v1.15.0 // indirect
go.opentelemetry.io/otel/trace v1.15.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/arch v0.4.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/mod v0.12.0 // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/text v0.13.0 // indirect
golang.org/x/mod v0.9.0 // indirect
golang.org/x/net v0.9.0 // indirect
golang.org/x/text v0.9.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.13.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/protobuf v1.31.0 // indirect
golang.org/x/tools v0.7.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
)
replace github.com/samedi/caldav-go => github.com/kolaente/caldav-go v3.0.1-0.20190610114120-2a4eb8b5dcc9+incompatible // Branch: feature/dynamic-supported-components, PR: https://github.com/samedi/caldav-go/pull/6 and https://github.com/samedi/caldav-go/pull/7
go 1.21
toolchain go1.21.2
go 1.20

813
go.sum

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@ -177,6 +177,9 @@ func init() {
// Some variables have external dependencies (like git) which may not always be available.
func initVars() {
Tags = os.Getenv("TAGS")
if !strings.Contains(Tags, "swagger") {
Tags += " swagger"
}
setVersion()
setBinLocation()
setPkgVersion()
@ -346,6 +349,10 @@ const swaggerDocsFolderLocation = `./pkg/swagger/`
// Generates the swagger docs from the code annotations
func DoTheSwag() {
mg.Deps(initVars)
if _, err := os.Stat(swaggerDocsFolderLocation + "swagger.json"); err == nil {
fmt.Println("Swagger docs already generated, not generating. Remove the files in " + swaggerDocsFolderLocation + " and run this command again to regenerate them.")
return
}
checkAndInstallGoTool("swag", "github.com/swaggo/swag/cmd/swag")
runAndStreamOutput("swag", "init", "-g", "./pkg/routes/routes.go", "--parseDependency", "-d", RootPath, "-o", RootPath+"/pkg/swagger")
@ -412,7 +419,7 @@ func checkGolangCiLintInstalled() {
mg.Deps(initVars)
if err := exec.Command("golangci-lint").Run(); err != nil && strings.Contains(err.Error(), "executable file not found") {
fmt.Println("Please manually install golangci-lint by running")
fmt.Println("curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.54.2")
fmt.Println("curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.52.1")
os.Exit(1)
}
}
@ -453,12 +460,16 @@ func (Build) Clean() error {
if err := os.RemoveAll(BinLocation); err != nil && !os.IsNotExist(err) {
return err
}
if err := os.RemoveAll(swaggerDocsFolderLocation); err != nil && !os.IsNotExist(err) {
return err
}
return nil
}
// Builds a vikunja binary, ready to run
func (Build) Build() {
mg.Deps(initVars)
mg.Deps(DoTheSwag)
runAndStreamOutput("go", "build", Goflags[0], "-tags", Tags, "-ldflags", "-s -w "+Ldflags, "-o", Executable)
}
@ -468,6 +479,7 @@ type Release mg.Namespace
func (Release) Release(ctx context.Context) error {
mg.Deps(initVars)
mg.Deps(Release.Dirs)
mg.Deps(DoTheSwag)
// Run compiling in parallel to speed it up
errs, _ := errgroup.WithContext(ctx)
@ -509,6 +521,7 @@ func (Release) Dirs() error {
func runXgo(targets string) error {
mg.Deps(initVars)
mg.Deps(DoTheSwag)
checkAndInstallGoTool("xgo", "src.techknowlogick.com/xgo")
extraLdflags := `-linkmode external -extldflags "-static" `
@ -757,7 +770,7 @@ func (Dev) MakeMigration() error {
date := time.Now().Format("20060102150405")
migration := `// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by

View File

@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by

View File

@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by

View File

@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by

View File

@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@ -23,7 +23,6 @@ import (
"time"
"code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/utils"
@ -91,12 +90,8 @@ func ParseTaskFromVTODO(content string) (vTask *models.Task, err error) {
}
// We put the vTodo details in a map to be able to handle them more easily
task := make(map[string]ics.IANAProperty)
var relation ics.IANAProperty
for _, c := range vTodo.UnknownPropertiesIANAProperties() {
task[c.IANAToken] = c
if strings.HasPrefix(c.IANAToken, "RELATED-TO") {
relation = c
}
}
// Parse the priority
@ -139,19 +134,6 @@ func ParseTaskFromVTODO(content string) (vTask *models.Task, err error) {
DoneAt: caldavTimeToTimestamp(task["COMPLETED"]),
}
if relation.Value != "" {
s := db.NewSession()
defer s.Close()
subtask, err := models.GetTaskSimpleByUUID(s, relation.Value)
if err != nil {
return nil, err
}
vTask.RelatedTasks = make(map[models.RelationKind][]*models.Task)
vTask.RelatedTasks[models.RelationKindSubtask] = []*models.Task{subtask}
}
if task["STATUS"].Value == "COMPLETED" {
vTask.Done = true
}

View File

@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by

View File

@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by

View File

@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by

View File

@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by

View File

@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@ -33,7 +33,7 @@ var dumpCmd = &cobra.Command{
Use: "dump",
Short: "Dump all vikunja data into a zip file. Includes config, files and db.",
PreRun: func(cmd *cobra.Command, args []string) {
initialize.FullInitWithoutAsync()
initialize.FullInit()
},
Run: func(cmd *cobra.Command, args []string) {
filename := "vikunja-dump_" + time.Now().Format("2006-01-02_15-03-05") + ".zip"

View File

@ -1,58 +0,0 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public Licensee for more details.
//
// You should have received a copy of the GNU Affero General Public Licensee
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package cmd
import (
"code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/initialize"
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/models"
"github.com/spf13/cobra"
)
func init() {
rootCmd.AddCommand(indexCmd)
}
var indexCmd = &cobra.Command{
Use: "index",
Short: "Reindex all of Vikunja's data into Typesense. This will remove any existing index.",
PreRun: func(cmd *cobra.Command, args []string) {
initialize.FullInitWithoutAsync()
},
Run: func(cmd *cobra.Command, args []string) {
if !config.TypesenseEnabled.GetBool() {
log.Error("Typesense not enabled")
return
}
log.Infof("Indexing… This may take a while.")
err := models.CreateTypesenseCollections()
if err != nil {
log.Criticalf("Could not create Typesense collections: %s", err.Error())
return
}
err = models.ReindexAllTasks()
if err != nil {
log.Criticalf("Could not reindex all tasks into Typesense: %s", err.Error())
return
}
log.Infof("Done!")
},
}

View File

@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by

View File

@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@ -32,7 +32,7 @@ var restoreCmd = &cobra.Command{
Short: "Restores all vikunja data from a vikunja dump.",
Args: cobra.ExactArgs(1),
PreRun: func(cmd *cobra.Command, args []string) {
initialize.FullInitWithoutAsync()
initialize.FullInit()
},
Run: func(cmd *cobra.Command, args []string) {
if err := dump.Restore(args[0]); err != nil {

View File

@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by

View File

@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@ -46,7 +46,6 @@ var (
userFlagEnableUser bool
userFlagDisableUser bool
userFlagDeleteNow bool
userFlagDeleteConfirm bool
)
func init() {
@ -74,10 +73,7 @@ func init() {
// User deletion flags
userDeleteCmd.Flags().BoolVarP(&userFlagDeleteNow, "now", "n", false, "If provided, deletes the user immediately instead of sending them an email first.")
// Bypass confirm prompt
userDeleteCmd.Flags().BoolVarP(&userFlagDeleteConfirm, "confirm", "c", false, "Bypasses any prompts confirming the deletion request, use with caution!")
userCmd.AddCommand(userListCmd, userCreateCmd, userUpdateCmd, userResetPasswordCmd, userChangeEnabledCmd, userDeleteCmd)
userCmd.AddCommand(userProjectCmd, userCreateCmd, userUpdateCmd, userResetPasswordCmd, userChangeEnabledCmd, userDeleteCmd)
rootCmd.AddCommand(userCmd)
}
@ -104,16 +100,12 @@ func getPasswordFromFlagOrInput() (pw string) {
}
func getUserFromArg(s *xorm.Session, arg string) *user.User {
filter := user.User{}
id, err := strconv.ParseInt(arg, 10, 64)
if err != nil {
log.Infof("Invalid user ID [%s], assuming username instead", arg)
filter.Username = arg
} else {
filter.ID = id
log.Fatalf("Invalid user id: %s", err)
}
u, err := user.GetUserWithEmail(s, &filter)
u, err := user.GetUserWithEmail(s, &user.User{ID: id})
if err != nil {
log.Fatalf("Could not get user: %s", err)
}
@ -125,9 +117,9 @@ var userCmd = &cobra.Command{
Short: "Manage users locally through the cli.",
}
var userListCmd = &cobra.Command{
Use: "list",
Short: "Shows a list of all users.",
var userProjectCmd = &cobra.Command{
Use: "project",
Short: "Shows a project of all users.",
PreRun: func(cmd *cobra.Command, args []string) {
initialize.FullInit()
},
@ -135,7 +127,7 @@ var userListCmd = &cobra.Command{
s := db.NewSession()
defer s.Close()
users, err := user.ListAllUsers(s)
users, err := user.ProjectAllUsers(s)
if err != nil {
_ = s.Rollback()
log.Fatalf("Error getting users: %s", err)
@ -196,10 +188,10 @@ var userCreateCmd = &cobra.Command{
log.Fatalf("Error creating new user: %s", err)
}
err = models.CreateNewProjectForUser(s, newUser)
err = models.CreateNewNamespaceForUser(s, newUser)
if err != nil {
_ = s.Rollback()
log.Fatalf("Error creating new project for user: %s", err)
log.Fatalf("Error creating new namespace for user: %s", err)
}
if err := s.Commit(); err != nil {
@ -330,7 +322,7 @@ var userDeleteCmd = &cobra.Command{
initialize.FullInit()
},
Run: func(cmd *cobra.Command, args []string) {
if userFlagDeleteNow && !userFlagDeleteConfirm {
if userFlagDeleteNow {
fmt.Println("You requested to delete the user immediately. Are you sure?")
fmt.Println(`To confirm, please type "yes, I confirm" in all uppercase:`)

View File

@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by

View File

@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by

View File

@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@ -19,6 +19,7 @@ package config
import (
"crypto/rand"
"fmt"
"log"
"os"
"os/exec"
"path"
@ -28,7 +29,6 @@ import (
"time"
_ "time/tzdata" // Imports time zone data instead of relying on the os
"code.vikunja.io/api/pkg/log"
"github.com/spf13/viper"
)
@ -49,7 +49,6 @@ const (
ServiceRootpath Key = `service.rootpath`
ServiceStaticpath Key = `service.staticpath`
ServiceMaxItemsPerPage Key = `service.maxitemsperpage`
ServiceDemoMode Key = `service.demomode`
// Deprecated: Use metrics.enabled
ServiceEnableMetrics Key = `service.enablemetrics`
ServiceMotd Key = `service.motd`
@ -88,9 +87,9 @@ const (
DatabaseSslRootCert Key = `database.sslrootcert`
DatabaseTLS Key = `database.tls`
TypesenseEnabled Key = `typesense.enabled`
TypesenseURL Key = `typesense.url`
TypesenseAPIKey Key = `typesense.apikey`
CacheEnabled Key = `cache.enabled`
CacheType Key = `cache.type`
CacheMaxElementSize Key = `cache.maxelementsize`
MailerEnabled Key = `mailer.enabled`
MailerHost Key = `mailer.host`
@ -172,11 +171,6 @@ const (
DefaultSettingsLanguage Key = `defaultsettings.language`
DefaultSettingsTimezone Key = `defaultsettings.timezone`
DefaultSettingsOverdueTaskRemindersTime Key = `defaultsettings.overdue_tasks_reminders_time`
WebhooksEnabled Key = `webhooks.enabled`
WebhooksTimeoutSeconds Key = `webhooks.timeoutseconds`
WebhooksProxyURL Key = `webhooks.proxyurl`
WebhooksProxyPassword Key = `webhooks.proxypassword`
)
// GetString returns a string config value
@ -306,7 +300,6 @@ func InitDefaultConfig() {
ServiceEnableEmailReminders.setDefault(true)
ServiceEnableUserDeletion.setDefault(true)
ServiceMaxAvatarSize.setDefault(1024)
ServiceDemoMode.setDefault(false)
// Auth
AuthLocalEnabled.setDefault(true)
@ -328,9 +321,10 @@ func InitDefaultConfig() {
DatabaseSslRootCert.setDefault("")
DatabaseTLS.setDefault("false")
// Typesense
TypesenseEnabled.setDefault(false)
// Cacher
CacheEnabled.setDefault(false)
CacheType.setDefault("memory")
CacheMaxElementSize.setDefault(1000)
// Mailer
MailerEnabled.setDefault(false)
MailerHost.setDefault("")
@ -392,9 +386,6 @@ func InitDefaultConfig() {
DefaultSettingsAvatarProvider.setDefault("initials")
DefaultSettingsOverdueTaskRemindersEnabled.setDefault(true)
DefaultSettingsOverdueTaskRemindersTime.setDefault("9:00")
// Webhook
WebhooksEnabled.setDefault(true)
WebhooksTimeoutSeconds.setDefault(30)
}
// InitConfig initializes the config, sets defaults etc.
@ -408,17 +399,13 @@ func InitConfig() {
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
viper.AutomaticEnv()
// Just load environment variables
_ = viper.ReadInConfig()
log.ConfigLogger(LogEnabled.GetBool(), LogStandard.GetString(), LogPath.GetString(), LogLevel.GetString())
// Load the config file
viper.AddConfigPath(ServiceRootpath.GetString())
viper.AddConfigPath("/etc/vikunja/")
homeDir, err := os.UserHomeDir()
if err != nil {
log.Debugf("No home directory found, not using config from ~/.config/vikunja/. Error was: %s\n", err.Error())
log.Printf("No home directory found, not using config from ~/.config/vikunja/. Error was: %s\n", err.Error())
} else {
viper.AddConfigPath(path.Join(homeDir, ".config", "vikunja"))
}
@ -427,18 +414,19 @@ func InitConfig() {
viper.SetConfigName("config")
err = viper.ReadInConfig()
if viper.ConfigFileUsed() != "" {
log.Infof("Using config file: %s", viper.ConfigFileUsed())
log.Printf("Using config file: %s", viper.ConfigFileUsed())
if err != nil {
log.Warning(err.Error())
log.Warning("Using default config.")
} else {
log.ConfigLogger(LogEnabled.GetBool(), LogStandard.GetString(), LogPath.GetString(), LogLevel.GetString())
log.Println(err.Error())
log.Println("Using default config.")
}
} else {
log.Info("No config file found, using default or config from environment variables.")
log.Println("No config file found, using default or config from environment variables.")
}
if CacheType.GetString() == "keyvalue" {
CacheType.Set(KeyvalueType.GetString())
}
if RateLimitStore.GetString() == "keyvalue" {
@ -470,7 +458,7 @@ func InitConfig() {
}
if ServiceEnableMetrics.GetBool() {
log.Warning("service.enablemetrics is deprecated and will be removed in a future release. Please use metrics.enable.")
log.Println("WARNING: service.enablemetrics is deprecated and will be removed in a future release. Please use metrics.enable.")
MetricsEnabled.Set(true)
}
}

View File

@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by

View File

@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@ -17,6 +17,7 @@
package db
import (
"encoding/gob"
"fmt"
"net/url"
"os"
@ -26,7 +27,9 @@ import (
"code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/log"
xrc "gitea.com/xorm/xorm-redis-cache"
"xorm.io/xorm"
"xorm.io/xorm/caches"
"xorm.io/xorm/names"
"xorm.io/xorm/schemas"
@ -79,13 +82,33 @@ func CreateDBEngine() (engine *xorm.Engine, err error) {
}
engine.SetTZDatabase(loc)
engine.SetMapper(names.GonicMapper{})
logger := log.NewXormLogger(config.LogEnabled.GetBool(), config.LogDatabase.GetString(), config.LogDatabaseLevel.GetString())
logger := log.NewXormLogger("")
engine.SetLogger(logger)
// Cache
// We have to initialize the cache here to avoid import cycles
if config.CacheEnabled.GetBool() {
switch config.CacheType.GetString() {
case "memory":
cacher := caches.NewLRUCacher(caches.NewMemoryStore(), config.CacheMaxElementSize.GetInt())
engine.SetDefaultCacher(cacher)
case "redis":
cacher := xrc.NewRedisCacher(config.RedisHost.GetString(), config.RedisPassword.GetString(), xrc.DEFAULT_EXPIRATION, engine.Logger())
engine.SetDefaultCacher(cacher)
default:
log.Info("Did not find a valid cache type. Caching disabled. Please refer to the docs for poosible cache types.")
}
}
x = engine
return
}
// RegisterTableStructsForCache registers tables in gob encoding for redis cache
func RegisterTableStructsForCache(val interface{}) {
gob.Register(val)
}
func initMysqlEngine() (engine *xorm.Engine, err error) {
// We're using utf8mb here instead of just utf8 because we want to use non-BMP characters.
// See https://stackoverflow.com/a/30074553/10924593 for more info.

View File

@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by

View File

@ -1,30 +0,0 @@
- id: 1
title: 'test token 1'
token_salt: iC1Qbpf7H1
token_hash: a1813a558185d99f5197d2d549e4dd91292376aa00210229d70f77b57e165f6613fd12c1f790aa6493548cb9bceff33b45b4
token_last_eight: 75f29d2e
permissions: '{"tasks":["read_all","update"]}'
expires_at: 2099-01-01 00:00:00
owner_id: 1
created: 2023-09-01 07:00:00
# token in plaintext is tk_2eef46f40ebab3304919ab2e7e39993f75f29d2e
- id: 2
title: 'test token 2'
token_salt: EtwMsqDfOA
token_hash: 5c4d80c58947f21295064d473937709f1159ab09085eb59e38783da6032181069ec2e1d236486533b66999f9f4ac375b45f5
token_last_eight: 235008c8
permissions: '{"tasks":["read_all","update"]}'
expires_at: 2023-01-01 00:00:00
owner_id: 1
created: 2023-09-01 07:00:00
# token in plaintext is tk_a5e6f92ddbad68f49ee2c63e52174db0235008c8
- id: 3
title: 'test token 3'
token_salt: AHeetyp1aB
token_hash: da4b9c3aa72633274c37ab3419fbfbe4c5b79310b76027ac36f85e4c5ad0c2342a1d9e1c9b72ca07ec0a66ad2ee3505539af
token_last_eight: 0b8dcb7c
permissions: '{"tasks":["read_all","update"]}'
expires_at: 2099-01-01 00:00:00
owner_id: 2
created: 2023-09-01 07:00:00
# token in plaintext is tk_5e29ae2ae079781ff73b0a3e0fe4d75a0b8dcb7c

View File

@ -18,6 +18,7 @@
title: testbucket3
project_id: 1
created_by_id: 1
is_done_bucket: 1
position: 3
created: 2020-04-18 21:13:52
updated: 2020-04-18 21:13:52
@ -25,6 +26,7 @@
title: testbucket4 - other project
project_id: 2
created_by_id: 1
is_done_bucket: 1
created: 2020-04-18 21:13:52
updated: 2020-04-18 21:13:52
# The following are not or only partly owned by user 1
@ -38,7 +40,6 @@
title: testbucket6
project_id: 6
created_by_id: 1
position: 1
created: 2020-04-18 21:13:52
updated: 2020-04-18 21:13:52
- id: 7
@ -136,7 +137,6 @@
title: testbucket22
project_id: 6
created_by_id: 1
position: 2
created: 2020-04-18 21:13:52
updated: 2020-04-18 21:13:52
- id: 23
@ -220,19 +220,7 @@
updated: 2020-04-18 21:13:52
- id: 36
title: testbucket36
project_id: 33
created_by_id: 6
created: 2020-04-18 21:13:52
updated: 2020-04-18 21:13:52
- id: 37
title: testbucket37
project_id: 34
created_by_id: 6
created: 2020-04-18 21:13:52
updated: 2020-04-18 21:13:52
- id: 38
title: testbucket36
project_id: 36
project_id: 26
created_by_id: 15
created: 2020-04-18 21:13:52
updated: 2020-04-18 21:13:52

View File

@ -10,6 +10,9 @@
- entity_id: 34
user_id: 13 # owner
kind: 1
- entity_id: 34
user_id: 1
kind: 1
- entity_id: 23
user_id: 12 # owner
kind: 2

View File

@ -15,6 +15,6 @@
label_id: 4
created: 2018-12-01 15:13:12
- id: 5
task_id: 40
task_id: 39
label_id: 4
created: 2018-12-01 15:13:12

View File

@ -0,0 +1,96 @@
- id: 1
title: testnamespace
description: Lorem Ipsum
owner_id: 1
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
- id: 2
title: testnamespace2
description: Lorem Ipsum
owner_id: 2
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
- id: 3
title: testnamespace3
description: Lorem Ipsum
owner_id: 3
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
- id: 6
title: testnamespace6
description: Lorem Ipsum
owner_id: 6
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
- id: 7
title: testnamespace7
description: Lorem Ipsum
owner_id: 6
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
- id: 8
title: testnamespace8
description: Lorem Ipsum
owner_id: 6
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
- id: 9
title: testnamespace9
description: Lorem Ipsum
owner_id: 6
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
- id: 10
title: testnamespace10
description: Lorem Ipsum
owner_id: 6
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
- id: 11
title: testnamespace11
description: Lorem Ipsum
owner_id: 6
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
- id: 12
title: testnamespace12
description: Lorem Ipsum
owner_id: 6
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
- id: 13
title: testnamespace13
description: Lorem Ipsum
owner_id: 7
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
- id: 14
title: testnamespace14
description: Lorem Ipsum
owner_id: 7
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
- id: 15
title: testnamespace15
description: Lorem Ipsum
owner_id: 13
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
- id: 16
title: Archived testnamespace16
owner_id: 1
is_archived: 1
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
- id: 17
title: testnamespace17
description: Lorem Ipsum
owner_id: 12
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
- id: 18
title: testnamespace18
description: Lorem Ipsum
owner_id: 15
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12

View File

@ -4,8 +4,8 @@
description: Lorem Ipsum
identifier: test1
owner_id: 1
namespace_id: 1
position: 3
done_bucket_id: 3
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
-
@ -14,8 +14,8 @@
description: Lorem Ipsum
identifier: test2
owner_id: 3
namespace_id: 1
position: 2
done_bucket_id: 4
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
-
@ -24,6 +24,7 @@
description: Lorem Ipsum
identifier: test3
owner_id: 3
namespace_id: 2
position: 1
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
@ -33,6 +34,7 @@
description: Lorem Ipsum
identifier: test4
owner_id: 3
namespace_id: 3
position: 4
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
@ -42,6 +44,7 @@
description: Lorem Ipsum
identifier: test5
owner_id: 5
namespace_id: 5
position: 5
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
@ -51,8 +54,8 @@
description: Lorem Ipsum
identifier: test6
owner_id: 6
namespace_id: 6
position: 6
default_bucket_id: 22
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
-
@ -61,6 +64,7 @@
description: Lorem Ipsum
identifier: test7
owner_id: 6
namespace_id: 6
position: 7
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
@ -70,6 +74,7 @@
description: Lorem Ipsum
identifier: test8
owner_id: 6
namespace_id: 6
position: 8
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
@ -79,6 +84,7 @@
description: Lorem Ipsum
identifier: test9
owner_id: 6
namespace_id: 6
position: 9
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
@ -88,6 +94,7 @@
description: Lorem Ipsum
identifier: test10
owner_id: 6
namespace_id: 6
position: 10
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
@ -97,6 +104,7 @@
description: Lorem Ipsum
identifier: test11
owner_id: 6
namespace_id: 6
position: 11
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
@ -106,8 +114,8 @@
description: Lorem Ipsum
identifier: test12
owner_id: 6
namespace_id: 7
position: 12
parent_project_id: 27
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
-
@ -116,8 +124,8 @@
description: Lorem Ipsum
identifier: test13
owner_id: 6
namespace_id: 8
position: 13
parent_project_id: 28
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
-
@ -126,8 +134,8 @@
description: Lorem Ipsum
identifier: test14
owner_id: 6
namespace_id: 9
position: 14
parent_project_id: 29
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
-
@ -136,8 +144,8 @@
description: Lorem Ipsum
identifier: test15
owner_id: 6
namespace_id: 10
position: 15
parent_project_id: 32
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
-
@ -146,8 +154,8 @@
description: Lorem Ipsum
identifier: test16
owner_id: 6
namespace_id: 11
position: 16
parent_project_id: 33
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
-
@ -156,8 +164,8 @@
description: Lorem Ipsum
identifier: test17
owner_id: 6
namespace_id: 12
position: 17
parent_project_id: 34
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
# This project is owned by user 7, and several other users have access to it via different methods.
@ -168,6 +176,7 @@
description: Lorem Ipsum
identifier: test18
owner_id: 7
namespace_id: 13
position: 18
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
@ -177,8 +186,8 @@
description: Lorem Ipsum
identifier: test19
owner_id: 7
namespace_id: 14
position: 19
parent_project_id: 29
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
# User 1 does not have access to this project
@ -188,17 +197,18 @@
description: Lorem Ipsum
identifier: test20
owner_id: 13
namespace_id: 15
position: 20
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
-
id: 21
title: Test21 archived through parent list
title: Test21 archived through namespace
description: Lorem Ipsum
identifier: test21
owner_id: 1
namespace_id: 16
position: 21
parent_project_id: 22
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
-
@ -207,6 +217,7 @@
description: Lorem Ipsum
identifier: test22
owner_id: 1
namespace_id: 1
is_archived: 1
position: 22
updated: 2018-12-02 15:13:12
@ -217,6 +228,7 @@
description: Lorem Ipsum
identifier: test23
owner_id: 12
namespace_id: 17
position: 23
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
@ -226,104 +238,28 @@
description: Lorem Ipsum
identifier: test6
owner_id: 6
namespace_id: 6
position: 7
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
-
id: 25
title: Test25
owner_id: 6
parent_project_id: 12
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
-
id: 26
title: Test26
owner_id: 6
parent_project_id: 25
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
-
id: 27
title: Test27
owner_id: 6
position: 2700
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
-
id: 28
title: Test28
owner_id: 6
position: 2800
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
-
id: 29
title: Test29
owner_id: 6
position: 2900
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
-
id: 30
title: Test30
owner_id: 6
position: 3000
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
-
id: 31
title: Test31
owner_id: 6
position: 3100
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
-
id: 32
title: Test32
owner_id: 6
position: 3200
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
-
id: 33
title: Test33
owner_id: 6
position: 3300
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
-
id: 34
title: Test34
owner_id: 6
position: 3400
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
-
id: 35
title: Test35 with background
title: Test25 with background
description: Lorem Ipsum
identifier: test6
owner_id: 6
namespace_id: 6
background_file_id: 1
position: 8
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
-
id: 36
title: Project 36 for Caldav tests
id: 26
title: List 26 for Caldav tests
description: Lorem Ipsum
identifier: test36
identifier: test26
owner_id: 15
position: 1
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
-
id: 37
title: Project 37
description: Lorem Ipsum
identifier: test37
owner_id: 16
namespace_id: 18
position: 1
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12

View File

@ -3,14 +3,24 @@
entity_id: 2
user_id: 1
created: 2021-02-01 15:13:12
- id: 2
entity_type: 1 # Namespace
entity_id: 6
user_id: 6
created: 2021-02-01 15:13:12
- id: 3
entity_type: 2 # project
entity_id: 12 # belongs to parent project 7
entity_id: 12 # belongs to namespace 7
user_id: 6
created: 2021-02-01 15:13:12
- id: 4
entity_type: 3 # Task
entity_id: 22 # belongs to project 13
entity_id: 22 # belongs to project 13 which belongs to namespace 8
user_id: 6
created: 2021-02-01 15:13:12
- id: 5
entity_type: 1 # Namespace
entity_id: 8
user_id: 6
created: 2021-02-01 15:13:12
- id: 6
@ -23,8 +33,3 @@
entity_id: 26
user_id: 6
created: 2021-02-01 15:13:12
- id: 8
entity_type: 2 # Project
entity_id: 32
user_id: 6
created: 2021-02-01 15:13:12

View File

@ -13,6 +13,6 @@
reminder: 2018-12-01 01:13:44
created: 2018-12-01 01:12:04
- id: 4
task_id: 40
task_id: 39
reminder: 2023-03-04 15:00:00
created: 2018-12-01 01:12:04

View File

@ -193,7 +193,7 @@
title: 'task #21'
done: false
created_by_id: 6
project_id: 32
project_id: 12
index: 1
bucket_id: 12
created: 2018-12-01 01:12:04
@ -202,18 +202,18 @@
title: 'task #22'
done: false
created_by_id: 6
project_id: 33
project_id: 13
index: 1
bucket_id: 36
bucket_id: 13
created: 2018-12-01 01:12:04
updated: 2018-12-01 01:12:04
- id: 23
title: 'task #23'
done: false
created_by_id: 6
project_id: 34
project_id: 14
index: 1
bucket_id: 37
bucket_id: 14
created: 2018-12-01 01:12:04
updated: 2018-12-01 01:12:04
- id: 24
@ -357,19 +357,13 @@
updated: 2018-12-01 01:12:04
due_date: 2018-10-30 22:25:24
- id: 39
title: 'task #39'
created_by_id: 1
project_id: 25
created: 2018-12-01 01:12:04
updated: 2018-12-01 01:12:04
- id: 40
uid: 'uid-caldav-test'
title: 'Title Caldav Test'
description: 'Description Caldav Test'
priority: 3
done: false
created_by_id: 15
project_id: 36
project_id: 26
index: 39
due_date: 2023-03-01 15:00:00
created: 2018-12-01 01:12:04

View File

@ -0,0 +1,52 @@
- id: 1
team_id: 1
namespace_id: 3
right: 0
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
- id: 2
team_id: 2
namespace_id: 3
right: 0
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
- id: 3
team_id: 5
namespace_id: 7
right: 0
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
- id: 4
team_id: 6
namespace_id: 8
right: 1
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
- id: 5
team_id: 7
namespace_id: 9
right: 2
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
- id: 6
team_id: 11
namespace_id: 14
right: 0
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
- id: 7
team_id: 12
namespace_id: 14
right: 1
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
- id: 8
team_id: 13
namespace_id: 14
right: 2
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12

View File

@ -52,45 +52,3 @@
right: 0
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
- id: 9
team_id: 1
project_id: 28
right: 0
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
- id: 10
team_id: 11
project_id: 29
right: 0
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
- id: 11
team_id: 12
project_id: 29
right: 1
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
- id: 12
team_id: 13
project_id: 29
right: 2
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
- id: 13
team_id: 1
project_id: 32
right: 0
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
- id: 14
team_id: 1
project_id: 33
right: 1
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
- id: 15
team_id: 1
project_id: 34
right: 2
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12

View File

@ -11,6 +11,15 @@
- id: 4
name: testteam4_admin_on_project8
created_by_id: 1
- id: 5
name: testteam2_read_only_on_namespace7
created_by_id: 1
- id: 6
name: testteam3_write_on_namespace8
created_by_id: 1
- id: 7
name: testteam4_admin_on_namespace9
created_by_id: 1
- id: 8
name: testteam8
created_by_id: 7

View File

@ -12,7 +12,6 @@
password: '$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.' # 1234
email: 'user2@example.com'
issuer: local
default_project_id: 4
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
-
@ -21,7 +20,6 @@
password: '$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.' # 1234
email: 'user3@example.com'
issuer: local
default_project_id: 4
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
-
@ -118,11 +116,3 @@
issuer: local
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
- id: 16
username: 'user16'
password: '$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.' # 1234
email: 'user16@example.com'
issuer: local
default_project_id: 37
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12

View File

@ -0,0 +1,52 @@
- id: 1
user_id: 1
namespace_id: 3
right: 0
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
- id: 2
user_id: 2
namespace_id: 3
right: 0
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
- id: 3
user_id: 1
namespace_id: 10
right: 0
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
- id: 4
user_id: 1
namespace_id: 11
right: 1
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
- id: 5
user_id: 1
namespace_id: 12
right: 2
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
- id: 6
user_id: 11
namespace_id: 14
right: 0
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
- id: 7
user_id: 12
namespace_id: 14
right: 1
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
- id: 8
user_id: 13
namespace_id: 14
right: 2
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12

View File

@ -47,54 +47,6 @@
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
- id: 9
user_id: 1
project_id: 27
right: 0
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
- id: 10
user_id: 11
project_id: 29
right: 0
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
- id: 11
user_id: 12
project_id: 29
right: 1
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
- id: 12
user_id: 13
project_id: 29
right: 2
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
- id: 13
user_id: 1
project_id: 30
right: 1
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
- id: 14
user_id: 1
project_id: 31
right: 2
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
- id: 15
user_id: 1
project_id: 28
right: 1
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
- id: 16
user_id: 1
project_id: 29
right: 2
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
- id: 17
user_id: 15
project_id: 26
right: 0

View File

@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by

View File

@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@ -17,7 +17,6 @@
package db
import (
"encoding/json"
"fmt"
"os"
"testing"
@ -52,7 +51,7 @@ func CreateTestEngine() (engine *xorm.Engine, err error) {
}
engine.SetMapper(names.GonicMapper{})
logger := log.NewXormLogger(config.LogEnabled.GetBool(), config.LogDatabase.GetString(), "DEBUG")
logger := log.NewXormLogger("DEBUG")
logger.ShowSQL(os.Getenv("UNIT_TESTS_VERBOSE") == "1")
engine.SetLogger(logger)
engine.SetTZLocation(config.GetTimeZone())
@ -94,16 +93,7 @@ func AssertExists(t *testing.T, table string, values map[string]interface{}, cus
exists, err = x.Table(table).Where(values).Get(&v)
}
assert.NoError(t, err, fmt.Sprintf("Failed to assert entries exist in db, error was: %s", err))
if !exists {
all := []map[string]interface{}{}
err = x.Table(table).Find(&all)
assert.NoError(t, err, fmt.Sprintf("Failed to assert entries exist in db, error was: %s", err))
pretty, err := json.MarshalIndent(all, "", " ")
assert.NoError(t, err, fmt.Sprintf("Failed to assert entries exist in db, error was: %s", err))
t.Errorf(fmt.Sprintf("Entries %v do not exist in table %s\n\nFound entries instead: %v", values, table, string(pretty)))
}
assert.True(t, exists, fmt.Sprintf("Entries %v do not exist in table %s", values, table))
}
// AssertMissing checks and asserts the nonexiste nce of certain entries in the db

View File

@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by

View File

@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@ -21,7 +21,6 @@ import (
"encoding/json"
"time"
"code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/log"
vmetrics "code.vikunja.io/api/pkg/metrics"
"github.com/ThreeDotsLabs/watermill"
@ -40,7 +39,7 @@ type Event interface {
// InitEvents sets up everything needed to work with events
func InitEvents() (err error) {
logger := log.NewWatermillLogger(config.LogEnabled.GetBool(), config.LogEvents.GetString(), config.LogEventsLevel.GetString())
logger := log.NewWatermillLogger()
router, err := message.NewRouter(
message.RouterConfig{},

View File

@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by

View File

@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by

View File

@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@ -17,6 +17,7 @@
package files
import (
"code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/log"
"xorm.io/xorm"
@ -32,6 +33,11 @@ func SetEngine() (err error) {
return
}
// Cache
if config.CacheEnabled.GetBool() && config.CacheType.GetString() == "redis" {
db.RegisterTableStructsForCache(GetTables())
}
return nil
}

View File

@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by

View File

@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by

View File

@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by

View File

@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by

View File

@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by

View File

@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by

View File

@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by

View File

@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@ -29,15 +29,14 @@ import (
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/modules/auth/openid"
"code.vikunja.io/api/pkg/modules/keyvalue"
migrator "code.vikunja.io/api/pkg/modules/migration"
"code.vikunja.io/api/pkg/notifications"
"code.vikunja.io/api/pkg/red"
"code.vikunja.io/api/pkg/user"
)
// LightInit will only fullInit config, redis, logger but no db connection.
func LightInit() {
// Set logger
log.InitLogger()
// Init the config
config.InitConfig()
@ -46,6 +45,9 @@ func LightInit() {
// Init keyvalue store
keyvalue.InitStorage()
// Set logger
log.InitLogger()
}
// InitEngines intializes all db connections
@ -54,14 +56,27 @@ func InitEngines() {
if err != nil {
log.Fatal(err.Error())
}
err = user.InitDB()
if err != nil {
log.Fatal(err.Error())
}
err = files.SetEngine()
if err != nil {
log.Fatal(err.Error())
}
err = migrator.InitDB()
if err != nil {
log.Fatal(err.Error())
}
err = notifications.InitDB()
if err != nil {
log.Fatal(err.Error())
}
}
// FullInitWithoutAsync does a full init without any async handlers (cron or events)
func FullInitWithoutAsync() {
// FullInit initializes all kinds of things in the right order
func FullInit() {
LightInit()
// Initialize the files handler
@ -73,17 +88,8 @@ func FullInitWithoutAsync() {
// Set Engine
InitEngines()
// Init Typesense
models.InitTypesense()
// Start the mail daemon
mail.StartMailDaemon()
}
// FullInit initializes all kinds of things in the right order
func FullInit() {
FullInitWithoutAsync()
// Start the cron
cron.Init()
@ -94,7 +100,6 @@ func FullInit() {
models.RegisterUserDeletionCron()
models.RegisterOldExportCleanupCron()
openid.CleanupSavedOpenIDProviders()
models.RegisterPeriodicTypesenseResyncCron()
// Start processing events
go func() {

View File

@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by

View File

@ -1,113 +0,0 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public Licensee for more details.
//
// You should have received a copy of the GNU Affero General Public Licensee
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package integrations
import (
"net/http"
"net/http/httptest"
"testing"
"code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/modules/auth"
"code.vikunja.io/api/pkg/routes"
"code.vikunja.io/api/pkg/user"
"github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert"
)
func TestAPIToken(t *testing.T) {
t.Run("valid token", func(t *testing.T) {
e, err := setupTestEnv()
assert.NoError(t, err)
req := httptest.NewRequest(http.MethodGet, "/api/v1/tasks/all", nil)
res := httptest.NewRecorder()
c := e.NewContext(req, res)
h := routes.SetupTokenMiddleware()(func(c echo.Context) error {
u, err := auth.GetAuthFromClaims(c)
if err != nil {
return c.String(http.StatusInternalServerError, err.Error())
}
return c.JSON(http.StatusOK, u)
})
req.Header.Set(echo.HeaderAuthorization, "Bearer tk_2eef46f40ebab3304919ab2e7e39993f75f29d2e") // Token 1
assert.NoError(t, h(c))
// check if the request handlers "see" the request as if it came directly from that user
assert.Contains(t, res.Body.String(), `"username":"user1"`)
})
t.Run("invalid token", func(t *testing.T) {
e, err := setupTestEnv()
assert.NoError(t, err)
req := httptest.NewRequest(http.MethodGet, "/api/v1/tasks/all", nil)
res := httptest.NewRecorder()
c := e.NewContext(req, res)
h := routes.SetupTokenMiddleware()(func(c echo.Context) error {
return c.String(http.StatusOK, "test")
})
req.Header.Set(echo.HeaderAuthorization, "Bearer tk_loremipsumdolorsitamet")
assert.Error(t, h(c))
})
t.Run("expired token", func(t *testing.T) {
e, err := setupTestEnv()
assert.NoError(t, err)
req := httptest.NewRequest(http.MethodGet, "/api/v1/tasks/all", nil)
res := httptest.NewRecorder()
c := e.NewContext(req, res)
h := routes.SetupTokenMiddleware()(func(c echo.Context) error {
return c.String(http.StatusOK, "test")
})
req.Header.Set(echo.HeaderAuthorization, "Bearer tk_a5e6f92ddbad68f49ee2c63e52174db0235008c8") // Token 2
assert.Error(t, h(c))
})
t.Run("valid token, invalid scope", func(t *testing.T) {
e, err := setupTestEnv()
assert.NoError(t, err)
req := httptest.NewRequest(http.MethodGet, "/api/v1/projects", nil)
res := httptest.NewRecorder()
c := e.NewContext(req, res)
h := routes.SetupTokenMiddleware()(func(c echo.Context) error {
return c.String(http.StatusOK, "test")
})
req.Header.Set(echo.HeaderAuthorization, "Bearer tk_2eef46f40ebab3304919ab2e7e39993f75f29d2e")
assert.Error(t, h(c))
})
t.Run("jwt", func(t *testing.T) {
e, err := setupTestEnv()
assert.NoError(t, err)
req := httptest.NewRequest(http.MethodGet, "/api/v1/tasks/all", nil)
res := httptest.NewRecorder()
c := e.NewContext(req, res)
h := routes.SetupTokenMiddleware()(func(c echo.Context) error {
return c.String(http.StatusOK, "test")
})
s := db.NewSession()
defer s.Close()
u, err := user.GetUserByID(s, 1)
assert.NoError(t, err)
jwt, err := auth.NewUserJWTAuthtoken(u, false)
assert.NoError(t, err)
req.Header.Set(echo.HeaderAuthorization, "Bearer "+jwt)
assert.NoError(t, h(c))
})
}

View File

@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@ -17,6 +17,7 @@
package integrations
import (
"net/url"
"testing"
"code.vikunja.io/api/pkg/models"
@ -25,27 +26,32 @@ import (
)
// This tests the following behaviour:
// 2. A project which belongs to an archived project cannot be edited.
// 1. A namespace should not be editable if it is archived.
// 1. With the exception being to un-archive it.
// 2. A project which belongs to an archived namespace cannot be edited.
// 3. An archived project should not be editable.
// 1. Except for un-archiving it.
// 4. It is not possible to un-archive a project individually if its parent project is archived.
// 5. Creating new child projects in an archived project should not work.
// 4. It is not possible to un-archive a project individually if its namespace is archived.
// 5. Creating new projects on an archived namespace should not work.
// 6. Creating new tasks on an archived project should not work.
// 7. Creating new tasks on a project whose parent project is archived should not work.
// 7. Creating new tasks on a project who's namespace is archived should not work.
// 8. Editing tasks on an archived project should not work.
// 9. Editing tasks on a project whose parent project is archived should not work.
// 11. Archived projects should not appear in the list with all projects.
// 12. Projects whose parent project is archived should not appear in the project with all projects.
// 9. Editing tasks on a project who's namespace is archived should not work.
// 10. Archived namespaces should not appear in the project with all namespaces.
// 11. Archived projects should not appear in the project with all projects.
// 12. Projects who's namespace is archived should not appear in the project with all projects.
//
// All of this is tested through integration tests because it's not yet clear if this will be implemented directly
// or with some kind of middleware.
//
// Maybe the inheritance of projects from parents could be solved with some kind of is_archived_inherited flag -
// Maybe the inheritance of projects from namespaces could be solved with some kind of is_archived_inherited flag -
// that way I'd only need to implement the checking on a project level and update the flag for all projects once the
// project is archived. The archived flag would then be used to not accedentially unarchive projects which were
// already individually archived when the parent project was archived.
// namespace is archived. The archived flag would then be used to not accedentially unarchive projects which were
// already individually archived when the namespace was archived.
// Should still test it all though.
//
// Project 21 belongs to project 16
// Namespace 16 is archived
// Project 21 belongs to namespace 16
// Project 22 is archived individually
func TestArchived(t *testing.T) {
@ -56,6 +62,13 @@ func TestArchived(t *testing.T) {
},
t: t,
}
testNamespaceHandler := webHandlerTest{
user: &testuser1,
strFunc: func() handler.CObject {
return &models.Namespace{}
},
t: t,
}
testTaskHandler := webHandlerTest{
user: &testuser1,
strFunc: func() handler.CObject {
@ -92,103 +105,134 @@ func TestArchived(t *testing.T) {
t: t,
}
taskTests := func(taskID string, errCode int, t *testing.T) {
t.Run("task", func(t *testing.T) {
t.Run("edit task", func(t *testing.T) {
_, err := testTaskHandler.testUpdateWithUser(nil, map[string]string{"projecttask": taskID}, `{"title":"TestIpsum"}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, errCode)
})
t.Run("delete", func(t *testing.T) {
_, err := testTaskHandler.testDeleteWithUser(nil, map[string]string{"projecttask": taskID})
assert.Error(t, err)
assertHandlerErrorCode(t, err, errCode)
})
t.Run("add new labels", func(t *testing.T) {
_, err := testLabelHandler.testCreateWithUser(nil, map[string]string{"projecttask": taskID}, `{"label_id":1}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, errCode)
})
t.Run("remove lables", func(t *testing.T) {
_, err := testLabelHandler.testDeleteWithUser(nil, map[string]string{"projecttask": taskID, "label": "4"})
assert.Error(t, err)
assertHandlerErrorCode(t, err, errCode)
})
t.Run("add assignees", func(t *testing.T) {
_, err := testAssigneeHandler.testCreateWithUser(nil, map[string]string{"projecttask": taskID}, `{"user_id":3}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, errCode)
})
t.Run("remove assignees", func(t *testing.T) {
_, err := testAssigneeHandler.testDeleteWithUser(nil, map[string]string{"projecttask": taskID, "user": "2"})
assert.Error(t, err)
assertHandlerErrorCode(t, err, errCode)
})
t.Run("add relation", func(t *testing.T) {
_, err := testRelationHandler.testCreateWithUser(nil, map[string]string{"task": taskID}, `{"other_task_id":1,"relation_kind":"related"}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, errCode)
})
t.Run("remove relation", func(t *testing.T) {
_, err := testRelationHandler.testDeleteWithUser(nil, map[string]string{"task": taskID}, `{"other_task_id":2,"relation_kind":"related"}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, errCode)
})
t.Run("add comment", func(t *testing.T) {
_, err := testCommentHandler.testCreateWithUser(nil, map[string]string{"task": taskID}, `{"comment":"Lorem"}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, errCode)
})
t.Run("remove comment", func(t *testing.T) {
var commentID = "15"
if taskID == "36" {
commentID = "16"
}
_, err := testCommentHandler.testDeleteWithUser(nil, map[string]string{"task": taskID, "commentid": commentID})
assert.Error(t, err)
assertHandlerErrorCode(t, err, errCode)
})
})
}
// The project belongs to an archived parent project
t.Run("archived parent project", func(t *testing.T) {
t.Run("namespace", func(t *testing.T) {
t.Run("not editable", func(t *testing.T) {
_, err := testProjectHandler.testUpdateWithUser(nil, map[string]string{"project": "21"}, `{"title":"TestIpsum","is_archived":true}`)
_, err := testNamespaceHandler.testUpdateWithUser(nil, map[string]string{"namespace": "16"}, `{"title":"TestIpsum","is_archived":true}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeProjectIsArchived)
})
t.Run("no new tasks", func(t *testing.T) {
_, err := testTaskHandler.testCreateWithUser(nil, map[string]string{"project": "21"}, `{"title":"Lorem"}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeProjectIsArchived)
})
t.Run("not unarchivable", func(t *testing.T) {
_, err := testProjectHandler.testUpdateWithUser(nil, map[string]string{"project": "21"}, `{"title":"LoremIpsum","is_archived":false}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeProjectIsArchived)
})
taskTests("35", models.ErrCodeProjectIsArchived, t)
})
// The project itself is archived
t.Run("archived individually", func(t *testing.T) {
t.Run("not editable", func(t *testing.T) {
_, err := testProjectHandler.testUpdateWithUser(nil, map[string]string{"project": "22"}, `{"title":"TestIpsum","is_archived":true}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeProjectIsArchived)
})
t.Run("no new tasks", func(t *testing.T) {
_, err := testTaskHandler.testCreateWithUser(nil, map[string]string{"project": "22"}, `{"title":"Lorem"}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeProjectIsArchived)
assertHandlerErrorCode(t, err, models.ErrCodeNamespaceIsArchived)
})
t.Run("unarchivable", func(t *testing.T) {
rec, err := testProjectHandler.testUpdateWithUser(nil, map[string]string{"project": "22"}, `{"title":"LoremIpsum","is_archived":false}`)
rec, err := testNamespaceHandler.testUpdateWithUser(nil, map[string]string{"namespace": "16"}, `{"title":"TestIpsum","is_archived":false}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"is_archived":false`)
})
t.Run("no new projects", func(t *testing.T) {
_, err := testProjectHandler.testCreateWithUser(nil, map[string]string{"namespace": "16"}, `{"title":"Lorem"}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeNamespaceIsArchived)
})
t.Run("should not appear in the project", func(t *testing.T) {
rec, err := testNamespaceHandler.testReadAllWithUser(nil, nil)
assert.NoError(t, err)
assert.NotContains(t, rec.Body.String(), `"title":"Archived testnamespace16"`)
})
t.Run("should appear in the project if explicitly requested", func(t *testing.T) {
rec, err := testNamespaceHandler.testReadAllWithUser(url.Values{"is_archived": []string{"true"}}, nil)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Archived testnamespace16"`)
})
})
taskTests("36", models.ErrCodeProjectIsArchived, t)
t.Run("project", func(t *testing.T) {
taskTests := func(taskID string, errCode int, t *testing.T) {
t.Run("task", func(t *testing.T) {
t.Run("edit task", func(t *testing.T) {
_, err := testTaskHandler.testUpdateWithUser(nil, map[string]string{"projecttask": taskID}, `{"title":"TestIpsum"}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, errCode)
})
t.Run("delete", func(t *testing.T) {
_, err := testTaskHandler.testDeleteWithUser(nil, map[string]string{"projecttask": taskID})
assert.Error(t, err)
assertHandlerErrorCode(t, err, errCode)
})
t.Run("add new labels", func(t *testing.T) {
_, err := testLabelHandler.testCreateWithUser(nil, map[string]string{"projecttask": taskID}, `{"label_id":1}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, errCode)
})
t.Run("remove lables", func(t *testing.T) {
_, err := testLabelHandler.testDeleteWithUser(nil, map[string]string{"projecttask": taskID, "label": "4"})
assert.Error(t, err)
assertHandlerErrorCode(t, err, errCode)
})
t.Run("add assignees", func(t *testing.T) {
_, err := testAssigneeHandler.testCreateWithUser(nil, map[string]string{"projecttask": taskID}, `{"user_id":3}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, errCode)
})
t.Run("remove assignees", func(t *testing.T) {
_, err := testAssigneeHandler.testDeleteWithUser(nil, map[string]string{"projecttask": taskID, "user": "2"})
assert.Error(t, err)
assertHandlerErrorCode(t, err, errCode)
})
t.Run("add relation", func(t *testing.T) {
_, err := testRelationHandler.testCreateWithUser(nil, map[string]string{"task": taskID}, `{"other_task_id":1,"relation_kind":"related"}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, errCode)
})
t.Run("remove relation", func(t *testing.T) {
_, err := testRelationHandler.testDeleteWithUser(nil, map[string]string{"task": taskID}, `{"other_task_id":2,"relation_kind":"related"}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, errCode)
})
t.Run("add comment", func(t *testing.T) {
_, err := testCommentHandler.testCreateWithUser(nil, map[string]string{"task": taskID}, `{"comment":"Lorem"}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, errCode)
})
t.Run("remove comment", func(t *testing.T) {
var commentID = "15"
if taskID == "36" {
commentID = "16"
}
_, err := testCommentHandler.testDeleteWithUser(nil, map[string]string{"task": taskID, "commentid": commentID})
assert.Error(t, err)
assertHandlerErrorCode(t, err, errCode)
})
})
}
// The project belongs to an archived namespace
t.Run("archived namespace", func(t *testing.T) {
t.Run("not editable", func(t *testing.T) {
_, err := testProjectHandler.testUpdateWithUser(nil, map[string]string{"project": "21"}, `{"title":"TestIpsum","is_archived":true}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeNamespaceIsArchived)
})
t.Run("no new tasks", func(t *testing.T) {
_, err := testTaskHandler.testCreateWithUser(nil, map[string]string{"project": "21"}, `{"title":"Lorem"}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeNamespaceIsArchived)
})
t.Run("not unarchivable", func(t *testing.T) {
_, err := testProjectHandler.testUpdateWithUser(nil, map[string]string{"project": "21"}, `{"title":"LoremIpsum","is_archived":false}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeNamespaceIsArchived)
})
taskTests("35", models.ErrCodeNamespaceIsArchived, t)
})
// The project itself is archived
t.Run("archived individually", func(t *testing.T) {
t.Run("not editable", func(t *testing.T) {
_, err := testProjectHandler.testUpdateWithUser(nil, map[string]string{"project": "22"}, `{"title":"TestIpsum","is_archived":true}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeProjectIsArchived)
})
t.Run("no new tasks", func(t *testing.T) {
_, err := testTaskHandler.testCreateWithUser(nil, map[string]string{"project": "22"}, `{"title":"Lorem"}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeProjectIsArchived)
})
t.Run("unarchivable", func(t *testing.T) {
rec, err := testProjectHandler.testUpdateWithUser(nil, map[string]string{"project": "22"}, `{"title":"LoremIpsum","is_archived":false,"namespace_id":1}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"is_archived":false`)
})
taskTests("36", models.ErrCodeProjectIsArchived, t)
})
})
}

View File

@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@ -28,7 +28,7 @@ const vtodo = `BEGIN:VCALENDAR
VERSION:2.0
METHOD:PUBLISH
X-PUBLISHED-TTL:PT4H
X-WR-CALNAME:List 36 for Caldav tests
X-WR-CALNAME:List 26 for Caldav tests
PRODID:-//Vikunja Todo App//EN
BEGIN:VTODO
UID:uid
@ -46,22 +46,22 @@ END:VCALENDAR`
func TestCaldav(t *testing.T) {
t.Run("Delivers VTODO for project", func(t *testing.T) {
rec, err := newCaldavTestRequestWithUser(t, http.MethodGet, caldav.ProjectHandler, &testuser15, ``, nil, map[string]string{"project": "36"})
rec, err := newCaldavTestRequestWithUser(t, http.MethodGet, caldav.ProjectHandler, &testuser15, ``, nil, map[string]string{"project": "26"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), "BEGIN:VCALENDAR")
assert.Contains(t, rec.Body.String(), "PRODID:-//Vikunja Todo App//EN")
assert.Contains(t, rec.Body.String(), "X-WR-CALNAME:Project 36 for Caldav tests")
assert.Contains(t, rec.Body.String(), "X-WR-CALNAME:List 26 for Caldav tests")
assert.Contains(t, rec.Body.String(), "BEGIN:VTODO")
assert.Contains(t, rec.Body.String(), "END:VTODO")
assert.Contains(t, rec.Body.String(), "END:VCALENDAR")
})
t.Run("Import VTODO", func(t *testing.T) {
rec, err := newCaldavTestRequestWithUser(t, http.MethodPut, caldav.TaskHandler, &testuser15, vtodo, nil, map[string]string{"project": "36", "task": "uid"})
rec, err := newCaldavTestRequestWithUser(t, http.MethodPut, caldav.TaskHandler, &testuser15, vtodo, nil, map[string]string{"project": "26", "task": "uid"})
assert.NoError(t, err)
assert.Equal(t, 201, rec.Result().StatusCode)
assert.Equal(t, rec.Result().StatusCode, 201)
})
t.Run("Export VTODO", func(t *testing.T) {
rec, err := newCaldavTestRequestWithUser(t, http.MethodGet, caldav.TaskHandler, &testuser15, ``, nil, map[string]string{"project": "36", "task": "uid-caldav-test"})
rec, err := newCaldavTestRequestWithUser(t, http.MethodGet, caldav.TaskHandler, &testuser15, ``, nil, map[string]string{"project": "26", "task": "uid-caldav-test"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), "BEGIN:VCALENDAR")
assert.Contains(t, rec.Body.String(), "SUMMARY:Title Caldav Test")

View File

@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by

View File

@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by

View File

@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@ -115,33 +115,33 @@ func TestBucket(t *testing.T) {
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
})
t.Run("Shared Via Parent Project User readonly", func(t *testing.T) {
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"bucket": "12"}, `{"title":"TestLoremIpsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project User write", func(t *testing.T) {
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"bucket": "13"}, `{"title":"TestLoremIpsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
})
t.Run("Shared Via Parent Project User admin", func(t *testing.T) {
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"bucket": "14"}, `{"title":"TestLoremIpsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
})
t.Run("Shared Via Parent Project Team readonly", func(t *testing.T) {
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"bucket": "15"}, `{"title":"TestLoremIpsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project Team write", func(t *testing.T) {
t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"bucket": "16"}, `{"title":"TestLoremIpsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
})
t.Run("Shared Via Parent Project Team admin", func(t *testing.T) {
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"bucket": "17"}, `{"title":"TestLoremIpsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
@ -198,33 +198,33 @@ func TestBucket(t *testing.T) {
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
})
t.Run("Shared Via Parent Project Team readonly", func(t *testing.T) {
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "12", "bucket": "12"})
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project Team write", func(t *testing.T) {
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "13", "bucket": "13"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
})
t.Run("Shared Via Parent Project Team admin", func(t *testing.T) {
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "14", "bucket": "14"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
})
t.Run("Shared Via Parent Project User readonly", func(t *testing.T) {
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "15", "bucket": "15"})
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project User write", func(t *testing.T) {
t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "16", "bucket": "16"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
})
t.Run("Shared Via Parent Project User admin", func(t *testing.T) {
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "17", "bucket": "17"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
@ -281,33 +281,33 @@ func TestBucket(t *testing.T) {
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
t.Run("Shared Via Parent Project Team readonly", func(t *testing.T) {
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "12"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project Team write", func(t *testing.T) {
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "13"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
t.Run("Shared Via Parent Project Team admin", func(t *testing.T) {
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "14"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
t.Run("Shared Via Parent Project User readonly", func(t *testing.T) {
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "15"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project User write", func(t *testing.T) {
t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "16"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
t.Run("Shared Via Parent Project User admin", func(t *testing.T) {
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "17"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)

View File

@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by

View File

@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@ -273,10 +273,10 @@ func TestLinkSharing(t *testing.T) {
})
})
// Creating a project should always be forbidden
// Creating a project should always be forbidden, since users need access to a namespace to create a project
t.Run("Create", func(t *testing.T) {
t.Run("Nonexisting", func(t *testing.T) {
_, err := testHandlerProjectReadOnly.testCreateWithLinkShare(nil, nil, `{"title":"Lorem"}`)
_, err := testHandlerProjectReadOnly.testCreateWithLinkShare(nil, map[string]string{"namespace": "999999"}, `{"title":"Lorem"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
@ -806,4 +806,284 @@ func TestLinkSharing(t *testing.T) {
})
})
})
t.Run("Namespace", func(t *testing.T) {
testHandlerNamespaceReadOnly := webHandlerTest{
linkShare: linkshareRead,
strFunc: func() handler.CObject {
return &models.Namespace{}
},
t: t,
}
testHandlerNamespaceWrite := webHandlerTest{
linkShare: linkShareWrite,
strFunc: func() handler.CObject {
return &models.Namespace{}
},
t: t,
}
testHandlerNamespaceAdmin := webHandlerTest{
linkShare: linkShareAdmin,
strFunc: func() handler.CObject {
return &models.Namespace{}
},
t: t,
}
t.Run("ReadAll", func(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerNamespaceReadOnly.testReadAllWithLinkShare(nil, map[string]string{"namespace": "1"})
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrorCodeGenericForbidden)
})
t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerNamespaceWrite.testReadAllWithLinkShare(nil, map[string]string{"namespace": "2"})
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrorCodeGenericForbidden)
})
t.Run("Shared admin", func(t *testing.T) {
_, err := testHandlerNamespaceAdmin.testReadAllWithLinkShare(nil, map[string]string{"namespace": "3"})
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrorCodeGenericForbidden)
})
})
t.Run("Create", func(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerNamespaceReadOnly.testCreateWithLinkShare(nil, nil, `{"title":"LoremIpsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerNamespaceWrite.testCreateWithLinkShare(nil, nil, `{"title":"LoremIpsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared admin", func(t *testing.T) {
_, err := testHandlerNamespaceAdmin.testCreateWithLinkShare(nil, nil, `{"title":"LoremIpsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
})
t.Run("Update", func(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerNamespaceReadOnly.testUpdateWithLinkShare(nil, map[string]string{"namespace": "1"}, `{"title":"LoremIpsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerNamespaceWrite.testUpdateWithLinkShare(nil, map[string]string{"namespace": "2"}, `{"title":"LoremIpsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared admin", func(t *testing.T) {
_, err := testHandlerNamespaceAdmin.testUpdateWithLinkShare(nil, map[string]string{"namespace": "3"}, `{"title":"LoremIpsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
})
t.Run("Delete", func(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerNamespaceReadOnly.testDeleteWithLinkShare(nil, map[string]string{"namespace": "1"})
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerNamespaceWrite.testDeleteWithLinkShare(nil, map[string]string{"namespace": "2"})
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared admin", func(t *testing.T) {
_, err := testHandlerNamespaceAdmin.testDeleteWithLinkShare(nil, map[string]string{"namespace": "3"})
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
})
t.Run("Right Management", func(t *testing.T) {
t.Run("Users", func(t *testing.T) {
testHandlerNamespaceUserReadOnly := webHandlerTest{
linkShare: linkshareRead,
strFunc: func() handler.CObject {
return &models.NamespaceUser{}
},
t: t,
}
testHandlerNamespaceUserWrite := webHandlerTest{
linkShare: linkShareWrite,
strFunc: func() handler.CObject {
return &models.NamespaceUser{}
},
t: t,
}
testHandlerNamespaceUserAdmin := webHandlerTest{
linkShare: linkShareAdmin,
strFunc: func() handler.CObject {
return &models.NamespaceUser{}
},
t: t,
}
t.Run("ReadAll", func(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerNamespaceUserReadOnly.testReadAllWithLinkShare(nil, map[string]string{"namespace": "1"})
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeNeedToHaveNamespaceReadAccess)
})
t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerNamespaceUserWrite.testReadAllWithLinkShare(nil, map[string]string{"namespace": "2"})
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeNeedToHaveNamespaceReadAccess)
})
t.Run("Shared admin", func(t *testing.T) {
_, err := testHandlerNamespaceUserAdmin.testReadAllWithLinkShare(nil, map[string]string{"namespace": "3"})
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeNeedToHaveNamespaceReadAccess)
})
})
t.Run("Create", func(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerNamespaceUserReadOnly.testCreateWithLinkShare(nil, nil, `{"user_id":"user1"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerNamespaceUserWrite.testCreateWithLinkShare(nil, nil, `{"user_id":"user1"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared admin", func(t *testing.T) {
_, err := testHandlerNamespaceUserAdmin.testCreateWithLinkShare(nil, nil, `{"user_id":"user1"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
})
t.Run("Update", func(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerNamespaceUserReadOnly.testUpdateWithLinkShare(nil, map[string]string{"namespace": "1"}, `{"user_id":"user1"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerNamespaceUserWrite.testUpdateWithLinkShare(nil, map[string]string{"namespace": "2"}, `{"user_id":"user1"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared admin", func(t *testing.T) {
_, err := testHandlerNamespaceUserAdmin.testUpdateWithLinkShare(nil, map[string]string{"namespace": "3"}, `{"user_id":"user1"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
})
t.Run("Delete", func(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerNamespaceUserReadOnly.testDeleteWithLinkShare(nil, map[string]string{"namespace": "1"})
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerNamespaceUserWrite.testDeleteWithLinkShare(nil, map[string]string{"namespace": "2"})
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared admin", func(t *testing.T) {
_, err := testHandlerNamespaceUserAdmin.testDeleteWithLinkShare(nil, map[string]string{"namespace": "3"})
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
})
})
t.Run("Teams", func(t *testing.T) {
testHandlerNamespaceTeamReadOnly := webHandlerTest{
linkShare: linkshareRead,
strFunc: func() handler.CObject {
return &models.TeamNamespace{}
},
t: t,
}
testHandlerNamespaceTeamWrite := webHandlerTest{
linkShare: linkShareWrite,
strFunc: func() handler.CObject {
return &models.TeamNamespace{}
},
t: t,
}
testHandlerNamespaceTeamAdmin := webHandlerTest{
linkShare: linkShareAdmin,
strFunc: func() handler.CObject {
return &models.TeamNamespace{}
},
t: t,
}
t.Run("ReadAll", func(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerNamespaceTeamReadOnly.testReadAllWithLinkShare(nil, map[string]string{"namespace": "1"})
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeNeedToHaveNamespaceReadAccess)
})
t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerNamespaceTeamWrite.testReadAllWithLinkShare(nil, map[string]string{"namespace": "2"})
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeNeedToHaveNamespaceReadAccess)
})
t.Run("Shared admin", func(t *testing.T) {
_, err := testHandlerNamespaceTeamAdmin.testReadAllWithLinkShare(nil, map[string]string{"namespace": "3"})
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeNeedToHaveNamespaceReadAccess)
})
})
t.Run("Create", func(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerNamespaceTeamReadOnly.testCreateWithLinkShare(nil, map[string]string{"namespace": "1"}, `{"team_id":1}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerNamespaceTeamWrite.testCreateWithLinkShare(nil, map[string]string{"namespace": "2"}, `{"team_id":1}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared admin", func(t *testing.T) {
_, err := testHandlerNamespaceTeamAdmin.testCreateWithLinkShare(nil, map[string]string{"namespace": "3"}, `{"team_id":1}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
})
t.Run("Update", func(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerNamespaceTeamReadOnly.testUpdateWithLinkShare(nil, map[string]string{"namespace": "1"}, `{"team_id":1}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerNamespaceTeamWrite.testUpdateWithLinkShare(nil, map[string]string{"namespace": "2"}, `{"team_id":1}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared admin", func(t *testing.T) {
_, err := testHandlerNamespaceTeamAdmin.testUpdateWithLinkShare(nil, map[string]string{"namespace": "3"}, `{"team_id":1}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
})
t.Run("Delete", func(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerNamespaceTeamReadOnly.testDeleteWithLinkShare(nil, map[string]string{"namespace": "1"})
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerNamespaceTeamWrite.testDeleteWithLinkShare(nil, map[string]string{"namespace": "2"})
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared admin", func(t *testing.T) {
_, err := testHandlerNamespaceTeamAdmin.testDeleteWithLinkShare(nil, map[string]string{"namespace": "3"})
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
})
})
})
})
}

View File

@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by

View File

@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@ -40,9 +40,10 @@ func TestProject(t *testing.T) {
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `Test1`)
assert.NotContains(t, rec.Body.String(), `Test2"`)
assert.Contains(t, rec.Body.String(), `Test3`) // Shared directly via users_project
assert.Contains(t, rec.Body.String(), `Test12`) // Shared via parent project
assert.Contains(t, rec.Body.String(), `Test3`) // Shared directly via users_project
assert.Contains(t, rec.Body.String(), `Test4`) // Shared via namespace
assert.NotContains(t, rec.Body.String(), `Test5`)
assert.NotContains(t, rec.Body.String(), `Test21`) // Archived through namespace
assert.NotContains(t, rec.Body.String(), `Test22`) // Archived directly
})
t.Run("Search", func(t *testing.T) {
@ -59,10 +60,10 @@ func TestProject(t *testing.T) {
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `Test1`)
assert.NotContains(t, rec.Body.String(), `Test2"`)
assert.Contains(t, rec.Body.String(), `Test3`) // Shared directly via users_project
assert.Contains(t, rec.Body.String(), `Test12`) // Shared via parent project
assert.Contains(t, rec.Body.String(), `Test3`) // Shared directly via users_project
assert.Contains(t, rec.Body.String(), `Test4`) // Shared via namespace
assert.NotContains(t, rec.Body.String(), `Test5`)
assert.Contains(t, rec.Body.String(), `Test21`) // Archived through project
assert.Contains(t, rec.Body.String(), `Test21`) // Archived through namespace
assert.Contains(t, rec.Body.String(), `Test22`) // Archived directly
})
})
@ -75,7 +76,7 @@ func TestProject(t *testing.T) {
assert.Contains(t, rec.Body.String(), `"owner":{"id":1,"name":"","username":"user1",`)
assert.NotContains(t, rec.Body.String(), `"owner":{"id":2,"name":"","username":"user2",`)
assert.NotContains(t, rec.Body.String(), `"tasks":`)
assert.Equal(t, "2", rec.Result().Header.Get("x-max-right")) // User 1 is owner, so they should have admin rights.
assert.Equal(t, "2", rec.Result().Header.Get("x-max-right")) // User 1 is owner so they should have admin rights.
})
t.Run("Nonexisting", func(t *testing.T) {
_, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "9999"})
@ -128,38 +129,38 @@ func TestProject(t *testing.T) {
assert.Equal(t, "2", rec.Result().Header.Get("x-max-right"))
})
t.Run("Shared Via Parent Project Team readonly", func(t *testing.T) {
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "12"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Test12"`)
assert.Equal(t, "0", rec.Result().Header.Get("x-max-right"))
})
t.Run("Shared Via Parent Project Team write", func(t *testing.T) {
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "13"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Test13"`)
assert.Equal(t, "1", rec.Result().Header.Get("x-max-right"))
})
t.Run("Shared Via Parent Project Team admin", func(t *testing.T) {
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "14"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Test14"`)
assert.Equal(t, "2", rec.Result().Header.Get("x-max-right"))
})
t.Run("Shared Via Parent Project User readonly", func(t *testing.T) {
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "15"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Test15"`)
assert.Equal(t, "0", rec.Result().Header.Get("x-max-right"))
})
t.Run("Shared Via Parent Project User write", func(t *testing.T) {
t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "16"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Test16"`)
assert.Equal(t, "1", rec.Result().Header.Get("x-max-right"))
})
t.Run("Shared Via Parent Project User admin", func(t *testing.T) {
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "17"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Test17"`)
@ -170,7 +171,7 @@ func TestProject(t *testing.T) {
t.Run("Update", func(t *testing.T) {
t.Run("Normal", func(t *testing.T) {
// Check the project was loaded successfully afterwards, see testReadOneWithUser
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "1"}, `{"title":"TestLoremIpsum"}`)
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "1"}, `{"title":"TestLoremIpsum","namespace_id":1}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
// The description should not be updated but returned correctly
@ -182,7 +183,7 @@ func TestProject(t *testing.T) {
assertHandlerErrorCode(t, err, models.ErrCodeProjectDoesNotExist)
})
t.Run("Normal with updating the description", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "1"}, `{"title":"TestLoremIpsum","description":"Lorem Ipsum dolor sit amet"}`)
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "1"}, `{"title":"TestLoremIpsum","description":"Lorem Ipsum dolor sit amet","namespace_id":1}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
assert.Contains(t, rec.Body.String(), `"description":"Lorem Ipsum dolor sit amet`)
@ -210,12 +211,12 @@ func TestProject(t *testing.T) {
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Team write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "7"}, `{"title":"TestLoremIpsum"}`)
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "7"}, `{"title":"TestLoremIpsum","namespace_id":6}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
})
t.Run("Shared Via Team admin", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "8"}, `{"title":"TestLoremIpsum"}`)
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "8"}, `{"title":"TestLoremIpsum","namespace_id":6}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
})
@ -226,44 +227,44 @@ func TestProject(t *testing.T) {
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via User write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "10"}, `{"title":"TestLoremIpsum"}`)
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "10"}, `{"title":"TestLoremIpsum","namespace_id":6}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
})
t.Run("Shared Via User admin", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "11"}, `{"title":"TestLoremIpsum"}`)
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "11"}, `{"title":"TestLoremIpsum","namespace_id":6}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
})
t.Run("Shared Via Parent Project Team readonly", func(t *testing.T) {
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "12"}, `{"title":"TestLoremIpsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project Team write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "13"}, `{"title":"TestLoremIpsum"}`)
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "13"}, `{"title":"TestLoremIpsum","namespace_id":8}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
})
t.Run("Shared Via Parent Project Team admin", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "14"}, `{"title":"TestLoremIpsum"}`)
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "14"}, `{"title":"TestLoremIpsum","namespace_id":9}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
})
t.Run("Shared Via Parent Project User readonly", func(t *testing.T) {
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "15"}, `{"title":"TestLoremIpsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project User write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "16"}, `{"title":"TestLoremIpsum"}`)
t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "16"}, `{"title":"TestLoremIpsum","namespace_id":11}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
})
t.Run("Shared Via Parent Project User admin", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "17"}, `{"title":"TestLoremIpsum"}`)
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "17"}, `{"title":"TestLoremIpsum","namespace_id":12}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
})
@ -319,33 +320,33 @@ func TestProject(t *testing.T) {
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
})
t.Run("Shared Via Parent Project Team readonly", func(t *testing.T) {
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "12"})
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project Team write", func(t *testing.T) {
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "13"})
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project Team admin", func(t *testing.T) {
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "14"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
})
t.Run("Shared Via Parent Project User readonly", func(t *testing.T) {
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "15"})
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project User write", func(t *testing.T) {
t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "16"})
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project User admin", func(t *testing.T) {
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "17"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
@ -355,7 +356,7 @@ func TestProject(t *testing.T) {
t.Run("Create", func(t *testing.T) {
t.Run("Normal", func(t *testing.T) {
// Check the project was loaded successfully after update, see testReadOneWithUser
rec, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem"}`)
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"namespace": "1"}, `{"title":"Lorem"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem"`)
assert.Contains(t, rec.Body.String(), `"description":""`)
@ -363,50 +364,52 @@ func TestProject(t *testing.T) {
assert.NotContains(t, rec.Body.String(), `"tasks":`)
})
t.Run("Normal with description", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem","description":"Lorem Ipsum"}`)
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"namespace": "1"}, `{"title":"Lorem","description":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem"`)
assert.Contains(t, rec.Body.String(), `"description":"Lorem Ipsum"`)
assert.Contains(t, rec.Body.String(), `"owner":{"id":1`)
assert.NotContains(t, rec.Body.String(), `"tasks":`)
})
t.Run("Nonexisting parent project", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem","parent_project_id":99999}`)
t.Run("Nonexisting Namespace", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"namespace": "999999"}, `{"title":"Lorem"}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeProjectDoesNotExist)
assertHandlerErrorCode(t, err, models.ErrCodeNamespaceDoesNotExist)
})
t.Run("Empty title", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, nil, `{"title":""}`)
_, err := testHandler.testCreateWithUser(nil, map[string]string{"namespace": "1"}, `{"title":""}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message.(models.ValidationHTTPError).InvalidFields, "title: non zero value required")
})
t.Run("Title too long", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea taki"}`)
_, err := testHandler.testCreateWithUser(nil, map[string]string{"namespace": "1"}, `{"title":"Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea taki"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message.(models.ValidationHTTPError).InvalidFields[0], "does not validate as runelength(1|250)")
})
t.Run("Rights check", func(t *testing.T) {
t.Run("Forbidden", func(t *testing.T) {
// Owned by user13
_, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem","parent_project_id":20}`)
_, err := testHandler.testCreateWithUser(nil, map[string]string{"namespace": "15"}, `{"title":"Lorem"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project Team readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem","parent_project_id":32}`)
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"namespace": "7"}, `{"title":"Lorem"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project Team write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem","parent_project_id":33}`)
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"namespace": "8"}, `{"title":"Lorem"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem"`)
assert.Contains(t, rec.Body.String(), `"description":""`)
assert.Contains(t, rec.Body.String(), `"owner":{"id":1`)
assert.NotContains(t, rec.Body.String(), `"tasks":`)
})
t.Run("Shared Via Parent Project Team admin", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem","parent_project_id":34}`)
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"namespace": "9"}, `{"title":"Lorem"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem"`)
assert.Contains(t, rec.Body.String(), `"description":""`)
@ -414,21 +417,21 @@ func TestProject(t *testing.T) {
assert.NotContains(t, rec.Body.String(), `"tasks":`)
})
t.Run("Shared Via Parent Project User readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem","parent_project_id":9}`)
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"namespace": "10"}, `{"title":"Lorem"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project User write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem","parent_project_id":10}`)
t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"namespace": "11"}, `{"title":"Lorem"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem"`)
assert.Contains(t, rec.Body.String(), `"description":""`)
assert.Contains(t, rec.Body.String(), `"owner":{"id":1`)
assert.NotContains(t, rec.Body.String(), `"tasks":`)
})
t.Run("Shared Via Parent Project User admin", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"namespace": "12"}, `{"title":"Lorem","parent_project_id":11}`)
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"namespace": "12"}, `{"title":"Lorem"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem"`)
assert.Contains(t, rec.Body.String(), `"description":""`)

View File

@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@ -29,7 +29,7 @@ func TestRegister(t *testing.T) {
t.Run("normal register", func(t *testing.T) {
rec, err := newTestRequest(t, http.MethodPost, apiv1.RegisterUser, `{
"username": "newUser",
"password": "12345678",
"password": "1234",
"email": "email@example.com"
}`, nil, nil)
assert.NoError(t, err)
@ -43,7 +43,7 @@ func TestRegister(t *testing.T) {
t.Run("Empty username", func(t *testing.T) {
_, err := newTestRequest(t, http.MethodPost, apiv1.RegisterUser, `{
"username": "",
"password": "12345678",
"password": "1234",
"email": "email@example.com"
}`, nil, nil)
assert.Error(t, err)
@ -61,7 +61,7 @@ func TestRegister(t *testing.T) {
t.Run("Empty email", func(t *testing.T) {
_, err := newTestRequest(t, http.MethodPost, apiv1.RegisterUser, `{
"username": "newUser",
"password": "12345678",
"password": "1234",
"email": ""
}`, nil, nil)
assert.Error(t, err)
@ -70,7 +70,7 @@ func TestRegister(t *testing.T) {
t.Run("Already existing username", func(t *testing.T) {
_, err := newTestRequest(t, http.MethodPost, apiv1.RegisterUser, `{
"username": "user1",
"password": "12345678",
"password": "1234",
"email": "email@example.com"
}`, nil, nil)
assert.Error(t, err)
@ -79,7 +79,7 @@ func TestRegister(t *testing.T) {
t.Run("Already existing email", func(t *testing.T) {
_, err := newTestRequest(t, http.MethodPost, apiv1.RegisterUser, `{
"username": "newUser",
"password": "12345678",
"password": "1234",
"email": "user1@example.com"
}`, nil, nil)
assert.Error(t, err)

View File

@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@ -113,49 +113,49 @@ func TestTaskCollection(t *testing.T) {
t.Run("by priority", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}}, urlParams)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`)
assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`)
})
t.Run("by priority desc", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"desc"}}, urlParams)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":3,"title":"task #3 high prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":4,"title":"task #4 low prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":1`)
assert.Contains(t, rec.Body.String(), `[{"id":3,"title":"task #3 high prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":4,"title":"task #4 low prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":1`)
})
t.Run("by priority asc", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"asc"}}, urlParams)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`)
assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`)
})
// should equal duedate asc
t.Run("by due_date", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}}, urlParams)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
})
t.Run("by duedate desc", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"desc"}}, urlParams)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":6,"title":"task #6 lower due date`)
assert.Contains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":6,"title":"task #6 lower due date`)
})
// Due date without unix suffix
t.Run("by duedate asc without suffix", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"asc"}}, urlParams)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
})
t.Run("by due_date without suffix", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}}, urlParams)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
})
t.Run("by duedate desc without suffix", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"desc"}}, urlParams)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":6,"title":"task #6 lower due date`)
assert.Contains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":6,"title":"task #6 lower due date`)
})
t.Run("by duedate asc", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"asc"}}, urlParams)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
})
t.Run("invalid sort parameter", func(t *testing.T) {
_, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"loremipsum"}}, urlParams)
@ -171,10 +171,10 @@ func TestTaskCollection(t *testing.T) {
// Invalid parameter should not sort at all
rec, err := testHandler.testReadAllWithUser(url.Values{"sort": []string{"loremipsum"}}, urlParams)
assert.NoError(t, err)
assert.NotContains(t, rec.Body.String(), `[{"id":3,"title":"task #3 high prio","description":"","done":false,"due_date":0,"reminders":null,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":0,"end_date":0,"assignees":null,"labels":null,"hex_color":"","created":1543626724,"updated":1543626724,"created_by":{"id":0,"name":"","username":"","email":"","created":0,"updated":0}},{"id":4,"title":"task #4 low prio","description":"","done":false,"due_date":0,"repeat_after":0,"repeat_mode":0,"priority":1`)
assert.NotContains(t, rec.Body.String(), `{"id":4,"title":"task #4 low prio","description":"","done":false,"due_date":0,"reminders":null,"repeat_after":0,"repeat_mode":0,"priority":1,"start_date":0,"end_date":0,"assignees":null,"labels":null,"hex_color":"","created":1543626724,"updated":1543626724,"created_by":{"id":0,"name":"","username":"","email":"","created":0,"updated":0}},{"id":3,"title":"task #3 high prio","description":"","done":false,"due_date":0,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":0,"end_date":0,"assignees":null,"labels":null,"created":1543626724,"updated":1543626724,"created_by":{"id":0,"name":"","username":"","email":"","created":0,"updated":0}}]`)
assert.NotContains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"due_date":1543636724,"reminders":null,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":0,"end_date":0,"assignees":null,"labels":null,"hex_color":"","created":1543626724,"updated":1543626724,"created_by":{"id":0,"name":"","username":"","email":"","created":0,"updated":0}},{"id":6,"title":"task #6 lower due date"`)
assert.NotContains(t, rec.Body.String(), `{"id":6,"title":"task #6 lower due date","description":"","done":false,"due_date":1543616724,"reminders":null,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":0,"end_date":0,"assignees":null,"labels":null,"hex_color":"","created":1543626724,"updated":1543626724,"created_by":{"id":0,"name":"","username":"","email":"","created":0,"updated":0}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"due_date":1543636724,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":0,"end_date":0,"assignees":null,"labels":null,"created":1543626724,"updated":1543626724,"created_by":{"id":0,"name":"","username":"","email":"","created":0,"updated":0}}]`)
assert.NotContains(t, rec.Body.String(), `[{"id":3,"title":"task #3 high prio","description":"","done":false,"due_date":0,"reminder_dates":null,"reminders":null,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":0,"end_date":0,"assignees":null,"labels":null,"hex_color":"","created":1543626724,"updated":1543626724,"created_by":{"id":0,"name":"","username":"","email":"","created":0,"updated":0}},{"id":4,"title":"task #4 low prio","description":"","done":false,"due_date":0,"reminder_dates":null,"repeat_after":0,"repeat_mode":0,"priority":1`)
assert.NotContains(t, rec.Body.String(), `{"id":4,"title":"task #4 low prio","description":"","done":false,"due_date":0,"reminder_dates":null,"reminders":null,"repeat_after":0,"repeat_mode":0,"priority":1,"start_date":0,"end_date":0,"assignees":null,"labels":null,"hex_color":"","created":1543626724,"updated":1543626724,"created_by":{"id":0,"name":"","username":"","email":"","created":0,"updated":0}},{"id":3,"title":"task #3 high prio","description":"","done":false,"due_date":0,"reminder_dates":null,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":0,"end_date":0,"assignees":null,"labels":null,"created":1543626724,"updated":1543626724,"created_by":{"id":0,"name":"","username":"","email":"","created":0,"updated":0}}]`)
assert.NotContains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"due_date":1543636724,"reminder_dates":null,"reminders":null,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":0,"end_date":0,"assignees":null,"labels":null,"hex_color":"","created":1543626724,"updated":1543626724,"created_by":{"id":0,"name":"","username":"","email":"","created":0,"updated":0}},{"id":6,"title":"task #6 lower due date"`)
assert.NotContains(t, rec.Body.String(), `{"id":6,"title":"task #6 lower due date","description":"","done":false,"due_date":1543616724,"reminder_dates":null,"reminders":null,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":0,"end_date":0,"assignees":null,"labels":null,"hex_color":"","created":1543626724,"updated":1543626724,"created_by":{"id":0,"name":"","username":"","email":"","created":0,"updated":0}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"due_date":1543636724,"reminder_dates":null,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":0,"end_date":0,"assignees":null,"labels":null,"created":1543626724,"updated":1543626724,"created_by":{"id":0,"name":"","username":"","email":"","created":0,"updated":0}}]`)
})
})
t.Run("Filter", func(t *testing.T) {
@ -366,42 +366,42 @@ func TestTaskCollection(t *testing.T) {
t.Run("by priority", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}}, nil)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":35,"title":"task #35","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":21,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":[{"id":2,"name":"","username":"user2","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}],"labels":[{"id":4,"title":"Label #4 - visible via other task","description":"","hex_color":"","created_by":{"id":2,"name":"","username":"user2","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"},"created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}],"hex_color":"","percent_done":0,"identifier":"test21-1","index":1,"related_tasks":{"related":[{"id":1,"title":"task #1","description":"Lorem Ipsum","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"","index":1,"related_tasks":null,"attachments":null,"cover_image_attachment_id":0,"is_favorite":true,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":2,"kanban_position":0,"created_by":null},{"id":1,"title":"task #1","description":"Lorem Ipsum","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"","index":1,"related_tasks":null,"attachments":null,"cover_image_attachment_id":0,"is_favorite":true,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":2,"kanban_position":0,"created_by":null}]},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":19,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`)
assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`)
})
t.Run("by priority desc", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"desc"}}, nil)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":3,"title":"task #3 high prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":4,"title":"task #4 low prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":1`)
assert.Contains(t, rec.Body.String(), `[{"id":3,"title":"task #3 high prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":4,"title":"task #4 low prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":1`)
})
t.Run("by priority asc", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"asc"}}, nil)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":35,"title":"task #35","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":21,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":[{"id":2,"name":"","username":"user2","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}],"labels":[{"id":4,"title":"Label #4 - visible via other task","description":"","hex_color":"","created_by":{"id":2,"name":"","username":"user2","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"},"created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}],"hex_color":"","percent_done":0,"identifier":"test21-1","index":1,"related_tasks":{"related":[{"id":1,"title":"task #1","description":"Lorem Ipsum","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"","index":1,"related_tasks":null,"attachments":null,"cover_image_attachment_id":0,"is_favorite":true,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":2,"kanban_position":0,"created_by":null},{"id":1,"title":"task #1","description":"Lorem Ipsum","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"","index":1,"related_tasks":null,"attachments":null,"cover_image_attachment_id":0,"is_favorite":true,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":2,"kanban_position":0,"created_by":null}]},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":19,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`)
assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`)
})
// should equal duedate asc
t.Run("by due_date", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}}, nil)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
})
t.Run("by duedate desc", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"desc"}}, nil)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":6,"title":"task #6 lower due date`)
assert.Contains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":6,"title":"task #6 lower due date`)
})
t.Run("by duedate asc", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"asc"}}, nil)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
})
t.Run("invalid parameter", func(t *testing.T) {
// Invalid parameter should not sort at all
rec, err := testHandler.testReadAllWithUser(url.Values{"sort": []string{"loremipsum"}}, nil)
assert.NoError(t, err)
assert.NotContains(t, rec.Body.String(), `[{"id":3,"title":"task #3 high prio","description":"","done":false,"due_date":0,"reminders":null,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":0,"end_date":0,"assignees":null,"labels":null,"hex_color":"","created":1543626724,"updated":1543626724,"created_by":{"id":0,"name":"","username":"","email":"","created":0,"updated":0}},{"id":4,"title":"task #4 low prio","description":"","done":false,"due_date":0,"repeat_after":0,"repeat_mode":0,"priority":1`)
assert.NotContains(t, rec.Body.String(), `{"id":4,"title":"task #4 low prio","description":"","done":false,"due_date":0,"reminders":null,"repeat_after":0,"repeat_mode":0,"priority":1,"start_date":0,"end_date":0,"assignees":null,"labels":null,"hex_color":"","created":1543626724,"updated":1543626724,"created_by":{"id":0,"name":"","username":"","email":"","created":0,"updated":0}},{"id":3,"title":"task #3 high prio","description":"","done":false,"due_date":0,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":0,"end_date":0,"assignees":null,"labels":null,"created":1543626724,"updated":1543626724,"created_by":{"id":0,"name":"","username":"","email":"","created":0,"updated":0}}]`)
assert.NotContains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"due_date":1543636724,"reminders":null,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":0,"end_date":0,"assignees":null,"labels":null,"hex_color":"","created":1543626724,"updated":1543626724,"created_by":{"id":0,"name":"","username":"","email":"","created":0,"updated":0}},{"id":6,"title":"task #6 lower due date"`)
assert.NotContains(t, rec.Body.String(), `{"id":6,"title":"task #6 lower due date","description":"","done":false,"due_date":1543616724,"reminders":null,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":0,"end_date":0,"assignees":null,"labels":null,"hex_color":"","created":1543626724,"updated":1543626724,"created_by":{"id":0,"name":"","username":"","email":"","created":0,"updated":0}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"due_date":1543636724,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":0,"end_date":0,"assignees":null,"labels":null,"created":1543626724,"updated":1543626724,"created_by":{"id":0,"name":"","username":"","email":"","created":0,"updated":0}}]`)
assert.NotContains(t, rec.Body.String(), `[{"id":3,"title":"task #3 high prio","description":"","done":false,"due_date":0,"reminder_dates":null,"reminders":null,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":0,"end_date":0,"assignees":null,"labels":null,"hex_color":"","created":1543626724,"updated":1543626724,"created_by":{"id":0,"name":"","username":"","email":"","created":0,"updated":0}},{"id":4,"title":"task #4 low prio","description":"","done":false,"due_date":0,"reminder_dates":null,"repeat_after":0,"repeat_mode":0,"priority":1`)
assert.NotContains(t, rec.Body.String(), `{"id":4,"title":"task #4 low prio","description":"","done":false,"due_date":0,"reminder_dates":null,"reminders":null,"repeat_after":0,"repeat_mode":0,"priority":1,"start_date":0,"end_date":0,"assignees":null,"labels":null,"hex_color":"","created":1543626724,"updated":1543626724,"created_by":{"id":0,"name":"","username":"","email":"","created":0,"updated":0}},{"id":3,"title":"task #3 high prio","description":"","done":false,"due_date":0,"reminder_dates":null,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":0,"end_date":0,"assignees":null,"labels":null,"created":1543626724,"updated":1543626724,"created_by":{"id":0,"name":"","username":"","email":"","created":0,"updated":0}}]`)
assert.NotContains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"due_date":1543636724,"reminder_dates":null,"reminders":null,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":0,"end_date":0,"assignees":null,"labels":null,"hex_color":"","created":1543626724,"updated":1543626724,"created_by":{"id":0,"name":"","username":"","email":"","created":0,"updated":0}},{"id":6,"title":"task #6 lower due date"`)
assert.NotContains(t, rec.Body.String(), `{"id":6,"title":"task #6 lower due date","description":"","done":false,"due_date":1543616724,"reminder_dates":null,"reminders":null,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":0,"end_date":0,"assignees":null,"labels":null,"hex_color":"","created":1543626724,"updated":1543626724,"created_by":{"id":0,"name":"","username":"","email":"","created":0,"updated":0}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"due_date":1543636724,"reminder_dates":null,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":0,"end_date":0,"assignees":null,"labels":null,"created":1543626724,"updated":1543626724,"created_by":{"id":0,"name":"","username":"","email":"","created":0,"updated":0}}]`)
})
})
t.Run("Filter", func(t *testing.T) {

View File

@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@ -101,33 +101,33 @@ func TestTaskComments(t *testing.T) {
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project Team readonly", func(t *testing.T) {
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "21", "commentid": "9"}, `{"comment":"Lorem Ipsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project Team write", func(t *testing.T) {
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "22", "commentid": "10"}, `{"comment":"Lorem Ipsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project Team admin", func(t *testing.T) {
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "23", "commentid": "11"}, `{"comment":"Lorem Ipsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project User readonly", func(t *testing.T) {
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "24", "commentid": "12"}, `{"comment":"Lorem Ipsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project User write", func(t *testing.T) {
t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "25", "commentid": "13"}, `{"comment":"Lorem Ipsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project User admin", func(t *testing.T) {
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "26", "commentid": "14"}, `{"comment":"Lorem Ipsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
@ -184,33 +184,33 @@ func TestTaskComments(t *testing.T) {
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project Team readonly", func(t *testing.T) {
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "21", "commentid": "9"})
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project Team write", func(t *testing.T) {
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "22", "commentid": "10"})
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project Team admin", func(t *testing.T) {
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "23", "commentid": "11"})
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project User readonly", func(t *testing.T) {
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "24", "commentid": "12"})
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project User write", func(t *testing.T) {
t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "25", "commentid": "13"})
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project User admin", func(t *testing.T) {
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "26", "commentid": "14"})
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
@ -267,33 +267,33 @@ func TestTaskComments(t *testing.T) {
assert.Contains(t, rec.Body.String(), `"comment":"Lorem Ipsum"`)
})
t.Run("Shared Via Parent Project Team readonly", func(t *testing.T) {
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "21"}, `{"comment":"Lorem Ipsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project Team write", func(t *testing.T) {
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "22"}, `{"comment":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"comment":"Lorem Ipsum"`)
})
t.Run("Shared Via Parent Project Team admin", func(t *testing.T) {
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "23"}, `{"comment":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"comment":"Lorem Ipsum"`)
})
t.Run("Shared Via Parent Project User readonly", func(t *testing.T) {
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "24"}, `{"comment":"Lorem Ipsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project User write", func(t *testing.T) {
t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "25"}, `{"comment":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"comment":"Lorem Ipsum"`)
})
t.Run("Shared Via Parent Project User admin", func(t *testing.T) {
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "26"}, `{"comment":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"comment":"Lorem Ipsum"`)

View File

@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@ -95,6 +95,27 @@ func TestTask(t *testing.T) {
assert.Contains(t, rec.Body.String(), `"due_date":"0001-01-01T00:00:00Z"`)
assert.NotContains(t, rec.Body.String(), `"due_date":"2020-02-10T10:00:00Z"`)
})
// Deprecated: Remove if ReminderDates is removed
t.Run("ReminderDates", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"reminder_dates": ["2020-02-10T10:00:00Z","2020-02-11T10:00:00Z"]}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"reminder_dates":["2020-02-10T10:00:00Z","2020-02-11T10:00:00Z"]`)
assert.NotContains(t, rec.Body.String(), `"reminder_dates": null`)
})
// Deprecated: Remove if ReminderDates is removed
t.Run("ReminderDates unset to empty array", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "27"}, `{"reminder_dates": []}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"reminder_dates":null`)
assert.NotContains(t, rec.Body.String(), `"reminder_dates":[1543626724,1543626824]`)
})
// Deprecated: Remove if ReminderDates is removed
t.Run("ReminderDates unset to null", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "27"}, `{"reminder_dates": null}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"reminder_dates":null`)
assert.NotContains(t, rec.Body.String(), `"reminder_dates":[1543626724,1543626824]`)
})
t.Run("Reminders", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"reminders": [{"reminder": "2020-02-10T10:00:00Z"},{"reminder": "2020-02-11T10:00:00Z"}]}`)
assert.NoError(t, err)
@ -112,7 +133,7 @@ func TestTask(t *testing.T) {
t.Run("Reminders unset to null", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "27"}, `{"reminders": null}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"reminders":null`)
assert.Contains(t, rec.Body.String(), `"reminder_dates":null`)
assert.NotContains(t, rec.Body.String(), `{"Reminder":"2020-02-10T10:00:00Z"`)
})
t.Run("Repeat after", func(t *testing.T) {
@ -256,33 +277,33 @@ func TestTask(t *testing.T) {
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
t.Run("Shared Via Parent Project Team readonly", func(t *testing.T) {
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "21"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project Team write", func(t *testing.T) {
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "22"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
t.Run("Shared Via Parent Project Team admin", func(t *testing.T) {
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "23"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
t.Run("Shared Via Parent Project User readonly", func(t *testing.T) {
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "24"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project User write", func(t *testing.T) {
t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "25"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
t.Run("Shared Via Parent Project User admin", func(t *testing.T) {
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "26"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
@ -374,33 +395,33 @@ func TestTask(t *testing.T) {
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
})
t.Run("Shared Via Parent Project Team readonly", func(t *testing.T) {
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "21"})
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project Team write", func(t *testing.T) {
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "22"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
})
t.Run("Shared Via Parent Project Team admin", func(t *testing.T) {
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "23"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
})
t.Run("Shared Via Parent Project User readonly", func(t *testing.T) {
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "24"})
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project User write", func(t *testing.T) {
t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "25"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
})
t.Run("Shared Via Parent Project User admin", func(t *testing.T) {
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "26"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
@ -457,33 +478,33 @@ func TestTask(t *testing.T) {
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
t.Run("Shared Via Parent Project Team readonly", func(t *testing.T) {
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "12"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project Team write", func(t *testing.T) {
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "13"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
t.Run("Shared Via Parent Project Team admin", func(t *testing.T) {
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "14"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
t.Run("Shared Via Parent Project User readonly", func(t *testing.T) {
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "15"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project User write", func(t *testing.T) {
t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "16"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
t.Run("Shared Via Parent Project User admin", func(t *testing.T) {
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "17"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)

View File

@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by

Some files were not shown because too many files have changed in this diff Show More