feat(caldav): Add support for subtasks in CalDAV. #4

Open
zewaren wants to merge 424 commits from feature/caldav-subtasks into main
461 changed files with 13719 additions and 12293 deletions

View File

@ -39,7 +39,7 @@ volumes:
services: services:
- name: test-mysql-unit - name: test-mysql-unit
image: mariadb:10 image: mariadb:11
environment: environment:
MYSQL_ROOT_PASSWORD: vikunjatest MYSQL_ROOT_PASSWORD: vikunjatest
MYSQL_DATABASE: vikunjatest MYSQL_DATABASE: vikunjatest
@ -47,7 +47,7 @@ services:
- name: tmp-mysql-unit - name: tmp-mysql-unit
path: /var/lib/mysql path: /var/lib/mysql
- name: test-mysql-integration - name: test-mysql-integration
image: mariadb:10 image: mariadb:11
environment: environment:
MYSQL_ROOT_PASSWORD: vikunjatest MYSQL_ROOT_PASSWORD: vikunjatest
MYSQL_DATABASE: vikunjatest MYSQL_DATABASE: vikunjatest
@ -55,7 +55,7 @@ services:
- name: tmp-mysql-integration - name: tmp-mysql-integration
path: /var/lib/mysql path: /var/lib/mysql
- name: test-mysql-migration - name: test-mysql-migration
image: mariadb:10 image: mariadb:11
environment: environment:
MYSQL_ROOT_PASSWORD: vikunjatest MYSQL_ROOT_PASSWORD: vikunjatest
MYSQL_DATABASE: vikunjatest MYSQL_DATABASE: vikunjatest
@ -63,7 +63,7 @@ services:
- name: tmp-mysql-migration - name: tmp-mysql-migration
path: /var/lib/mysql path: /var/lib/mysql
- name: test-postgres-unit - name: test-postgres-unit
image: postgres:14 image: postgres:16
environment: environment:
POSTGRES_PASSWORD: vikunjatest POSTGRES_PASSWORD: vikunjatest
POSTGRES_DB: vikunjatest POSTGRES_DB: vikunjatest
@ -73,7 +73,7 @@ services:
commands: commands:
- docker-entrypoint.sh -c fsync=off -c full_page_writes=off # turns of wal - docker-entrypoint.sh -c fsync=off -c full_page_writes=off # turns of wal
- name: test-postgres-integration - name: test-postgres-integration
image: postgres:14 image: postgres:16
environment: environment:
POSTGRES_PASSWORD: vikunjatest POSTGRES_PASSWORD: vikunjatest
POSTGRES_DB: vikunjatest POSTGRES_DB: vikunjatest
@ -83,7 +83,7 @@ services:
commands: commands:
- docker-entrypoint.sh -c fsync=off -c full_page_writes=off # turns of wal - docker-entrypoint.sh -c fsync=off -c full_page_writes=off # turns of wal
- name: test-postgres-migration - name: test-postgres-migration
image: postgres:14 image: postgres:16
environment: environment:
POSTGRES_PASSWORD: vikunjatest POSTGRES_PASSWORD: vikunjatest
POSTGRES_DB: vikunjatest POSTGRES_DB: vikunjatest
@ -133,16 +133,14 @@ steps:
event: [ push, tag, pull_request ] event: [ push, tag, pull_request ]
- name: lint - name: lint
image: golang:1.19-alpine image: golangci/golangci-lint:v1.54.2
pull: always pull: always
environment: environment:
GOPROXY: 'https://goproxy.kolaente.de' GOPROXY: 'https://goproxy.kolaente.de'
depends_on: [ build ] depends_on: [ build ]
commands: commands:
- export "GOROOT=$(go env GOROOT)" - export "GOROOT=$(go env GOROOT)"
- apk --no-cache add build-base git - ./mage-static check:golangci
- wget -O - -q https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.51.2
- ./mage-static check:all
when: when:
event: [ push, tag, pull_request ] event: [ push, tag, pull_request ]
@ -338,6 +336,50 @@ steps:
when: when:
event: [ push, tag, pull_request ] 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 # Build a release when tagging
@ -394,6 +436,7 @@ steps:
# This path does not exist. However, when we set the gopath to /go, the build fails. Not sure why. # 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. # Leaving this here until we know how to resolve this properly.
GOPATH: /srv/app GOPATH: /srv/app
GOPROXY: https://goproxy.kolaente.de
commands: commands:
- export PATH=$PATH:$GOPATH/bin - export PATH=$PATH:$GOPATH/bin
- go install github.com/magefile/mage - go install github.com/magefile/mage
@ -407,6 +450,7 @@ steps:
# This path does not exist. However, when we set the gopath to /go, the build fails. Not sure why. # 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. # Leaving this here until we know how to resolve this properly.
GOPATH: /srv/app GOPATH: /srv/app
GOPROXY: https://goproxy.kolaente.de
commands: commands:
- export PATH=$PATH:$GOPATH/bin - export PATH=$PATH:$GOPATH/bin
- go install github.com/magefile/mage - go install github.com/magefile/mage
@ -420,6 +464,7 @@ steps:
# This path does not exist. However, when we set the gopath to /go, the build fails. Not sure why. # 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. # Leaving this here until we know how to resolve this properly.
GOPATH: /srv/app GOPATH: /srv/app
GOPROXY: https://goproxy.kolaente.de
commands: commands:
- export PATH=$PATH:$GOPATH/bin - export PATH=$PATH:$GOPATH/bin
- go install github.com/magefile/mage - go install github.com/magefile/mage
@ -506,7 +551,7 @@ steps:
# Build os packages and push it to our bucket # Build os packages and push it to our bucket
- name: build-os-packages-unstable - name: build-os-packages-unstable
image: goreleaser/nfpm:v2.27.1 image: goreleaser/nfpm:v2.33.1
pull: always pull: always
commands: commands:
- apk add git go - apk add git go
@ -522,7 +567,7 @@ steps:
depends_on: [ after-build-compress ] depends_on: [ after-build-compress ]
- name: build-os-packages-version - name: build-os-packages-version
image: goreleaser/nfpm:v2.27.1 image: goreleaser/nfpm:v2.33.1
pull: always pull: always
commands: commands:
- apk add git go - apk add git go
@ -607,7 +652,7 @@ steps:
- tar -xzf vikunja-theme.tar.gz - tar -xzf vikunja-theme.tar.gz
- name: build - name: build
image: klakegg/hugo:0.107.0 image: klakegg/hugo:0.111.3
pull: always pull: always
commands: commands:
- cd docs - cd docs
@ -731,6 +776,6 @@ steps:
- failure - failure
--- ---
kind: signature kind: signature
hmac: f1743e2bb4ca8e5d8df1e222720feb4eac91e15a5ef74d4b54f1df246f2353fe hmac: 3ad78b828f36d4473527b8c6ee0985a5bf6f290fe73b3f7381a41d8c4937ffaa
... ...

23
.github/workflows/lockdown.yml vendored Normal file
View File

@ -0,0 +1,23 @@
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.'

1
.gitignore vendored
View File

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

View File

@ -80,6 +80,7 @@ issues:
linters: linters:
- goheader - goheader
- misspell - misspell
- gosmopolitan
- text: "Missed string" - text: "Missed string"
linters: linters:
- goheader - goheader
@ -89,6 +90,19 @@ issues:
- path: pkg/models/favorites\.go - path: pkg/models/favorites\.go
linters: linters:
- nilerr - nilerr
- path: pkg/models/project\.go
text: "string `parent_project_id` has 3 occurrences, make it a constant"
- path: pkg/models/events\.go - path: pkg/models/events\.go
linters: linters:
- musttag - 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,6 +7,275 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
All releases can be found on https://code.vikunja.io/api/releases. 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 ## [0.20.4] - 2023-03-12
### Bug Fixes ### Bug Fixes
@ -165,7 +434,7 @@ All releases can be found on https://code.vikunja.io/api/releases.
* *(background)* Add Last-Modified header (#1376) * *(background)* Add Last-Modified header (#1376)
* *(caldav)* Add support for repeating tasks * *(caldav)* Add support for repeating tasks
* *(caldav)* Export Labels to Caldav (#1409) * *(caldav)* Export Labels to CalDAV (#1409)
* *(caldav)* Import caldav categories as Labels (#1413) * *(caldav)* Import caldav categories as Labels (#1413)
* *(migrators)* Remove wunderlist (#1346) * *(migrators)* Remove wunderlist (#1346)
* *(release)* Use compressed binaries for package releases * *(release)* Use compressed binaries for package releases
@ -1427,7 +1696,7 @@ All releases can be found on https://code.vikunja.io/api/releases.
* Add option to disable totp for everyone * Add option to disable totp for everyone
* Add plausible to docs * Add plausible to docs
* Add restarting commands to all example docker compose files * Add restarting commands to all example docker compose files
* Add seperate docker pipeline for amd64 and arm * Add separate docker pipeline for amd64 and arm
* Add test mail command (#571) * Add test mail command (#571)
* Add todoist migrator to available migrators in info endpoint if it is enabled * Add todoist migrator to available migrators in info endpoint if it is enabled
* Add unsplash image proxy for images and thumbnails * Add unsplash image proxy for images and thumbnails
@ -1620,7 +1889,7 @@ All releases can be found on https://code.vikunja.io/api/releases.
* Update Renovate Configuration (#161) * Update Renovate Configuration (#161)
* Update src.techknowlogick.com/xgo commit hash to bb0faa3 (#279) * Update src.techknowlogick.com/xgo commit hash to bb0faa3 (#279)
* Update src.techknowlogick.com/xgo commit hash to c43d4c4 (#224) * Update src.techknowlogick.com/xgo commit hash to c43d4c4 (#224)
* Update xorm redis cacher to use the xorm logger instead of a special seperate one * Update xorm redis cacher to use the xorm logger instead of a special separate one
* Update xorm to v1 (#323) * Update xorm to v1 (#323)
## [0.12] - 2020-04-04 ## [0.12] - 2020-04-04
@ -1761,7 +2030,7 @@ All releases can be found on https://code.vikunja.io/api/releases.
### Added ### Added
* Better Caldav support (#73) * Better CalDAV support (#73)
* Added settings for max open/idle connections and max connection lifetime (#74) * Added settings for max open/idle connections and max connection lifetime (#74)
* /info endpoint (#85) * /info endpoint (#85)
* Added http endpoint to list all users on a list (#87) * Added http endpoint to list all users on a list (#87)
@ -1906,4 +2175,3 @@ Misc bugfixes and improvements to the build process
## [0.2] - 2018-10-17 ## [0.2] - 2018-10-17
## [0.1] - 2018-09-20 ## [0.1] - 2018-09-20

3
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,3 @@
# 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.20.0 AS builder FROM --platform=$BUILDPLATFORM techknowlogick/xgo:go-1.21.x AS builder
RUN go install github.com/magefile/mage@latest && \ RUN go install github.com/magefile/mage@latest && \
mv /go/bin/mage /usr/local/go/bin mv /go/bin/mage /usr/local/go/bin
@ -13,7 +13,9 @@ COPY . ./
ARG TARGETOS TARGETARCH TARGETVARIANT ARG TARGETOS TARGETARCH TARGETVARIANT
RUN mage build:clean && \ ENV GOPROXY https://goproxy.kolaente.de
RUN export PATH=$PATH:$GOPATH/bin && \
mage build:clean && \
mage release:xgo "${TARGETOS}/${TARGETARCH}/${TARGETVARIANT}" mage release:xgo "${TARGETOS}/${TARGETARCH}/${TARGETVARIANT}"
# ┬─┐┬ ┐┌┐┐┌┐┐┬─┐┬─┐ # ┬─┐┬ ┐┌┐┐┌┐┐┬─┐┬─┐
@ -23,7 +25,7 @@ RUN mage build:clean && \
# The actual image # The actual image
# Note: I wanted to use the scratch image here, but unfortunatly the go-sqlite bindings require cgo and # 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. # because of this, the container would not start when I compiled the image without cgo.
FROM alpine:3.16 AS runner FROM alpine:3.18 AS runner
LABEL maintainer="maintainers@vikunja.io" LABEL maintainer="maintainers@vikunja.io"
WORKDIR /app/vikunja WORKDIR /app/vikunja
ENTRYPOINT [ "/sbin/tini", "-g", "--", "/entrypoint.sh" ] 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) [![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) [![License: AGPL v3](https://img.shields.io/badge/License-AGPL%20v3-blue.svg)](LICENSE)
[![Download](https://img.shields.io/badge/download-v0.20.4-brightgreen.svg)](https://dl.vikunja.io) [![Download](https://img.shields.io/badge/download-v0.21.0-brightgreen.svg)](https://dl.vikunja.io)
[![Docker Pulls](https://img.shields.io/docker/pulls/vikunja/api.svg)](https://hub.docker.com/r/vikunja/api/) [![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) [![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) [![Go Report Card](https://goreportcard.com/badge/kolaente.dev/vikunja/api)](https://goreportcard.com/report/kolaente.dev/vikunja/api)
@ -26,13 +26,7 @@ If you find any security-related issues you don't want to disclose publicly, ple
## Features ## Features
* Create TODO lists with tasks See [the features page](https://vikunja.io/features/) on our website for a more exaustive list or
* 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)! try it on [try.vikunja.io](https://try.vikunja.io)!
## Docs ## Docs
@ -58,4 +52,4 @@ Fork -> Push -> Pull-Request. Also see the [dev docs](https://vikunja.io/docs/de
## License ## 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

@ -1,12 +0,0 @@
#!/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'

View File

@ -1,29 +0,0 @@
### 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
###

View File

@ -1,70 +0,0 @@
# 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}
]
}
###

View File

@ -1,177 +0,0 @@
# 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

@ -1,71 +0,0 @@
# 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}}
###

View File

@ -1,29 +0,0 @@
# 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}}
###

View File

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

View File

@ -3,7 +3,7 @@ service:
# Default is a random token which will be generated at each startup of vikunja. # Default is a random token which will be generated at each startup of vikunja.
# (This means all already issued tokens will be invalid once you restart vikunja) # (This means all already issued tokens will be invalid once you restart vikunja)
JWTSecret: "<jwt-secret>" JWTSecret: "<jwt-secret>"
# The duration of the issed JWT tokens in seconds. # The duration of the issued JWT tokens in seconds.
# The default is 259200 seconds (3 Days). # The default is 259200 seconds (3 Days).
jwtttl: 259200 jwtttl: 259200
# The duration of the "remember me" time in seconds. When the login request is made with # The duration of the "remember me" time in seconds. When the login request is made with
@ -46,7 +46,7 @@ service:
sentrydsn: '' sentrydsn: ''
# If not empty, this will enable `/test/{table}` endpoints which allow to put any content in the database. # If not empty, this will enable `/test/{table}` endpoints which allow to put any content in the database.
# Used to reset the db before frontend tests. Because this is quite a dangerous feature allowing for lots of harm, # Used to reset the db before frontend tests. Because this is quite a dangerous feature allowing for lots of harm,
# each request made to this endpoint neefs to provide an `Authorization: <token>` header with the token from below. <br/> # each request made to this endpoint needs to provide an `Authorization: <token>` header with the token from below. <br/>
# **You should never use this unless you know exactly what you're doing** # **You should never use this unless you know exactly what you're doing**
testingtoken: '' testingtoken: ''
# If enabled, vikunja will send an email to everyone who is either assigned to a task or created it when a task reminder # If enabled, vikunja will send an email to everyone who is either assigned to a task or created it when a task reminder
@ -59,6 +59,9 @@ service:
# The maximum size clients will be able to request for user avatars. # 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. # If clients request a size bigger than this, it will be changed on the fly.
maxavatarsize: 1024 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:
# Database type to use. Supported types are mysql, postgres and sqlite. # Database type to use. Supported types are mysql, postgres and sqlite.
@ -77,7 +80,7 @@ database:
maxopenconnections: 100 maxopenconnections: 100
# Sets the maximum number of idle connections to the db. # Sets the maximum number of idle connections to the db.
maxidleconnections: 50 maxidleconnections: 50
# The maximum lifetime of a single db connection in miliseconds. # The maximum lifetime of a single db connection in milliseconds.
maxconnectionlifetime: 10000 maxconnectionlifetime: 10000
# Secure connection mode. Only used with postgres. # Secure connection mode. Only used with postgres.
# (see https://pkg.go.dev/github.com/lib/pq?tab=doc#hdr-Connection_String_Parameters) # (see https://pkg.go.dev/github.com/lib/pq?tab=doc#hdr-Connection_String_Parameters)
@ -91,29 +94,31 @@ database:
# Enable SSL/TLS for mysql connections. Options: false, true, skip-verify, preferred # Enable SSL/TLS for mysql connections. Options: false, true, skip-verify, preferred
tls: false tls: false
cache: typesense:
# If cache is enabled or not # 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.
enabled: false enabled: false
# Cache type. Possible values are "keyvalue", "memory" or "redis". # The url to the Typesense instance you want to use. Can be hosted locally or in Typesense Cloud as long
# When choosing "keyvalue" this setting follows the one configured in the "keyvalue" section. # as Vikunja is able to reach it.
# When choosing "redis" you will need to configure the redis connection seperately. url: ''
type: keyvalue # The Typesense API key you want to use.
# When using memory this defines the maximum size an element can take apikey: ''
maxelementsize: 1000
redis: redis:
# Whether to enable redis or not # Whether to enable redis or not
enabled: false enabled: false
# The host of the redis server including its port. # The host of the redis server including its port.
host: 'localhost:6379' host: 'localhost:6379'
# The password used to authenicate against the redis server # The password used to authenticate against the redis server
password: '' password: ''
# 0 means default database # 0 means default database
db: 0 db: 0
cors: cors:
# Whether to enable or disable cors headers. # Whether to enable or disable cors headers.
# Note: If you want to put the frontend and the api on seperate domains or ports, you will need to enable this. # Note: If you want to put the frontend and the api on separate domains or ports, you will need to enable this.
# Otherwise the frontend won't be able to make requests to the api through the browser. # Otherwise the frontend won't be able to make requests to the api through the browser.
enable: true enable: true
# A list of origins which may access the api. These need to include the protocol (`http://` or `https://`) and port, if any. # A list of origins which may access the api. These need to include the protocol (`http://` or `https://`) and port, if any.
@ -168,6 +173,10 @@ log:
events: "off" events: "off"
# The log level for event log messages. Possible values (case-insensitive) are ERROR, INFO, DEBUG. # The log level for event log messages. Possible values (case-insensitive) are ERROR, INFO, DEBUG.
eventslevel: "info" eventslevel: "info"
# Whether or not to log mail log messages. This will not log mail contents. Possible values are stdout, stderr, file or off to disable mail-related logging.
mail: "off"
# The log level for mail log messages. Possible values (case-insensitive) are ERROR, WARNING, INFO, DEBUG.
maillevel: "info"
ratelimit: ratelimit:
# whether or not to enable the rate limit # whether or not to enable the rate limit
@ -206,7 +215,7 @@ migration:
# Note that the vikunja frontend expects this to be /migrate/todoist # Note that the vikunja frontend expects this to be /migrate/todoist
redirecturl: <frontend url>/migrate/todoist redirecturl: <frontend url>/migrate/todoist
trello: trello:
# Wheter to enable the trello migrator or not # Whether to enable the trello migrator or not
enable: false enable: false
# The client id, required for making requests to the trello api # The client id, required for making requests to the trello api
# You need to register your vikunja instance at https://trello.com/app-key (log in before you visit that link) to get this # You need to register your vikunja instance at https://trello.com/app-key (log in before you visit that link) to get this
@ -222,7 +231,7 @@ migration:
enable: false enable: false
# The client id, required for making requests to the microsoft graph api # The client id, required for making requests to the microsoft graph api
# See https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app#register-an-application # See https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app#register-an-application
# for information about how to register your vikuinja instance. # for information about how to register your Vikunja instance.
clientid: clientid:
# The client secret, also required for making requests to the microsoft graph api # The client secret, also required for making requests to the microsoft graph api
clientsecret: clientsecret:
@ -265,7 +274,7 @@ legal:
# Key Value Storage settings # Key Value Storage settings
# The Key Value Storage is used for different kinds of things like metrics and a few cache systems. # The Key Value Storage is used for different kinds of things like metrics and a few cache systems.
keyvalue: keyvalue:
# The type of the storage backend. Can be either "memory" or "redis". If "redis" is chosen it needs to be configured seperately. # The type of the storage backend. Can be either "memory" or "redis". If "redis" is chosen it needs to be configured separately.
type: "memory" type: "memory"
auth: auth:
@ -280,7 +289,7 @@ auth:
# If the email is not public in those cases, authenticating will fail. # If the email is not public in those cases, authenticating will fail.
# **Note 2:** The frontend expects to be redirected after authentication by the third party # **Note 2:** The frontend expects to be redirected after authentication by the third party
# to <frontend-url>/auth/openid/<auth key>. Please make sure to configure the redirect url with your third party # to <frontend-url>/auth/openid/<auth key>. Please make sure to configure the redirect url with your third party
# auth service accordingy if you're using the default vikunja frontend. # auth service accordingly if you're using the default vikunja frontend.
# Take a look at the [default config file](https://kolaente.dev/vikunja/api/src/branch/main/config.yml.sample) for more information about how to configure openid authentication. # Take a look at the [default config file](https://kolaente.dev/vikunja/api/src/branch/main/config.yml.sample) for more information about how to configure openid authentication.
openid: openid:
# Enable or disable OpenID Connect authentication # Enable or disable OpenID Connect authentication
@ -306,7 +315,7 @@ auth:
# Prometheus metrics endpoint # Prometheus metrics endpoint
metrics: metrics:
# If set to true, enables a /metrics endpoint for prometheus to collect metrics about Vikunja. # If set to true, enables a /metrics endpoint for prometheus to collect metrics about Vikunja. You can query it from `/api/v1/metrics`.
enabled: false enabled: false
# If set to a non-empty value the /metrics endpoint will require this as a username via basic auth in combination with the password below. # If set to a non-empty value the /metrics endpoint will require this as a username via basic auth in combination with the password below.
username: username:
@ -333,7 +342,17 @@ defaultsettings:
default_project_id: 0 default_project_id: 0
# Start of the week for the user. `0` is sunday, `1` is monday and so on. # Start of the week for the user. `0` is sunday, `1` is monday and so on.
week_start: 0 week_start: 0
# 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. # 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.
language: <unset> language: <unset>
# The time zone of each individual user. This will affect when users get reminders and overdue task emails. # 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> 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

@ -54,3 +54,10 @@ menu:
- name: Get it Hosted - name: Get it Hosted
url: https://vikunja.cloud/?utm_source=io&utm_medium=io&utm_campaign=menu url: https://vikunja.cloud/?utm_source=io&utm_medium=io&utm_campaign=menu
weight: 70 weight: 70
sidebar:
- name: setup
weight: 10
- name: usage
weight: 20
- name: development
weight: 30

View File

@ -22,4 +22,4 @@ and [available configuration options]({{< ref "./setup/config.md">}}).
## Developing ## Developing
If you want to start contributing to Vikunja, take a look at [the development docs]({{< ref "./development/development.md">}}). If you want to start contributing to Vikunja, take a look at [the development docs]({{< ref "./development/development.md">}}).

View File

@ -12,10 +12,10 @@ menu:
All cli-related functions are located in `pkg/cmd`. All cli-related functions are located in `pkg/cmd`.
Each cli command usually calls a function in another package. Each cli command usually calls a function in another package.
For example, the `vikunja migrate` command calls `migration.Migrate()`. For example, the `vikunja migrate` command calls `migration.Migrate()`.
Vikunja uses the amazing [cobra](https://github.com/spf13/cobra) library for its cli. Vikunja uses the amazing [cobra](https://github.com/spf13/cobra) library for its cli.
Please refer to its documentation for informations about how to use flags etc. Please refer to its documentation for information about how to use flags etc.
To add a new cli command, add something like the following: To add a new cli command, add something like the following:

View File

@ -29,7 +29,7 @@ Then run `mage generate-docs` to generate the configuration docs from the sample
## Getting Configuration Values ## Getting Configuration Values
To retreive a configured value call the key with a getter for the type you need. To retrieve a configured value call the key with a getter for the type you need.
For example: For example:
{{< highlight golang >}} {{< highlight golang >}}

View File

@ -12,9 +12,7 @@ menu:
Cron jobs are tasks which run on a predefined schedule. Cron jobs are tasks which run on a predefined schedule.
Vikunja uses these through a light wrapper package around the excellent [github.com/robfig/cron](https://github.com/robfig/cron) package. Vikunja uses these through a light wrapper package around the excellent [github.com/robfig/cron](https://github.com/robfig/cron) package.
The package exposes a `cron.Schedule` method with two arguments: The first one to define the schedule when the cron task The package exposes a `cron.Schedule` method with two arguments: The first one to define the schedule when the cron task should run, and the second one with the actual function to run at the schedule. You would then create a new function to register your the actual cron task in your package.
should run, and the second one with the actual function to run at the schedule.
You would then create a new function to register your the actual cron task in your package.
A basic function to register a cron task looks like this: A basic function to register a cron task looks like this:

View File

@ -26,8 +26,7 @@ To add a new table to the database, create the struct and [add a migration for i
To learn more about how to configure your struct to create "good" tables, refer to [the xorm documentaion](https://xorm.io/docs/). To learn more about how to configure your struct to create "good" tables, refer to [the xorm documentaion](https://xorm.io/docs/).
In most cases you will also need to implement the `TableName() string` method on the new struct to make sure the table In most cases you will also need to implement the `TableName() string` method on the new struct to make sure the table name matches the rest of the tables - plural.
name matches the rest of the tables - plural.
## Adding data to test fixtures ## Adding data to test fixtures
@ -36,5 +35,4 @@ Adding data for test fixtures can be done via `yaml` files in `pkg/models/fixtur
The name of the yaml file should match the table name in the database. The name of the yaml file should match the table name in the database.
Adding values to it is done via array definition inside it. Adding values to it is done via array definition inside it.
**Note**: Table and column names need to be in snake_case as that's what is used internally in the database **Note**: Table and column names need to be in snake_case as that's what is used internally in the database and for mapping values from the database to xorm so your structs can use it.
and for mapping values from the database to xorm so your structs can use it.

View File

@ -25,7 +25,7 @@ All migrations are stored in `pkg/migrations` and files should have the same nam
Each migration should have a function to apply and roll it back, as well as a numeric id (the datetime) Each migration should have a function to apply and roll it back, as well as a numeric id (the datetime)
and a more in-depth description of what the migration actually does. and a more in-depth description of what the migration actually does.
To easily get a new id, run the following on any unix system: To easily get a new id, run the following on any unix system:
{{< highlight bash >}} {{< highlight bash >}}
date +%Y%m%d%H%M%S date +%Y%m%d%H%M%S
@ -75,4 +75,4 @@ func init() {
} }
{{< /highlight >}} {{< /highlight >}}
You should always copy the changed parts of the struct you're changing when adding migraitons. You should always copy the changed parts of the struct you're changing when adding migrations.

View File

@ -17,7 +17,9 @@ menu:
## General ## General
To contribute to Vikunja, fork the project and work on the main branch. 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. 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.
A maintainer will take a look and give you feedback. Once everyone is happy, the PR gets merged and released. 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. If you plan to do a bigger change, it is better to open an issue for discussion first.
@ -26,7 +28,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). The code for the api is located at [code.vikunja.io/api](https://code.vikunja.io/api).
We use go modules to manage third-party libraries for Vikunja, so you'll need at least go `1.17` to use these. You'll need at least Go 1.21 to build Vikunja's api.
A lot of developing tasks are automated using a Magefile, so make sure to [take a look at it]({{< ref "mage.md">}}). A lot of developing tasks are automated using a Magefile, so make sure to [take a look at it]({{< ref "mage.md">}}).
@ -38,18 +40,58 @@ 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). 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. More instructions can be found in the repo's README.
You need to have [pnpm](https://pnpm.io/) and nodejs in version 16 or 18 installed. You need to have [pnpm](https://pnpm.io/) and Node.JS in version 18 or higher installed.
## Git flow ## 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
The `main` branch is the latest and bleeding edge branch with all changes. Unstable releases are automatically created from this branch. 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. A release gets tagged from the main branch with the version name as tag name.
Backports and point-releases should go to a `release/version` branch, based on the tag they are building on top of. Backports and point-releases should go to a `release/version` branch, based on the tag they are building on top of.
## Conventional commits ## Conventional Commits
We're using [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) because they greatly simplify generating release notes. 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

@ -13,14 +13,14 @@ menu:
All custom errors are defined in `pkg/models/errors.go`. All custom errors are defined in `pkg/models/errors.go`.
You should add new ones in this file. You should add new ones in this file.
Custom errors usually have fields for the http return code, a [vikunja-specific error code]({{< ref "../usage/errors.md">}}) Custom errors usually have fields for the http return code, a [Vikunja-specific error code]({{< ref "../usage/errors.md">}})
and a human-readable error message about what went wrong. and a human-readable error message about what went wrong.
An error consists of multiple functions and definitions: An error consists of multiple functions and definitions:
{{< highlight golang >}} {{< highlight golang >}}
// This struct holds any information about this specific error. // This struct holds any information about this specific error.
// In this case, it contains the user ID of a nonexistand user. // In this case, it contains the user ID of a nonexistent user.
// This type should always be a struct, even if it has no values in it. // This type should always be a struct, even if it has no values in it.
// ErrUserDoesNotExist represents a "UserDoesNotExist" kind of error. // ErrUserDoesNotExist represents a "UserDoesNotExist" kind of error.
@ -44,21 +44,21 @@ func (err ErrUserDoesNotExist) Error() string {
return fmt.Sprintf("User does not exist [user id: %d]", err.UserID) return fmt.Sprintf("User does not exist [user id: %d]", err.UserID)
} }
// This const holds the vikunja error code used to be able to identify this error without having to // This const holds the Vikunja error code used to be able to identify this error without having to
// rely on an error string. // rely on an error string.
// This needs to be unique, so you should check whether the error code exists or not. // This needs to be unique, so you should check whether the error code exists or not.
// The general convention for error codes is as follows: // The general convention for error codes is as follows:
// * Every "group" errors lives in a thousend something. For example all user issues are 1000-something, all // * Every "group" errors lives in a thousend something. For example all user issues are 1000-something, all
// project errors are 3000-something and so on. // project errors are 3000-something and so on.
// * New error codes should be the current max error code + 1. Don't take free numbers to prevent old errors // * New error codes should be the current max error code + 1. Don't take free numbers to prevent old errors
// which are depricated and removed from being "new ones". For example, if there are error codes 1001, 1002, 1004, // which are deprecated and removed from being "new ones". For example, if there are error codes 1001, 1002, 1004,
// a new error should be 1005 and not 1003. // a new error should be 1005 and not 1003.
// ErrCodeUserDoesNotExist holds the unique world-error code of this error // ErrCodeUserDoesNotExist holds the unique world-error code of this error
const ErrCodeUserDoesNotExist = 1005 const ErrCodeUserDoesNotExist = 1005
// This is the implementation which returns an http error which is then passed to the client. // This is the implementation which returns an http error which is then passed to the client.
// Here you define the http status code with which one the error will be returned, the vikunja error code and // Here you define the http status code with which one the error will be returned, the Vikunja error code and
// a human-readable error message. // a human-readable error message.
// HTTPError holds the http error description // HTTPError holds the http error description

View File

@ -42,7 +42,7 @@ You then get the event with all its data back in the listener, see below.
#### Naming Convention #### Naming Convention
Event names should roughly have the entity they're dealing with on the left and the action on the right of the name, separated by `.`. Event names should roughly have the entity they're dealing with on the left and the action on the right of the name, separated by `.`.
There's no limit to how "deep" or specifig an event name can be. There's no limit to how "deep" or specific an event name can be.
The name should have the most general concept it's describing at the left, getting more specific on the right of it. The name should have the most general concept it's describing at the left, getting more specific on the right of it.
@ -104,7 +104,7 @@ func createTask(s *xorm.Session, t *Task, a web.Auth, updateAssignees bool) (err
} }
{{< /highlight >}} {{< /highlight >}}
As you can see, the curent task and doer are injected into it. As you can see, the current task and doer are injected into it.
### Special Events ### Special Events
@ -133,7 +133,7 @@ type Listener interface {
The `Handle` method is executed when the event this listener listens on is dispatched. The `Handle` method is executed when the event this listener listens on is dispatched.
* As the single parameter, it gets the payload of the event, which is the event struct when it was dispatched decoded as json object and passed as a slice of bytes. * As the single parameter, it gets the payload of the event, which is the event struct when it was dispatched decoded as json object and passed as a slice of bytes.
To use it you'll need to unmarshal it. Unfortunately there's no way to pass an already populated event object to the function because we would not know what type it has when parsing it. To use it you'll need to unmarshal it. Unfortunately there's no way to pass an already populated event object to the function because we would not know what type it has when parsing it.
* If the handler returns an error, the listener is retried 5 times, with an exponentional back-off period in between retries. * If the handler returns an error, the listener is retried 5 times, with an exponential back-off period in between retries.
If it still fails after the fifth retry, the event is nack'd and it's up to the event dispatcher to resend it. If it still fails after the fifth retry, the event is nack'd and it's up to the event dispatcher to resend it.
You can learn more about this mechanism in the [watermill documentation](https://watermill.io/docs/middlewares/#retry). You can learn more about this mechanism in the [watermill documentation](https://watermill.io/docs/middlewares/#retry).
@ -148,7 +148,7 @@ The easiest way to create a new listener for an event is with mage:
mage dev:make-listener <listener-name> <event-name> <package> mage dev:make-listener <listener-name> <event-name> <package>
``` ```
This will create a new listener type in the `pkg/<package>/listners.go` file and implement the `Handle` and `Name` methods. This will create a new listener type in the `pkg/<package>/listeners.go` file and implement the `Handle` and `Name` methods.
It will also pre-generate some boilerplate code to unmarshal the event from the payload. It will also pre-generate some boilerplate code to unmarshal the event from the payload.
Furthermore, it will register the listener for its event in the `RegisterListeners()` method of the same file. Furthermore, it will register the listener for its event in the `RegisterListeners()` method of the same file.
@ -157,7 +157,7 @@ This function is called at startup and has to contain all events you want to lis
### Listening for Events ### Listening for Events
To listen for an event, you need to register the listener for the event it should be called for. To listen for an event, you need to register the listener for the event it should be called for.
This usually happens in the `RegisterListeners()` method in `pkg/<package>/listners.go` which is called at start up. This usually happens in the `RegisterListeners()` method in `pkg/<package>/listeners.go` which is called at start up.
The listener will never be executed if it hasn't been registered. The listener will never be executed if it hasn't been registered.
@ -179,7 +179,7 @@ func (s *IncreaseTaskCounter) Name() string {
return "task.counter.increase" return "task.counter.increase"
} }
// Hanlde is executed when the event IncreaseTaskCounter listens on is fired // Handle is executed when the event IncreaseTaskCounter listens on is fired
func (s *IncreaseTaskCounter) Handle(payload message.Payload) (err error) { func (s *IncreaseTaskCounter) Handle(payload message.Payload) (err error) {
return keyvalue.IncrBy(metrics.TaskCountKey, 1) return keyvalue.IncrBy(metrics.TaskCountKey, 1)
} }

View File

@ -31,7 +31,7 @@ go install github.com/magefile/mage
There are multiple categories of subcommands in the magefile: There are multiple categories of subcommands in the magefile:
* `build`: Contains commands to build a single binary * `build`: Contains commands to build a single binary
* `check`: Contains commands to statically check the source code * `check`: Contains commands to statically check the source code
* `release`: Contains commands to release Vikunja with everything that's required * `release`: Contains commands to release Vikunja with everything that's required
* `test`: Contains commands to run all kinds of tests * `test`: Contains commands to run all kinds of tests
* `dev`: Contains commands to run development tasks * `dev`: Contains commands to run development tasks
@ -114,7 +114,7 @@ binary to be able to use it.
* `mage release:check` creates sha256 checksums for each binary which will be included in the zip file * `mage release:check` creates sha256 checksums for each binary which will be included in the zip file
* `mage release:os-package` bundles a binary with the `sha256` checksum file, a sample `config.yml` and a copy of the license in a folder for each architecture * `mage release:os-package` bundles a binary with the `sha256` checksum file, a sample `config.yml` and a copy of the license in a folder for each architecture
* `mage release:compress` compresses all build binaries with `upx` to save space * `mage release:compress` compresses all build binaries with `upx` to save space
* `mage release:zip` paclages a zip file for the files created by `release:os-package` * `mage release:zip` packages a zip file for the files created by `release:os-package`
### Build os packages ### Build os packages
@ -168,7 +168,7 @@ Runs all integration tests.
mage dev:create-migration mage dev:create-migration
{{< /highlight >}} {{< /highlight >}}
Creates a new migration with the current date. Creates a new migration with the current date.
Will ask for the name of the struct you want to create a migration for. Will ask for the name of the struct you want to create a migration for.
See also [migration docs]({{< ref "mage.md" >}}). See also [migration docs]({{< ref "mage.md" >}}).

View File

@ -34,14 +34,13 @@ promauto.NewGaugeFunc(prometheus.GaugeOpts{
}) })
{{< /highlight >}} {{< /highlight >}}
Then you'll need to set the metrics initial value on every startup of vikunja. Then you'll need to set the metrics initial value on every startup of Vikunja.
This is done in `pkg/routes/routes.go` to avoid cyclic imports. This is done in `pkg/routes/routes.go` to avoid cyclic imports.
If metrics are enabled, it checks if a redis connection is available and then sets the initial values. If metrics are enabled, it checks if a redis connection is available and then sets the initial values.
A convenience function is available if the metric is based on a database struct. A convenience function is available if the metric is based on a database struct.
Because metrics are stored in redis, you are responsible to increase or decrease these based on criteria you define. Because metrics are stored in redis, you are responsible to increase or decrease these based on criteria you define.
To do this, use `metrics.UpdateCount(value, key)` where `value` is the amount you want to cange it (you can pass To do this, use `metrics.UpdateCount(value, key)` where `value` is the amount you want to change it (you can pass negative values to decrease it) and `key` it the redis key used to define the metric.
negative values to decrease it) and `key` it the redis key used to define the metric.
## Using it ## Using it

View File

@ -17,12 +17,9 @@ In general, each migrator implements a migrator interface which is then called f
The interface makes it possible to use helper methods which handle http and focus only on the implementation of the migrator itself. The interface makes it possible to use helper methods which handle http and focus only on the implementation of the migrator itself.
There are two ways of migrating data from another service: There are two ways of migrating data from another service:
1. Through the auth-based flow where the user gives you access to their data at the third-party service through an
oauth flow. You can then call the service's api on behalf of your user to get all the data. 1. Through the auth-based flow where the user gives you access to their data at the third-party service through an oauth flow. You can then call the service's api on behalf of your user to get all the data. The Todoist, Trello and Microsoft To-Do Migrators use this pattern.
The Todoist, Trello and Microsoft To-Do Migrators use this pattern. 2. A file migration where the user uploads a file obtained from some third-party service. In your migrator, you need to parse the file and create the projects, tasks etc. The Vikunja File Import uses this pattern.
2. A file migration where the user uploads a file obtained from some third-party service. In your migrator, you need
to parse the file and create the projects, tasks etc.
The Vikunja File Import uses this pattern.
To differentiate the two, there are two different interfaces you must implement. To differentiate the two, there are two different interfaces you must implement.
@ -43,7 +40,7 @@ type Migrator interface {
// Name holds the name of the migration. // Name holds the name of the migration.
// This is used to show the name to users and to keep track of users who already migrated. // This is used to show the name to users and to keep track of users who already migrated.
Name() string Name() string
// Migrate is the interface used to migrate a user's tasks from another platform to vikunja. // Migrate is the interface used to migrate a user's tasks from another platform to Vikunja.
// The user object is the user who's tasks will be migrated. // The user object is the user who's tasks will be migrated.
Migrate(user *models.User) error Migrate(user *models.User) error
// AuthURL returns a url for clients to authenticate against. // AuthURL returns a url for clients to authenticate against.
@ -61,7 +58,7 @@ type FileMigrator interface {
// Name holds the name of the migration. // Name holds the name of the migration.
// This is used to show the name to users and to keep track of users who already migrated. // This is used to show the name to users and to keep track of users who already migrated.
Name() string Name() string
// Migrate is the interface used to migrate a user's tasks, projects and other things from a file to vikunja. // Migrate is the interface used to migrate a user's tasks, projects and other things from a file to Vikunja.
// The user object is the user who's tasks will be migrated. // The user object is the user who's tasks will be migrated.
Migrate(user *user.User, file io.ReaderAt, size int64) error Migrate(user *user.User, file io.ReaderAt, size int64) error
} }
@ -102,35 +99,33 @@ You should also document the routes with [swagger annotations]({{< ref "swagger-
## Insertion helper method ## 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. 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 namespace, then add projects inside that namespace, then tasks in the lists and so on. 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.
The root structure must be present as `[]*models.NamespaceWithProjectsAndTasks`. It allows to represent all of Vikunja's The root structure must be present as `[]*models.ProjectWithTasksAndBuckets`. It allows to represent all of Vikunja's hierarchy as a single data structure.
hierachie as a single data structure.
Then call the method like so: Then call the method like so:
```go ```go
fullVikunjaHierachie, err := convertWunderlistToVikunja(wContent) fullVikunjaHierarchy, err := convertWunderlistToVikunja(wContent)
if err != nil { if err != nil {
return return
} }
err = migration.InsertFromStructure(fullVikunjaHierachie, user) err = migration.InsertFromStructure(fullVikunjaHierarchy, user)
``` ```
## Configuration ## Configuration
If your migrator is an oauth-based one, you should add at least an option to enable or disable it. If your migrator is an oauth-based one, you should add at least an option to enable or disable it.
Chances are, you'll need some more options for things like client ID and secret Chances are, you'll need some more options for things like client ID and secret (if the other service uses oAuth as an authentication flow).
(if the other service uses oAuth as an authentication flow).
The easiest way to implement an on/off switch is to check whether your migration service is enabled or not when The easiest way to implement an on/off switch is to check whether your migration service is enabled or not when registering the routes, and then simply don't registering the routes in case it is disabled.
registering the routes, and then simply don't registering the routes in case it is disabled.
File based migrators can always be enabled. File based migrators can always be enabled.
### Making the migrator public in `/info` ### Making the migrator public in `/info`
You should make your migrator available in the `/info` endpoint so that frontends can display options to enable them or not. You should make your migrator available in the `/info` endpoint so that frontends can display options to enable them or not.
To do this, add an entry to the `AvailableMigrators` field in `pkg/routes/api/v1/info.go`. To do this, add an entry to the `AvailableMigrators` field in `pkg/routes/api/v1/info.go`.

View File

@ -10,7 +10,7 @@ menu:
# Notifications # Notifications
Vikunjs provides a simple abstraction to send notifications per mail and in the database. Vikunja provides a simple abstraction to send notifications per mail and in the database.
{{< table_of_contents >}} {{< table_of_contents >}}
@ -39,7 +39,7 @@ A list of chainable functions is available to compose a mail:
mail := NewMail(). mail := NewMail().
// The optional sender of the mail message. // The optional sender of the mail message.
From("test@example.com"). From("test@example.com").
// The optional receipient of the mail message. Uses the mail address of the notifiable if omitted. // The optional recipient of the mail message. Uses the mail address of the notifiable if omitted.
To("test@otherdomain.com"). To("test@otherdomain.com").
// The subject of the mail to send. // The subject of the mail to send.
Subject("Testmail"). Subject("Testmail").
@ -49,7 +49,7 @@ mail := NewMail().
Line("This is a line of text"). Line("This is a line of text").
// An action can contain a title and a url. It gets rendered as a big button in the mail. // An action can contain a title and a url. It gets rendered as a big button in the mail.
// Note that you can have only one action per mail. // Note that you can have only one action per mail.
// All lines added before an action will appearr in the mail before the button, all lines // All lines added before an action will appear in the mail before the button, all lines
// added afterwards will appear after it. // added afterwards will appear after it.
Action("The Action", "https://example.com"). Action("The Action", "https://example.com").
// Another line of text. // Another line of text.
@ -60,8 +60,7 @@ If not provided, the `from` field of the mail contains the value configured in [
### Database notifications ### Database notifications
All data returned from the `ToDB()` method is serialized to json and saved into the database, along with the id of the All data returned from the `ToDB()` method is serialized to json and saved into the database, along with the id of the notifiable, the name of the notification and a time stamp.
notifiable, the name of the notification and a time stamp.
If you don't use the database notification, the `Name()` function can return an empty string. If you don't use the database notification, the `Name()` function can return an empty string.
## Creating a new notification ## Creating a new notification

View File

@ -12,10 +12,10 @@ menu:
This checklist is a collection of all steps usually involved when releasing a new version of Vikunja. This checklist is a collection of all steps usually involved when releasing a new version of Vikunja.
Not all steps are necessary for every release. Not all steps are necessary for every release.
* Website update : * Website update
* New Features: If there are new features worth mentioning the feature page should be updated. * New Features: If there are new features worth mentioning the feature page should be updated.
* New Screenshots: If an overhaul of an existing feature happend so that it now looks different from the existing screenshot, a new one is required. * New Screenshots: If an overhaul of an existing feature happened so that it now looks different from the existing screenshot, a new one is required.
* Generate changelogs: (with git-cliff) * Generate changelogs (with git-cliff)
* Frontend * Frontend
* API * API
* Desktop * Desktop
@ -23,11 +23,11 @@ Not all steps are necessary for every release.
* Frontend * Frontend
* API * API
* Desktop * Desktop
* Once built: Prune the cloudflare cache so that the new versions show up at dl.vikunja.io * Once built: Prune the cloudflare cache so that the new versions show up at [dl.vikunja.io](https://dl.vikunja.io/)
* Release Highlights Blogpost: * Release Highlights Blogpost
* Include a section about Vikunja in general (totally fine to copy one from the earlier blog posts) * Include a section about Vikunja in general (totally fine to copy one from the earlier blog posts)
* New Features & Improvements: Mention bigger features, potentially with screenshots. Things like refactoring are sometimes also worth mentioneing. * New Features & Improvements: Mention bigger features, potentially with screenshots. Things like refactoring are sometimes also worth mentioning.
* Publish: * Publish
* Reddit * Reddit
* Twitter * Twitter
* Mastodon * Mastodon
@ -36,4 +36,3 @@ Not all steps are necessary for every release.
* Forum * Forum
* If features in the release were sponsored, send an email to relevant stakeholders * If features in the release were sponsored, send an email to relevant stakeholders
* Update Vikunja Cloud version and other instances * Update Vikunja Cloud version and other instances

View File

@ -64,7 +64,7 @@ See [integration tests]({{< ref "test.md" >}}#integration-tests) for more detail
### log ### log
Similar to `config`, this will set up the logging, based on differen logging backends. Similar to `config`, this will set up the logging, based on different logging backends.
This init is called in `main.go` after the config init is done. This init is called in `main.go` after the config init is done.
### mail ### mail
@ -126,7 +126,7 @@ See [writing a migrator]({{< ref "migration.md" >}}).
### red (redis) ### red (redis)
This package initializes a connection to a redis server. This package initializes a connection to a redis server.
This inizialization is automatically done at the startup of vikunja. This initialization is automatically done at the startup of Vikunja.
It also has a function (`GetRedis()`) which returns a redis client object you can then use in your package It also has a function (`GetRedis()`) which returns a redis client object you can then use in your package
to talk to redis. to talk to redis.
@ -138,7 +138,7 @@ In most cases, using the `keyvalue` package is a better fit.
### routes ### routes
This package defines all routes which are available for vikunja clients to use. This package defines all routes which are available for Vikunja clients to use.
To add a new route, see [adding a new route]({{< ref "feature.md">}}). To add a new route, see [adding a new route]({{< ref "feature.md">}}).
#### api/v1 #### api/v1
@ -148,7 +148,7 @@ Every handler function which does not use the standard web handler should live h
### swagger ### swagger
This is where the [generated]({{< ref "mage.md#generate-swagger-definitions-from-code-comments">}} [api docs]({{< ref "../usage/api.md">}}) live. This is where the [generated]({{< ref "mage.md#generate-swagger-definitions-from-code-comments">}}) [api docs]({{< ref "../usage/api.md">}}) live.
You usually don't need to touch this package. You usually don't need to touch this package.
### user ### user
@ -162,10 +162,9 @@ A small package, containing some helper functions:
* `MakeRandomString`: Generates a random string of a given length. * `MakeRandomString`: Generates a random string of a given length.
* `Sha256`: Calculates a sha256 hash from a given string. * `Sha256`: Calculates a sha256 hash from a given string.
See their function definitions for instructions on how to use them. See their function definitions for instructions on how to use them.
### version ### version
The single purpouse of this package is to hold the current vikunja version which gets overridden through build flags The single purpose of this package is to hold the current Vikunja version which gets overridden through build flags each time `mage release` or `mage build` is run.
each time `mage release` or `mage build` is run. It is a separate package to avoid import cycles with other packages.
It is a seperate package to avoid import cycles with other packages.

View File

@ -25,7 +25,7 @@ As an example, this is the definition of a project with all comments:
type Project struct { type Project struct {
// The unique, numeric id of this project. // The unique, numeric id of this project.
ID int64 `xorm:"bigint autoincr not null unique pk" json:"id" param:"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 namespace overview. // The title of the project. You'll see this in the overview.
Title string `xorm:"varchar(250) not null" json:"title" valid:"required,runelength(1|250)" minLength:"1" maxLength:"250"` Title string `xorm:"varchar(250) not null" json:"title" valid:"required,runelength(1|250)" minLength:"1" maxLength:"250"`
// The description of the project. // The description of the project.
Description string `xorm:"longtext null" json:"description"` Description string `xorm:"longtext null" json:"description"`
@ -34,13 +34,14 @@ type Project struct {
// The hex color of this project // The hex color of this project
HexColor string `xorm:"varchar(6) null" json:"hex_color" valid:"runelength(0|6)" maxLength:"6"` HexColor string `xorm:"varchar(6) null" json:"hex_color" valid:"runelength(0|6)" maxLength:"6"`
OwnerID int64 `xorm:"bigint INDEX not null" json:"-"` OwnerID int64 `xorm:"bigint INDEX not null" json:"-"`
NamespaceID int64 `xorm:"bigint INDEX not null" json:"namespace_id" param:"namespace"` ParentProjectID int64 `xorm:"bigint INDEX null" json:"parent_project_id"`
ParentProject *Project `xorm:"-" json:"-"`
// The user who created this project. // The user who created this project.
Owner *user.User `xorm:"-" json:"owner" valid:"-"` Owner *user.User `xorm:"-" json:"owner" valid:"-"`
// Whether or not a project is archived. // Whether a project is archived.
IsArchived bool `xorm:"not null default false" json:"is_archived" query:"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 // The id of the file this project has set as background
@ -50,11 +51,11 @@ 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. // 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"` BackgroundBlurHash string `xorm:"varchar(50) null" json:"background_blur_hash"`
// 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. // 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.
IsFavorite bool `xorm:"-" json:"is_favorite"` 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. // The subscription status for the user reading this project. You can only read this property, use the subscription endpoints to modify it.
// Will only returned when retreiving one project. // Will only returned when retrieving one project.
Subscription *Subscription `xorm:"-" json:"subscription,omitempty"` Subscription *Subscription `xorm:"-" json:"subscription,omitempty"`
// The position this project has when querying all projects. See the tasks.position property on how to use this. // The position this project has when querying all projects. See the tasks.position property on how to use this.

View File

@ -46,10 +46,10 @@ To run integration tests, use `mage test:integration`.
### Running tests with config ### Running tests with config
You can run tests with all available config variables if you want, enabeling you to run tests for a lot of scenarios. You can run tests with all available config variables if you want, enabling you to run tests for a lot of scenarios.
We use this in CI to run all tests with different databases. We use this in CI to run all tests with different databases.
To use the normal config set the enviroment variable `VIKUNJA_TESTS_USE_CONFIG=1`. To use the normal config set the environment variable `VIKUNJA_TESTS_USE_CONFIG=1`.
### Showing sql queries ### Showing sql queries

View File

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

View File

@ -21,7 +21,7 @@ Currently, only the frontend (and by extension, the desktop app) is translatable
## Translation Instructions ## Translation Instructions
> These are the instructions for translating Vikunja in another language. > These are the instructions for translating Vikunja in another language.
> For information about how to add new translation strings, see below. > For information about how to add new translation strings, see below.
For all languages these translation guidelines should be applied when translating: For all languages these translation guidelines should be applied when translating:
@ -44,7 +44,7 @@ Instead, translate it to reflect the original meaning in the translated string b
All translation strings are stored in `src/i18n/lang/`. All translation strings are stored in `src/i18n/lang/`.
New strings should be added only in the `en.json` file. New strings should be added only in the `en.json` file.
Strings in other languages will be synced through weblate and should not be added directly as a PR/commit in the frontend repo. Strings in other languages will be synced through [crowdin](https://crowdin.com/project/vikunja) and should not be added directly as a PR/commit in the frontend repo.
## Requesting a new language ## Requesting a new language

View File

@ -56,4 +56,4 @@ For more information, please visit the [relevant PostgreSQL documentation](https
### SQLite ### SQLite
To back up sqllite databases, it is enough to copy the [database file]({{< ref "config.md" >}}#path) to somwhere else. To back up sqllite databases, it is enough to copy the [database file]({{< ref "config.md" >}}#path) to somewhere else.

View File

@ -19,14 +19,10 @@ To completely build Vikunja from source, you need to build the api and frontend.
The Vikunja API has no other dependencies than go itself. The Vikunja API has no other dependencies than go itself.
That means compiling it boils down to these steps: 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.17`. 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. 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. 3. Clone the repo with `git clone https://code.vikunja.io/api` and switch into the directory.
4. Run `mage build: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. 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.
*Note:* Static ressources such as email templates are built into the binary.
For these to work, you may need to run `mage build:generate` before building the vikunja binary.
When builing entirely with `mage`, you dont need to do this, `mage build:generate` will be run automatically when running `mage build:build`.
### Build for different architectures ### Build for different architectures

View File

@ -10,8 +10,7 @@ menu:
# Configuration options # Configuration options
You can either use a `config.yml` file in the root directory of vikunja or set almost all config option with You can either use a `config.yml` file in the root directory of vikunja or set almost all config option with environment variables. If you have both, the value set in the config file is used.
environment variables. If you have both, the value set in the config file is used.
Right now it is not possible to configure openid authentication via environment variables. Right now it is not possible to configure openid authentication via environment variables.
Variables are nested in the `config.yml`, these nested variables become `VIKUNJA_FIRST_CHILD` when configuring via Variables are nested in the `config.yml`, these nested variables become `VIKUNJA_FIRST_CHILD` when configuring via
@ -31,7 +30,7 @@ first:
# Formats # Formats
Vikunja supports using `toml`, `yaml`, `hcl`, `ini`, `json`, envfile, env variables and Java Properties files. Vikunja supports using `toml`, `yaml`, `hcl`, `ini`, `json`, envfile, env variables and Java Properties files.
We reccomend yaml or toml, but you're free to use whatever you want. We recommend yaml or toml, but you're free to use whatever you want.
Vikunja provides a default [`config.yml`](https://kolaente.dev/vikunja/api/src/branch/main/config.yml.sample) file which you can use as a starting point. Vikunja provides a default [`config.yml`](https://kolaente.dev/vikunja/api/src/branch/main/config.yml.sample) file which you can use as a starting point.
@ -56,7 +55,7 @@ If you don't provide a value in your config file, their default will be used.
Most config variables are nested under some "higher-level" key. Most config variables are nested under some "higher-level" key.
For example, the `interface` config variable is a child of the `service` key. For example, the `interface` config variable is a child of the `service` key.
The docs below aim to reflect that leveling, but please also have a lookt at [the default config](https://code.vikunja.io/api/src/branch/main/config.yml.sample) file The docs below aim to reflect that leveling, but please also have a look at [the default config](https://code.vikunja.io/api/src/branch/main/config.yml.sample) file
to better grasp how the nesting looks like. to better grasp how the nesting looks like.
<!-- Generated config will be injected here --> <!-- Generated config will be injected here -->
@ -82,7 +81,7 @@ Environment path: `VIKUNJA_SERVICE_JWTSECRET`
### jwtttl ### jwtttl
The duration of the issed JWT tokens in seconds. The duration of the issued JWT tokens in seconds.
The default is 259200 seconds (3 Days). The default is 259200 seconds (3 Days).
Default: `259200` Default: `259200`
@ -287,7 +286,7 @@ Environment path: `VIKUNJA_SERVICE_SENTRYDSN`
If not empty, this will enable `/test/{table}` endpoints which allow to put any content in the database. If not empty, this will enable `/test/{table}` endpoints which allow to put any content in the database.
Used to reset the db before frontend tests. Because this is quite a dangerous feature allowing for lots of harm, Used to reset the db before frontend tests. Because this is quite a dangerous feature allowing for lots of harm,
each request made to this endpoint neefs to provide an `Authorization: <token>` header with the token from below. <br/> each request made to this endpoint needs to provide an `Authorization: <token>` header with the token from below. <br/>
**You should never use this unless you know exactly what you're doing** **You should never use this unless you know exactly what you're doing**
Default: `<empty>` Default: `<empty>`
@ -334,6 +333,18 @@ Full path: `service.maxavatarsize`
Environment path: `VIKUNJA_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 ## database
@ -430,7 +441,7 @@ Environment path: `VIKUNJA_DATABASE_MAXIDLECONNECTIONS`
### maxconnectionlifetime ### maxconnectionlifetime
The maximum lifetime of a single db connection in miliseconds. The maximum lifetime of a single db connection in milliseconds.
Default: `10000` Default: `10000`
@ -497,43 +508,45 @@ Environment path: `VIKUNJA_DATABASE_TLS`
--- ---
## cache ## typesense
### enabled ### enabled
If cache is enabled or not 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.
Default: `false` Default: `false`
Full path: `cache.enabled` Full path: `typesense.enabled`
Environment path: `VIKUNJA_CACHE_ENABLED` Environment path: `VIKUNJA_TYPESENSE_ENABLED`
### type ### url
Cache type. Possible values are "keyvalue", "memory" or "redis". The url to the Typesense instance you want to use. Can be hosted locally or in Typesense Cloud as long
When choosing "keyvalue" this setting follows the one configured in the "keyvalue" section. as Vikunja is able to reach it.
When choosing "redis" you will need to configure the redis connection seperately.
Default: `keyvalue` Default: `<empty>`
Full path: `cache.type` Full path: `typesense.url`
Environment path: `VIKUNJA_CACHE_TYPE` Environment path: `VIKUNJA_TYPESENSE_URL`
### maxelementsize ### apikey
When using memory this defines the maximum size an element can take The Typesense API key you want to use.
Default: `1000` Default: `<empty>`
Full path: `cache.maxelementsize` Full path: `typesense.apikey`
Environment path: `VIKUNJA_CACHE_MAXELEMENTSIZE` Environment path: `VIKUNJA_TYPESENSE_APIKEY`
--- ---
@ -566,7 +579,7 @@ Environment path: `VIKUNJA_REDIS_HOST`
### password ### password
The password used to authenicate against the redis server The password used to authenticate against the redis server
Default: `<empty>` Default: `<empty>`
@ -595,7 +608,7 @@ Environment path: `VIKUNJA_REDIS_DB`
### enable ### enable
Whether to enable or disable cors headers. Whether to enable or disable cors headers.
Note: If you want to put the frontend and the api on seperate domains or ports, you will need to enable this. Note: If you want to put the frontend and the api on separate domains or ports, you will need to enable this.
Otherwise the frontend won't be able to make requests to the api through the browser. Otherwise the frontend won't be able to make requests to the api through the browser.
Default: `true` Default: `true`
@ -871,6 +884,28 @@ Full path: `log.eventslevel`
Environment path: `VIKUNJA_LOG_EVENTSLEVEL` Environment path: `VIKUNJA_LOG_EVENTSLEVEL`
### mail
Whether or not to log mail log messages. This will not log mail contents. Possible values are stdout, stderr, file or off to disable mail-related logging.
Default: `off`
Full path: `log.mail`
Environment path: `VIKUNJA_LOG_MAIL`
### maillevel
The log level for mail log messages. Possible values (case-insensitive) are ERROR, WARNING, INFO, DEBUG.
Default: `info`
Full path: `log.maillevel`
Environment path: `VIKUNJA_LOG_MAILLEVEL`
--- ---
## ratelimit ## ratelimit
@ -1077,7 +1112,7 @@ The Key Value Storage is used for different kinds of things like metrics and a f
### type ### type
The type of the storage backend. Can be either "memory" or "redis". If "redis" is chosen it needs to be configured seperately. The type of the storage backend. Can be either "memory" or "redis". If "redis" is chosen it needs to be configured separately.
Default: `memory` Default: `memory`
@ -1112,7 +1147,7 @@ The provider needs to support the `openid`, `profile` and `email` scopes.<br/>
If the email is not public in those cases, authenticating will fail. If the email is not public in those cases, authenticating will fail.
**Note 2:** The frontend expects to be redirected after authentication by the third party **Note 2:** The frontend expects to be redirected after authentication by the third party
to <frontend-url>/auth/openid/<auth key>. Please make sure to configure the redirect url with your third party to <frontend-url>/auth/openid/<auth key>. Please make sure to configure the redirect url with your third party
auth service accordingy if you're using the default vikunja frontend. auth service accordingly if you're using the default vikunja frontend.
Take a look at the [default config file](https://kolaente.dev/vikunja/api/src/branch/main/config.yml.sample) for more information about how to configure openid authentication. Take a look at the [default config file](https://kolaente.dev/vikunja/api/src/branch/main/config.yml.sample) for more information about how to configure openid authentication.
Default: `<empty>` Default: `<empty>`
@ -1132,7 +1167,7 @@ Prometheus metrics endpoint
### enabled ### enabled
If set to true, enables a /metrics endpoint for prometheus to collect metrics about Vikunja. If set to true, enables a /metrics endpoint for prometheus to collect metrics about Vikunja. You can query it from `/api/v1/metrics`.
Default: `false` Default: `false`
@ -1272,7 +1307,7 @@ Environment path: `VIKUNJA_DEFAULTSETTINGS_WEEK_START`
### language ### language
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. 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.
Default: `<unset>` Default: `<unset>`
@ -1292,3 +1327,53 @@ Full path: `defaultsettings.timezone`
Environment path: `VIKUNJA_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

@ -16,7 +16,7 @@ It uses an nginx container or traefik on the host to proxy backend and frontend
For all available configuration options, see [configuration]({{< ref "config.md">}}). For all available configuration options, see [configuration]({{< ref "config.md">}}).
<div class="notification is-warning"> <div class="notification is-warning">
<b>NOTE:</b> If you intend to run Vikunja with mysql and/or to use non-latin characters <b>NOTE:</b> If you intend to run Vikunja with mysql and/or to use non-latin characters
<a href="{{< ref "utf-8.md">}}">make sure your db is utf-8 compatible</a>.<br/> <a href="{{< ref "utf-8.md">}}">make sure your db is utf-8 compatible</a>.<br/>
All examples on this page already reflect this and do not require additional work. All examples on this page already reflect this and do not require additional work.
</div> </div>
@ -25,8 +25,7 @@ All examples on this page already reflect this and do not require additional wor
## Redis ## Redis
While Vikunja has support to use redis as a caching backend, you'll probably not need it unless you're using Vikunja While Vikunja has support to use redis as a caching backend, you'll probably not need it unless you're using Vikunja with more than a handful of users.
with more than a handful of users.
To use redis, you'll need to add this to the config examples below: To use redis, you'll need to add this to the config examples below:
@ -66,19 +65,45 @@ db:
You'll also need to change the `VIKUNJA_DATABASE_TYPE` to `postgres` on the api container declaration. You'll also need to change the `VIKUNJA_DATABASE_TYPE` to `postgres` on the api container declaration.
<div class="notification is-warning"> <div class="notification is-warning">
<b>NOTE:</b> The mariadb container can sometimes take a while to initialize, especially on the first run. <b>NOTE:</b> The mariadb container can sometimes take a while to initialize, especially on the first run. During this time, the api container will fail to start at all. It will automatically restart every few seconds.
During this time, the api container will fail to start at all. It will automatically restart every few seconds. </div>
## Sqlite
Vikunja supports postgres, mysql and sqlite as a database backend. The examples on this page use mysql with a mariadb container.
To use sqlite as a database backend, change the `api` section of the examples to this:
{{< highlight yaml >}}
api:
image: vikunja/api
environment:
VIKUNJA_SERVICE_JWTSECRET: <a super secure random secret>
VIKUNJA_SERVICE_FRONTENDURL: http://<your public frontend url with slash>/
# Note the default path is /app/vikunja/vikunja.db This moves to a different folder so you can use a volume and store this outside of the container so state is persisted even if container destroyed.
VIKUNJA_DATABASE_PATH: /db/vikunja.db
ports:
- 3456:3456
volumes:
- ./files:/app/vikunja/files
- ./db:/db
restart: unless-stopped
{{< /highlight >}}
The default path Vikunja uses for sqlite is relative to the binary, which in the docker container would be `/app/vikunja/vikunja.db`. This config moves to a different folder using `VIKUNJA_DATABASE_PATH` so you can use a volume at `/db` and store this outside of the container so state is persisted even if container destroyed.
You'll also need to remove or change the `VIKUNJA_DATABASE_TYPE` to `sqlite` on the api container declaration.
You can also remove the db section.
<div class="notification is-warning">
<b>NOTE:</b> If you'll use your instance with more than a handful of users, we recommend using mysql or postgres.
</div> </div>
## Example without any proxy ## Example without any proxy
This example lets you host Vikunja without any reverse proxy in front of it. This is the absolute minimum configuration This example lets you host Vikunja without any reverse proxy in front of it. This is the absolute minimum configuration you need to get something up and running. If you want to host Vikunja on one single port instead of two different ones or need tls termination, check out one of the other examples.
you need to get something up and running. If you want to host Vikunja on one single port instead of two different ones
or need tls termination, check out one of the other examples.
Note that you need to change the `VIKUNJA_API_URL` environment variable to the ip (the docker host you're running this on) Note that you need to change the `VIKUNJA_API_URL` environment variable to the ip (the docker host you're running this on) is reachable at. Because the browser you'll use to access the Vikunja frontend uses that url to make the requests, it has to be able to reach that ip + port from the outside. Putting everything in a private network won't work.
is reachable at. Because the browser you'll use to access the Vikunja frontend uses that url to make the requests, it
has to be able to reach that ip + port from the outside. Putting everything in a private network won't work.
{{< highlight yaml >}} {{< highlight yaml >}}
version: '3' version: '3'
@ -235,7 +260,7 @@ services:
image: mariadb:10 image: mariadb:10
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
environment: environment:
MYSQL_ROOT_PASSWORD: supersupersecret MYSQL_ROOT_PASSWORD: supersupersecret
MYSQL_USER: vikunja MYSQL_USER: vikunja
MYSQL_PASSWORD: supersecret MYSQL_PASSWORD: supersecret
MYSQL_DATABASE: vikunja MYSQL_DATABASE: vikunja
@ -343,7 +368,7 @@ services:
image: mariadb:10 image: mariadb:10
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
environment: environment:
MYSQL_ROOT_PASSWORD: supersecret MYSQL_ROOT_PASSWORD: supersecret
MYSQL_USER: vikunja MYSQL_USER: vikunja
MYSQL_PASSWORD: secret MYSQL_PASSWORD: secret
MYSQL_DATABASE: vikunja MYSQL_DATABASE: vikunja
@ -360,7 +385,7 @@ services:
VIKUNJA_DATABASE_DATABASE: vikunja VIKUNJA_DATABASE_DATABASE: vikunja
VIKUNJA_SERVICE_JWTSECRET: <a super secure random secret> VIKUNJA_SERVICE_JWTSECRET: <a super secure random secret>
VIKUNJA_SERVICE_FRONTENDURL: https://<your public frontend url with slash>/ VIKUNJA_SERVICE_FRONTENDURL: https://<your public frontend url with slash>/
volumes: volumes:
- ./files:/app/vikunja/files - ./files:/app/vikunja/files
depends_on: depends_on:
- db - db
@ -406,8 +431,8 @@ To do that, you can
* without activating SSH as a "custom script" (go to Control Panel / Task Scheduler / Create / Scheduled Task / User-defined script) * without activating SSH as a "custom script" (go to Control Panel / Task Scheduler / Create / Scheduled Task / User-defined script)
* without activating SSH, by using Portainer (you have to install first, check out [this tutorial](https://www.portainer.io/blog/how-to-install-portainer-on-a-synology-nas) for exmple): * without activating SSH, by using Portainer (you have to install first, check out [this tutorial](https://www.portainer.io/blog/how-to-install-portainer-on-a-synology-nas) for exmple):
1. Go to **Dashboard / Stacks** click the button **"Add Stack"** 1. Go to **Dashboard / Stacks** click the button **"Add Stack"**
2. Give it the name Vikunja and paste the adapted docker compose file 2. Give it the name Vikunja and paste the adapted docker compose file
3. Deploy the Stack with the "Deploy Stack" button: 3. Deploy the Stack with the "Deploy Stack" button:
![Portainer Stack deploy](/docs/synology-proxy-2.png) ![Portainer Stack deploy](/docs/synology-proxy-2.png)
@ -459,4 +484,3 @@ You may want to change the volumes to match the rest of your setup.
Once deployed, you might want to change the [`PUID` and `GUID` settings]({{< ref "install-backend.md">}}#setting-user-and-group-id-of-the-user-running-vikunja) or [set the time zone]({{< ref "config.md">}}#timezone). Once deployed, you might want to change the [`PUID` and `GUID` settings]({{< ref "install-backend.md">}}#setting-user-and-group-id-of-the-user-running-vikunja) or [set the time zone]({{< ref "config.md">}}#timezone).
After registering all your users, you might also want to [disable the user registration]({{<ref "config.md">}}#enableregistration). After registering all your users, you might also want to [disable the user registration]({{<ref "config.md">}}#enableregistration).

View File

@ -102,7 +102,7 @@ It will automatically run all necessary database migrations.
## Docker ## Docker
(Note: this assumes some familarity with docker) (Note: this assumes some familiarity with docker)
Usage with docker is pretty straightforward: Usage with docker is pretty straightforward:
@ -119,7 +119,7 @@ You can mount a local configuration like so:
docker run -p 3456:3456 -v /path/to/config/on/host.yml:/app/vikunja/config.yml:ro vikunja/api docker run -p 3456:3456 -v /path/to/config/on/host.yml:/app/vikunja/config.yml:ro vikunja/api
{{< /highlight >}} {{< /highlight >}}
Though it is recommended to use eviroment variables or `.env` files to configure Vikunja in docker. Though it is recommended to use environment variables or `.env` files to configure Vikunja in docker.
See [config]({{< ref "config.md">}}) for a list of available configuration options. See [config]({{< ref "config.md">}}) for a list of available configuration options.
### Files volume ### Files volume
@ -129,7 +129,7 @@ You should mount the volume somewhere to the host to permanently store the files
### Setting user and group id of the user running vikunja ### Setting user and group id of the user running vikunja
You can set the user and group id of the user running vikunja with the `PUID` and `PGID` evironment variables. You can set the user and group id of the user running vikunja with the `PUID` and `PGID` environment variables.
This follows the pattern used by [the linuxserver.io](https://docs.linuxserver.io/general/understanding-puid-and-pgid) docker images. This follows the pattern used by [the linuxserver.io](https://docs.linuxserver.io/general/understanding-puid-and-pgid) docker images.
This is useful to solve general permission problems when host-mounting volumes such as the volume used for task attachments. This is useful to solve general permission problems when host-mounting volumes such as the volume used for task attachments.
@ -164,7 +164,7 @@ services:
- ./db:/var/lib/mysql - ./db:/var/lib/mysql
{{< /highlight >}} {{< /highlight >}}
See [full docker example]({{< ref "full-docker-example.md">}}) for more varations of this config. See [full docker example]({{< ref "full-docker-example.md">}}) for more variations of this config.
## Debian packages ## Debian packages

View File

@ -24,8 +24,7 @@ You also need to configure a rewrite condition to internally redirect all reques
By default, the frontend assumes it can reach the api at `/api/v1` relative to the frontend url. By default, the frontend assumes it can reach the api at `/api/v1` relative to the frontend url.
This means that if you make the frontend available at, say `https://vikunja.example.com`, it tries to reach the api This means that if you make the frontend available at, say `https://vikunja.example.com`, it tries to reach the api
at `https://vikunja.example.com/api/v1`. at `https://vikunja.example.com/api/v1`.
In this scenario it is not possible for the frontend and the api to live on seperate servers or even just seperate In this scenario it is not possible for the frontend and the api to live on separate servers or even just separate ports on the same server with [the use of a reverse proxy]({{< ref "reverse-proxies.md">}}).
ports on the same server with [the use of a reverse proxy]({{< ref "reverse-proxies.md">}}).
To make configurations like this possible, the api url can be set in the `index.html` file of the frontend releases. To make configurations like this possible, the api url can be set in the `index.html` file of the frontend releases.
Just open the file with a text editor - there are comments which will explain how to set the url. Just open the file with a text editor - there are comments which will explain how to set the url.
@ -45,7 +44,7 @@ docker run -p 80:80 vikunja/frontend
which will run the docker image and expose port 80 on the host. which will run the docker image and expose port 80 on the host.
See [full docker example]({{< ref "full-docker-example.md">}}) for more varations of this config. See [full docker example]({{< ref "full-docker-example.md">}}) for more variations of this config.
The docker container runs as an unprivileged user and does not mount anything. The docker container runs as an unprivileged user and does not mount anything.

View File

@ -8,8 +8,15 @@ menu:
parent: "setup" parent: "setup"
--- ---
# Hosting Vikunja with k8s
We have an official Helm Chart for Vikunja.
Check out [the repo](https://kolaente.dev/vikunja/helm-chart/) for more information about how to use it.
## Third-party Helm Charts
There are two third-party Helm-Charts which can be used to host Vikunja with k8s: There are two third-party Helm-Charts which can be used to host Vikunja with k8s:
* [Truecharts](https://truecharts.org/charts/stable/vikunja/) * [Truecharts](https://truecharts.org/charts/stable/vikunja/)
* [k8s at Home](https://github.com/k8s-at-home/charts) * [k8s at Home](https://github.com/k8s-at-home/charts)

View File

@ -61,8 +61,30 @@ openid:
Google config: Google config:
- Navigate to https://console.cloud.google.com/apis/credentials in the target project - Navigate to `https://console.cloud.google.com/apis/credentials` in the target project
- Create a new OAuth client ID - Create a new OAuth client ID
- Configure an authorized redirect URI of https://vikunja.mydomain.com/auth/openid/google - 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. 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,127 +1,316 @@
--- ---
date: "2019-02-12:00:00+02:00" date: "2019-02-12:00:00+02:00"
title: "Reverse Proxy" title: "Reverse Proxy"
draft: false draft: false
type: "doc" type: "doc"
menu: menu:
sidebar: sidebar:
parent: "setup" parent: "setup"
--- ---
# Setup behind a reverse proxy which also serves the frontend # 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`. 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. If you've changed this setting, you need to update the server configurations accordingly.
{{< table_of_contents >}} {{< table_of_contents >}}
## NGINX ## NGINX
Below are two example configurations which you can put in your `nginx.conf`: Below are two example configurations which you can put in your `nginx.conf`:
You may need to adjust `server_name` and `root` accordingly. You may need to adjust `server_name` and `root` accordingly.
### with gzip enabled (recommended) ### with gzip enabled (recommended)
{{< highlight conf >}} {{< highlight conf >}}
gzip on; gzip on;
gzip_disable "msie6"; gzip_disable "msie6";
gzip_vary on; gzip_vary on;
gzip_proxied any; gzip_proxied any;
gzip_comp_level 6; gzip_comp_level 6;
gzip_buffers 16 8k; gzip_buffers 16 8k;
gzip_http_version 1.1; gzip_http_version 1.1;
gzip_min_length 256; 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; 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 { server {
listen 80; listen 80;
server_name localhost; server_name localhost;
location / { location / {
root /path/to/vikunja/static/frontend/files; root /path/to/vikunja/static/frontend/files;
try_files $uri $uri/ /; try_files $uri $uri/ /;
index index.html index.htm; index index.html index.htm;
} }
location ~* ^/(api|dav|\.well-known)/ { location ~* ^/(api|dav|\.well-known)/ {
proxy_pass http://localhost:3456; proxy_pass http://localhost:3456;
client_max_body_size 20M; client_max_body_size 20M;
} }
} }
{{< /highlight >}} {{< /highlight >}}
<div class="notification is-warning"> <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. <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> </div>
### without gzip ### without gzip
{{< highlight conf >}} {{< highlight conf >}}
server { server {
listen 80; listen 80;
server_name localhost; server_name localhost;
location / { location / {
root /path/to/vikunja/static/frontend/files; root /path/to/vikunja/static/frontend/files;
try_files $uri $uri/ /; try_files $uri $uri/ /;
index index.html index.htm; index index.html index.htm;
} }
location ~* ^/(api|dav|\.well-known)/ { location ~* ^/(api|dav|\.well-known)/ {
proxy_pass http://localhost:3456; proxy_pass http://localhost:3456;
client_max_body_size 20M; client_max_body_size 20M;
} }
} }
{{< /highlight >}} {{< /highlight >}}
<div class="notification is-warning"> <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. <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> </div>
## NGINX Proxy Manager (NPM) ## 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). ### Method 1
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 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.
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.) From there, all you have to do is adjust the following things:
```
location ~* ^/(api|dav|\.well-known)/ { #### In `docker-compose.yml`
proxy_pass http://api:3456;
client_max_body_size 20M; Under `api:`,
}
``` 1. Change `VIKUNJA_SERVICE_FRONTENDURL:` to your desired domain with `https://` and `/`.
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 browswer 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. 2. Expose your desired port on host under `ports:`.
## Apache example:
Put the following config in `cat /etc/apache2/sites-available/vikunja.conf`: ```yaml
api:
{{< highlight aconf >}} image: vikunja/api
<VirtualHost *:80> environment:
ServerName localhost VIKUNJA_DATABASE_HOST: db
VIKUNJA_DATABASE_PASSWORD: secret
<Proxy *> VIKUNJA_DATABASE_TYPE: mysql
Order Deny,Allow VIKUNJA_DATABASE_USER: vikunja
Allow from all VIKUNJA_DATABASE_DATABASE: vikunja
</Proxy> VIKUNJA_SERVICE_JWTSECRET: <your-random-secret>
ProxyPass /api http://localhost:3456/api VIKUNJA_SERVICE_FRONTENDURL: https://vikunja.your-domain.com/ # change vikunja.your-domain.com to your desired domain/subdomain.
ProxyPassReverse /api http://localhost:3456/api ports:
ProxyPass /dav http://localhost:3456/dav - 3456:3456 # Change 3456 on the left to the port of your choice.
ProxyPassReverse /dav http://localhost:3456/dav volumes:
ProxyPass /.well-known http://localhost:3456/.well-known - ./files:/app/vikunja/files
ProxyPassReverse /.well-known http://localhost:3456/.well-known depends_on:
- db
DocumentRoot /var/www/html restart: unless-stopped
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] Under `frontend:`,
</VirtualHost>
{{< /highlight >}} 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:`.
**Note:** The apache modules `proxy`, `proxy_http` and `rewrite` must be enabled for this. example:
For more details see the [frontend apache configuration]({{< ref "install-frontend.md#apache">}}). ```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 >}}

View File

@ -0,0 +1,23 @@
---
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

@ -11,11 +11,9 @@ menu:
# UTF-8 Settings # UTF-8 Settings
Vikunja itself is always fully capable of handling utf-8 characters. Vikunja itself is always fully capable of handling utf-8 characters.
However, your database might be not. However, your database might be not. Vikunja itself will work just fine until you want to use non-latin characters in your tasks/projects/etc.
Vikunja itself will work just fine until you want to use non-latin characters in your tasks/projects/etc.
On this page, you will find information about how to fully ensure non-latin characters like aüäß or emojis work On this page, you will find information about how to fully ensure non-latin characters like *aüäß* or emojis work with your installation.
with your installation.
{{< table_of_contents >}} {{< table_of_contents >}}
@ -57,8 +55,7 @@ Before attempting any conversion, please [back up your database]({{< ref "backup
### 1. Create a pre-conversion script ### 1. Create a pre-conversion script
Copy the following sql statements in a file called `preAlterTables.sql` and replace all occurences of `vikunja` with Copy the following sql statements in a file called `preAlterTables.sql` and replace all occurrences of `vikunja` with the name of your database:
the name of your database:
{{< highlight sql >}} {{< highlight sql >}}
use information_schema; use information_schema;

View File

@ -19,7 +19,7 @@ The Vikunja api and frontend are available in two different release flavors.
Stable releases have a fixed version number like `0.18.2` and are published at irregular intervals whenever a new version is ready. Stable releases have a fixed version number like `0.18.2` and are published at irregular intervals whenever a new version is ready.
They receive few bugfixes and security patches. They receive few bugfixes and security patches.
We use [Semantic Versioning](#) for these releases. We use [Semantic Versioning](https://semver.org) for these releases.
## Unstable ## Unstable

View File

@ -10,10 +10,10 @@ menu:
# API Documentation # API Documentation
You can find the api docs under `http://vikunja.tld/api/v1/docs` of your instance. You can find the api docs under `http://vikunja.tld/api/v1/docs` of your instance.<br />
A public instance is available on [try.vikunja.io](https://try.vikunja.io/api/v1/docs). A public instance is available on [try.vikunja.io](https://try.vikunja.io/api/v1/docs).
These docs are autgenerated from annotations in the code with swagger. These docs are autogenerated from annotations in the code with swagger.
The specification is hosted at `http://vikunja.tld/api/v1/docs.json`. The specification is hosted at `http://vikunja.tld/api/v1/docs.json`.
You can use this to embed it into other openapi compatible applications if you want. You can use this to embed it into other OpenAPI compatible applications if you want.

View File

@ -1,6 +1,6 @@
--- ---
date: "2019-05-12:00:00+01:00" date: "2019-05-12:00:00+01:00"
title: "Caldav" title: "CalDAV"
draft: false draft: false
type: "doc" type: "doc"
menu: menu:
@ -8,9 +8,9 @@ menu:
parent: "usage" parent: "usage"
--- ---
# Caldav # CalDAV
> **Warning:** The caldav integration is in an early alpha stage and has bugs. > **Warning:** The CalDAV integration is in an early alpha stage and has bugs.
> It works well with some clients while having issues with others. > It works well with some clients while having issues with others.
> If you encounter issues, please [report them](https://code.vikunja.io/api/issues/new?body=[caldav]) > If you encounter issues, please [report them](https://code.vikunja.io/api/issues/new?body=[caldav])
@ -26,8 +26,8 @@ Urls are:
* `/principals/<username>/`: Returns urls for project discovery. *Use this url to initially make connections to new clients.* * `/principals/<username>/`: Returns urls for project discovery. *Use this url to initially make connections to new clients.*
* `/projects/`: Used to manage projects * `/projects/`: Used to manage projects
* `/projects/<List ID>/`: Used to manage a single project * `/projects/<Project ID>/`: Used to manage a single project
* `/projects/<List ID>/<Task UID>`: Used to manage a task on a project * `/projects/<Project ID>/<Task UID>`: Used to manage a task on a project
## Supported properties ## Supported properties
@ -39,47 +39,48 @@ Vikunja currently supports the following properties:
* `PRIORITY` * `PRIORITY`
* `CATEGORIES` * `CATEGORIES`
* `COMPLETED` * `COMPLETED`
* `CREATED` (only Vikunja → Client)
* `DUE` * `DUE`
* `DTSTART`
* `DURATION` * `DURATION`
* `ORGANIZER`
* `RELATED-TO`
* `CREATED`
* `DTSTAMP` * `DTSTAMP`
* `LAST-MODIFIED` * `DTSTART`
* Recurrence * `LAST-MODIFIED` (only Vikunja → Client)
* `RRULE` (Recurrence) (only Vikunja → Client)
* `VALARM` (Reminders)
Vikunja **currently does not** support these properties: Vikunja **currently does not** support these properties:
* `ATTACH` * `ATTACH`
* `CLASS` * `CLASS`
* `COMMENT` * `COMMENT`
* `CONTACT`
* `GEO` * `GEO`
* `LOCATION` * `LOCATION`
* `ORGANIZER` (disabled)
* `PERCENT-COMPLETE` * `PERCENT-COMPLETE`
* `RESOURCES`
* `STATUS`
* `CONTACT`
* `RECURRENCE-ID` * `RECURRENCE-ID`
* `URL` * `RELATED-TO`
* `RESOURCES`
* `SEQUENCE` * `SEQUENCE`
* `STATUS`
* `URL`
## Tested Clients ## Tested Clients
### Working ### Working
* [Evolution](https://wiki.gnome.org/Apps/Evolution/) * [Evolution](https://wiki.gnome.org/Apps/Evolution/)
* [OpenTasks](https://opentasks.app/) + [DAVx⁵](https://www.davx5.com/) * [OpenTasks](https://opentasks.app/) & [DAVx⁵](https://www.davx5.com/)
* [Tasks (Android)](https://tasks.org/) * [Tasks (Android)](https://tasks.org/)
### Not working ### Not working
* [Thunderbird (68)](https://www.thunderbird.net/) * [Thunderbird (68)](https://www.thunderbird.net/)
* iOS calDAV Sync (See [#753](https://kolaente.dev/vikunja/api/issues/753)) * iOS CalDAV Sync (See [#753](https://kolaente.dev/vikunja/api/issues/753))
## Dev logs ## Dev logs
The whole thing is not optimized at all and probably pretty inefficent. The whole thing is not optimized at all and probably pretty inefficient.
Request body and headers are logged if the debug output is enabled. Request body and headers are logged if the debug output is enabled.
@ -140,6 +141,4 @@ Requests from the app:::
And then it just stops. And then it just stops.
... and complains about not being able to find the home set ... and complains about not being able to find the home set
... without even requesting it... ... without even requesting it...
``` ```

View File

@ -10,7 +10,7 @@ menu:
# Command line interface # Command line interface
You can interact with Vikunja using its `cli` interface. You can interact with Vikunja using its `cli` interface.<br />
The following commands are available: The following commands are available:
* [dump](#dump) * [dump](#dump)
@ -31,7 +31,7 @@ All commands use the same standard [config file]({{< ref "../setup/config.md">}}
When running Vikunja in docker, you'll need to execute all commands in the `api` container. When running Vikunja in docker, you'll need to execute all commands in the `api` container.
Instead of running the `vikunja` binary directly, run it like this: Instead of running the `vikunja` binary directly, run it like this:
``` ```sh
docker exec <name of the vikunja api container> /app/vikunja/vikunja <subcommand> docker exec <name of the vikunja api container> /app/vikunja/vikunja <subcommand>
``` ```
@ -80,12 +80,12 @@ Roll migrations back until a certain point.
Usage: Usage:
{{< highlight bash >}} {{< highlight bash >}}
$ vikunja migrate rollback [flags] $ vikunja migrate rollback [flags]
{{< /highlight >}} {{< /highlight >}}
Flags: Flags:
* `-n`, `--name` string: The id of the migration you want to roll back until. * `-n`, `--name` string: The id of the migration you want to roll back until.
### `restore` ### `restore`
Restores a previously created dump from a zip file, see `dump`. Restores a previously created dump from a zip file, see `dump`.
@ -138,9 +138,9 @@ Flags:
#### `user delete` #### `user delete`
Start the user deletion process. Start the user deletion process.
If called without the `--now` flag, this command will only trigger an email to the user in order for them to confirm and start the deletion process (this is the same behavoir as if the user requested their deletion via the web interface). If called without the `--now` flag, this command will only trigger an email to the user in order for them to confirm and start the deletion process (this is the same behavoir as if the user requested their deletion via the web interface).
With the flag the user is deleted **immediately**. With the flag the user is deleted **immediately**.
**USE WITH CAUTION.** **USE WITH CAUTION.**
@ -194,7 +194,7 @@ This is either the semantic version (something like `0.7`) or version + git comm
Usage: Usage:
{{< highlight bash >}} {{< highlight bash >}}
$ vikunja version $ vikunja version
{{< /highlight >}} {{< /highlight >}}
### `web` ### `web`
@ -203,5 +203,5 @@ Starts Vikunja's REST api server.
Usage: Usage:
{{< highlight bash >}} {{< highlight bash >}}
$ vikunja web $ vikunja web
{{< /highlight >}} {{< /highlight >}}

View File

@ -54,16 +54,19 @@ This document describes the different errors Vikunja can return.
## Project ## Project
| ErrorCode | HTTP Status Code | Description | | ErrorCode | HTTP Status Code | Description |
|-----------|------------------|-------------------------------------------------------------------------------------------------------------------------------| |-----------|------------------|-------------------------------------------------------------------------------------------------------------------------------------|
| 3001 | 404 | The project does not exist. | | 3001 | 404 | The project does not exist. |
| 3004 | 403 | The user needs to have read permissions on that project to perform that action. | | 3004 | 403 | The user needs to have read permissions on that project to perform that action. |
| 3005 | 400 | The project title cannot be empty. | | 3005 | 400 | The project title cannot be empty. |
| 3006 | 404 | The project share does not exist. | | 3006 | 404 | The project share does not exist. |
| 3007 | 400 | A project with this identifier already exists. | | 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. | | 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 namespace like "Favorites". | | 3009 | 412 | The project cannot belong to a dynamically generated parent project like "Favorites". |
| 3010 | 412 | The project must belong to a namespace. | | 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. |
## Task ## Task
@ -77,7 +80,7 @@ This document describes the different errors Vikunja can return.
| 4006 | 403 | The user tried to set a parent task as the task itself. | | 4006 | 403 | The user tried to set a parent task as the task itself. |
| 4007 | 400 | The user tried to create a task relation with an invalid kind of relation. | | 4007 | 400 | The user tried to create a task relation with an invalid kind of relation. |
| 4008 | 409 | The user tried to create a task relation which already exists. | | 4008 | 409 | The user tried to create a task relation which already exists. |
| 4009 | 404 | The task relation does not exist. | | 4009 | 404 | The task relation does not exist. |
| 4010 | 400 | Cannot relate a task with itself. | | 4010 | 400 | Cannot relate a task with itself. |
| 4011 | 404 | The task attachment does not exist. | | 4011 | 404 | The task attachment does not exist. |
| 4012 | 400 | The task attachment is too large. | | 4012 | 400 | The task attachment is too large. |
@ -90,28 +93,17 @@ This document describes the different errors Vikunja can return.
| 4019 | 400 | Invalid task filter value. | | 4019 | 400 | Invalid task filter value. |
| 4020 | 400 | The provided attachment does not belong to that task. | | 4020 | 400 | The provided attachment does not belong to that task. |
| 4021 | 400 | This user is already assigned to that task. | | 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 namspace 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 ## Team
| ErrorCode | HTTP Status Code | Description | | ErrorCode | HTTP Status Code | Description |
|-----------|------------------|-------------| |-----------|------------------|----------------------------------------------------------------------|
| 6001 | 400 | The team name cannot be emtpy. | | 6001 | 400 | The team name cannot be empty. |
| 6002 | 404 | The team does not exist. | | 6002 | 404 | The team does not exist. |
| 6004 | 409 | The team already has access to that namespace or project. | | 6004 | 409 | The team already has access to that project. |
| 6005 | 409 | The user is already a member of that team. | | 6005 | 409 | The user is already a member of that team. |
| 6006 | 400 | Cannot delete the last team member. | | 6006 | 400 | Cannot delete the last team member. |
| 6007 | 403 | The team does not have access to the project to perform that action. | | 6007 | 403 | The team does not have access to the project to perform that action. |
## User Project Access ## User Project Access
@ -133,7 +125,7 @@ This document describes the different errors Vikunja can return.
| ErrorCode | HTTP Status Code | Description | | ErrorCode | HTTP Status Code | Description |
|-----------|------------------|-------------| |-----------|------------------|-------------|
| 9001 | 403 | The right is invalid. | | 9001 | 403 | The right is invalid. |
## Kanban ## Kanban
@ -150,7 +142,7 @@ This document describes the different errors Vikunja can return.
| ErrorCode | HTTP Status Code | Description | | ErrorCode | HTTP Status Code | Description |
|-----------|------------------|-------------| |-----------|------------------|-------------|
| 11001 | 404 | The saved filter does not exist. | | 11001 | 404 | The saved filter does not exist. |
| 11002 | 412 | Saved filters are not available for link shares. | | 11002 | 412 | Saved filters are not available for link shares. |
## Subscriptions ## Subscriptions
@ -161,7 +153,8 @@ This document describes the different errors Vikunja can return.
## Link Shares ## Link Shares
| ErrorCode | HTTP Status Code | Description | | ErrorCode | HTTP Status Code | Description |
|-----------|------------------|-------------| |-----------|------------------|--------------------------------------------------------------------------------|
| 13001 | 412 | This link share requires a password for authentication, but none was provided. | | 13001 | 412 | This link share requires a password for authentication, but none was provided. |
| 13002 | 403 | The provided link share password was invalid. | | 13002 | 403 | The provided link share password is invalid. |
| 13003 | 400 | The provided link share token is invalid. |

View File

@ -0,0 +1,43 @@
---
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

@ -10,16 +10,16 @@ menu:
# Available task relation kinds # Available task relation kinds
| Code | Description | | Code | Description | Opposite of |
|------|-------------| |------|-------------|-------------|
| subtask | Task is a subtask of the other task. This is the opposite of `parenttask`. | | `subtask` | Task is a subtask of the other task. | `parenttask` |
| parenttask | Task is a parent task of the other task. This is the opposite of `subtask`. | | `parenttask` | Task is a parent task of the other task. | `subtask` |
| related | Both tasks are related to each other. How is not more specified. | | `related` | Both tasks are related to each other.<br /> How is not more specified. | ⸺ |
| duplicateof | Task is a duplicate of the other task. This is the opposite of `duplicates`. | | `duplicateof` | Task is a duplicate of the other task. | `duplicates` |
| duplicates | Task duplicates the other task. This is the opposite of `duplicateof`. | | `duplicates` | Task duplicates the other task. | `duplicateof` |
| blocking | Task is blocking the other task. This is the opposite of `blocked`. | | `blocking` | Task is blocking the other task. | `blocked` |
| blocked | Task is blocked by the other task. This is the opposite of `blocking`. | | `blocked` | Task is blocked by the other task. | `blocking` |
| precedes | Task precedes the other task. This is the opposite of `follows`. | | `precedes` | Task precedes the other task. | `follows` |
| follows | Task follows the other task. This is the opposite of `precedes`. | | `follows` | Task follows the other task. | `precedes` |
| copiedfrom | Task is copied from the other task. This is the opposite of `copiedto`. | | `copiedfrom` | Task is copied from the other task. | `copiedto` |
| copiedto | Task is copied to the other task. This is the opposite of `copiedfrom`. | | `copiedto` | Task is copied to the other task. | `copiedfrom` |

View File

@ -8,22 +8,22 @@ menu:
parent: "usage" parent: "usage"
--- ---
# Project and namespace rights for teams and users # Project rights for teams and users
Whenever you share a project or namespace with a user or team, you can specify a `rights` parameter. Whenever you share a project 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). 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. Rights are being specified using integers.
The following values are possible: The following values are possible:
| Right (int) | Meaning | | Right (int) | Meaning |
|-------------|---------------------------------------------------------------------------------------------------------------| |-------------|-------------------------------------------------------------------------------------------------|
| 0 (Default) | Read only. Anything which is shared with this right cannot be edited. | | 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. | | 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. | | 2 | Admin. Can do anything like read and write, but can additionally manage sharing options. |
## Team admins ## Team admins
When adding or querying a team, every member has an additional boolean value stating if it is admin or not. When adding or querying a team, every member has an additional boolean value stating if it is admin or not.
A team admin can also add and remove team members and also change whether a user in the team is admin or not. A team admin can also add and remove team members and also change whether a user in the team is admin or not.

View File

@ -0,0 +1,58 @@
---
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.

170
go.mod
View File

@ -18,104 +18,121 @@ module code.vikunja.io/api
require ( require (
code.vikunja.io/web v0.0.0-20210706160506-d85def955bd3 code.vikunja.io/web v0.0.0-20210706160506-d85def955bd3
gitea.com/xorm/xorm-redis-cache v0.2.0 dario.cat/mergo v1.0.0
github.com/ThreeDotsLabs/watermill v1.2.0 github.com/ThreeDotsLabs/watermill v1.3.5
github.com/adlio/trello v1.10.0 github.com/adlio/trello v1.10.0
github.com/arran4/golang-ical v0.0.0-20230318005454-19abf92700cc github.com/arran4/golang-ical v0.1.0
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef
github.com/bbrks/go-blurhash v1.1.1 github.com/bbrks/go-blurhash v1.1.1
github.com/c2h5oh/datasize v0.0.0-20220606134207-859f65c6625b github.com/c2h5oh/datasize v0.0.0-20220606134207-859f65c6625b
github.com/coreos/go-oidc/v3 v3.5.0 github.com/coreos/go-oidc/v3 v3.7.0
github.com/cweill/gotests v1.6.0 github.com/cweill/gotests v1.6.0
github.com/d4l3k/messagediff v1.2.1 github.com/d4l3k/messagediff v1.2.1
github.com/disintegration/imaging v1.6.2 github.com/disintegration/imaging v1.6.2
github.com/dustinkirkland/golang-petname v0.0.0-20191129215211-8e5a1ed0cff0 github.com/dustinkirkland/golang-petname v0.0.0-20231002161417-6a283f1aaaf2
github.com/gabriel-vasile/mimetype v1.4.2 github.com/gabriel-vasile/mimetype v1.4.3
github.com/getsentry/sentry-go v0.19.0 github.com/getsentry/sentry-go v0.25.0
github.com/go-sql-driver/mysql v1.7.0 github.com/go-sql-driver/mysql v1.7.1
github.com/go-testfixtures/testfixtures/v3 v3.8.1 github.com/go-testfixtures/testfixtures/v3 v3.9.0
github.com/gocarina/gocsv v0.0.0-20230226133904-70c27cb2918a github.com/gocarina/gocsv v0.0.0-20230616125104-99d496ca653d
github.com/golang-jwt/jwt/v4 v4.5.0 github.com/golang-jwt/jwt/v5 v5.0.0
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
github.com/google/uuid v1.3.0 github.com/google/uuid v1.4.0
github.com/iancoleman/strcase v0.2.0 github.com/hashicorp/go-version v1.6.0
github.com/imdario/mergo v0.3.14 github.com/iancoleman/strcase v0.3.0
github.com/jinzhu/copier v0.3.5 github.com/jinzhu/copier v0.4.0
github.com/labstack/echo-jwt/v4 v4.1.0 github.com/jszwedko/go-datemath v0.1.1-0.20230526204004-640a500621d6
github.com/labstack/echo/v4 v4.10.2 github.com/labstack/echo-jwt/v4 v4.2.0
github.com/labstack/echo/v4 v4.11.2
github.com/labstack/gommon v0.4.0 github.com/labstack/gommon v0.4.0
github.com/lib/pq v1.10.7 github.com/lib/pq v1.10.9
github.com/magefile/mage v1.14.0 github.com/magefile/mage v1.15.0
github.com/mattn/go-sqlite3 v1.14.16 github.com/mattn/go-sqlite3 v1.14.17
github.com/olekukonko/tablewriter v0.0.5 github.com/olekukonko/tablewriter v0.0.5
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
github.com/pquerna/otp v1.4.0 github.com/pquerna/otp v1.4.0
github.com/prometheus/client_golang v1.14.0 github.com/prometheus/client_golang v1.17.0
github.com/redis/go-redis/v9 v9.0.2 github.com/redis/go-redis/v9 v9.2.1
github.com/robfig/cron/v3 v3.0.1 github.com/robfig/cron/v3 v3.0.1
github.com/samedi/caldav-go v3.0.0+incompatible github.com/samedi/caldav-go v3.0.0+incompatible
github.com/spf13/afero v1.9.5 github.com/spf13/afero v1.10.0
github.com/spf13/cobra v1.6.1 github.com/spf13/cobra v1.7.0
github.com/spf13/viper v1.15.0 github.com/spf13/viper v1.17.0
github.com/stretchr/testify v1.8.2 github.com/stretchr/testify v1.8.4
github.com/swaggo/swag v1.8.11 github.com/swaggo/swag v1.16.2
github.com/tkuchiki/go-timezone v0.2.2 github.com/tkuchiki/go-timezone v0.2.2
github.com/ulule/limiter/v3 v3.11.1 github.com/typesense/typesense-go v0.8.0
github.com/vectordotdev/go-datemath v0.1.1-0.20220323213446-f3954d0b18ae github.com/ulule/limiter/v3 v3.11.2
github.com/wneessen/go-mail v0.3.8 github.com/wneessen/go-mail v0.4.0
github.com/yuin/goldmark v1.5.4 github.com/yuin/goldmark v1.5.6
golang.org/x/crypto v0.7.0 golang.org/x/crypto v0.14.0
golang.org/x/image v0.6.0 golang.org/x/image v0.13.0
golang.org/x/oauth2 v0.6.0 golang.org/x/oauth2 v0.13.0
golang.org/x/sync v0.1.0 golang.org/x/sync v0.4.0
golang.org/x/sys v0.6.0 golang.org/x/sys v0.13.0
golang.org/x/term v0.6.0 golang.org/x/term v0.13.0
gopkg.in/d4l3k/messagediff.v1 v1.2.1 gopkg.in/d4l3k/messagediff.v1 v1.2.1
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
src.techknowlogick.com/xgo v1.7.1-0.20230307171022-b60708668fc7 src.techknowlogick.com/xgo v1.7.1-0.20231019133136-ecfba3dfed5d
src.techknowlogick.com/xormigrate v1.5.0 src.techknowlogick.com/xormigrate v1.7.0
xorm.io/builder v0.3.12 xorm.io/builder v0.3.13
xorm.io/xorm v1.3.2 xorm.io/xorm v1.3.4
) )
require ( 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/KyleBanks/depth v1.2.1 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // 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/beevik/etree v1.1.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // 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/cenkalti/backoff/v3 v3.2.2 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // 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/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/deepmap/oapi-codegen v1.13.4 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/garyburd/redigo v1.6.0 // indirect github.com/gin-contrib/sse v0.1.0 // indirect
github.com/ghodss/yaml v1.0.0 // indirect github.com/gin-gonic/gin v1.9.1 // indirect
github.com/go-chi/chi/v5 v5.0.8 // 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/go-jose/go-jose/v3 v3.0.0 // indirect github.com/go-jose/go-jose/v3 v3.0.0 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonpointer v0.20.0 // indirect
github.com/go-openapi/jsonreference v0.19.6 // indirect github.com/go-openapi/jsonreference v0.19.6 // indirect
github.com/go-openapi/spec v0.20.4 // indirect github.com/go-openapi/spec v0.20.4 // indirect
github.com/go-openapi/swag v0.19.15 // indirect github.com/go-openapi/swag v0.22.4 // indirect
github.com/goccy/go-json v0.9.11 // 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/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/golang/protobuf v1.5.2 // indirect github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.4 // indirect github.com/golang/snappy v0.0.4 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.0.1 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/josharian/intern v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // 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/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/lithammer/shortuuid/v3 v3.0.7 // indirect
github.com/magiconair/properties v1.8.7 // indirect github.com/magiconair/properties v1.8.7 // indirect
github.com/mailru/easyjson v0.7.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mattn/go-runewidth v0.0.9 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
@ -123,32 +140,51 @@ require (
github.com/oklog/ulid v1.3.1 // indirect github.com/oklog/ulid v1.3.1 // indirect
github.com/onsi/ginkgo v1.16.4 // indirect github.com/onsi/ginkgo v1.16.4 // indirect
github.com/onsi/gomega v1.16.0 // indirect github.com/onsi/gomega v1.16.0 // indirect
github.com/pelletier/go-toml/v2 v2.0.6 // 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/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_model v0.3.0 // indirect github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect
github.com/prometheus/common v0.39.0 // indirect github.com/prometheus/common v0.44.0 // indirect
github.com/prometheus/procfs v0.9.0 // indirect github.com/prometheus/procfs v0.11.1 // indirect
github.com/rivo/uniseg v0.4.4 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/spf13/cast v1.5.0 // indirect github.com/sagikazarmark/locafero v0.3.0 // indirect
github.com/spf13/jwalterweatherman v1.1.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/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.4.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect
github.com/syndtr/goleveldb v1.0.0 // 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/urfave/cli/v2 v2.3.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect
golang.org/x/mod v0.8.0 // indirect github.com/yosssi/gohtml v0.0.0-20201013000340-ee4748c638f4 // indirect
golang.org/x/net v0.8.0 // indirect go.opentelemetry.io/otel v1.15.0 // indirect
golang.org/x/text v0.8.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/time v0.3.0 // indirect golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.6.0 // indirect golang.org/x/tools v0.13.0 // indirect
google.golang.org/appengine v1.6.7 // indirect google.golang.org/appengine v1.6.8 // indirect
google.golang.org/protobuf v1.28.1 // indirect google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.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 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.20 go 1.21
toolchain go1.21.2

847
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. // Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved. // Copyright 2018-present Vikunja and contributors. All rights reserved.
// //
// This program is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU Affero General Public Licensee as published by
@ -195,7 +195,11 @@ func runAndStreamOutput(cmd string, args ...string) {
stdout, _ := c.StdoutPipe() stdout, _ := c.StdoutPipe()
errbuf := bytes.Buffer{} errbuf := bytes.Buffer{}
c.Stderr = &errbuf c.Stderr = &errbuf
c.Start() err := c.Start()
if err != nil {
fmt.Printf("Could not start: %s\n", err)
os.Exit(1)
}
reader := bufio.NewReader(stdout) reader := bufio.NewReader(stdout)
line, err := reader.ReadString('\n') line, err := reader.ReadString('\n')
@ -337,9 +341,12 @@ func Fmt() {
runAndStreamOutput("gofmt", args...) runAndStreamOutput("gofmt", args...)
} }
const swaggerDocsFolderLocation = `./pkg/swagger/`
// Generates the swagger docs from the code annotations // Generates the swagger docs from the code annotations
func DoTheSwag() { func DoTheSwag() {
mg.Deps(initVars) mg.Deps(initVars)
checkAndInstallGoTool("swag", "github.com/swaggo/swag/cmd/swag") checkAndInstallGoTool("swag", "github.com/swaggo/swag/cmd/swag")
runAndStreamOutput("swag", "init", "-g", "./pkg/routes/routes.go", "--parseDependency", "-d", RootPath, "-o", RootPath+"/pkg/swagger") runAndStreamOutput("swag", "init", "-g", "./pkg/routes/routes.go", "--parseDependency", "-d", RootPath, "-o", RootPath+"/pkg/swagger")
} }
@ -405,7 +412,7 @@ func checkGolangCiLintInstalled() {
mg.Deps(initVars) mg.Deps(initVars)
if err := exec.Command("golangci-lint").Run(); err != nil && strings.Contains(err.Error(), "executable file not found") { 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("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.51.2") fmt.Println("curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.54.2")
os.Exit(1) os.Exit(1)
} }
} }
@ -750,7 +757,7 @@ func (Dev) MakeMigration() error {
date := time.Now().Format("20060102150405") date := time.Now().Format("20060102150405")
migration := `// Vikunja is a to-do list application to facilitate your life. migration := `// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved. // Copyright 2018-present Vikunja and contributors. All rights reserved.
// //
// This program is free software: you can redistribute it and/or modify // 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 // 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. // Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved. // Copyright 2018-present Vikunja and contributors. All rights reserved.
// //
// This program is free software: you can redistribute it and/or modify // 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 // 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. // Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved. // Copyright 2018-present Vikunja and contributors. All rights reserved.
// //
// This program is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU Affero General Public Licensee as published by
@ -31,19 +31,6 @@ import (
// DateFormat is the caldav date format // DateFormat is the caldav date format
const DateFormat = `20060102T150405` const DateFormat = `20060102T150405`
// Event holds a single caldav event
type Event struct {
Summary string
Description string
UID string
Alarms []Alarm
Color string
Timestamp time.Time
Start time.Time
End time.Time
}
// Todo holds a single VTODO // Todo holds a single VTODO
type Todo struct { type Todo struct {
// Required // Required
@ -51,20 +38,21 @@ type Todo struct {
UID string UID string
// Optional // Optional
Summary string Summary string
Description string Description string
Completed time.Time Completed time.Time
Organizer *user.User Organizer *user.User
Priority int64 // 0-9, 1 is highest Priority int64 // 0-9, 1 is highest
RelatedToUID string RelatedToParentUID string
Color string Color string
Categories []string Categories []string
Start time.Time Start time.Time
End time.Time End time.Time
DueDate time.Time DueDate time.Time
Duration time.Duration Duration time.Duration
RepeatAfter int64 RepeatAfter int64
RepeatMode models.TaskRepeatMode RepeatMode models.TaskRepeatMode
Alarms []Alarm
Created time.Time Created time.Time
Updated time.Time // last-mod Updated time.Time // last-mod
@ -73,6 +61,8 @@ type Todo struct {
// Alarm holds infos about an alarm from a caldav event // Alarm holds infos about an alarm from a caldav event
type Alarm struct { type Alarm struct {
Time time.Time Time time.Time
Duration time.Duration
RelativeTo models.ReminderRelation
Description string Description string
} }
@ -100,58 +90,6 @@ X-OUTLOOK-COLOR:` + color + `
X-FUNAMBOL-COLOR:` + color X-FUNAMBOL-COLOR:` + color
} }
// ParseEvents parses an array of caldav events and gives them back as string
func ParseEvents(config *Config, events []*Event) (caldavevents string) {
caldavevents += `BEGIN:VCALENDAR
VERSION:2.0
METHOD:PUBLISH
X-PUBLISHED-TTL:PT4H
X-WR-CALNAME:` + config.Name + `
PRODID:-//` + config.ProdID + `//EN` + getCaldavColor(config.Color)
for _, e := range events {
if e.UID == "" {
e.UID = makeCalDavTimeFromTimeStamp(e.Timestamp) + utils.Sha256(e.Summary)
}
formattedDescription := ""
if e.Description != "" {
re := regexp.MustCompile(`\r?\n`)
formattedDescription = re.ReplaceAllString(e.Description, "\\n")
}
caldavevents += `
BEGIN:VEVENT
UID:` + e.UID + `
SUMMARY:` + e.Summary + getCaldavColor(e.Color) + `
DESCRIPTION:` + formattedDescription + `
DTSTAMP:` + makeCalDavTimeFromTimeStamp(e.Timestamp) + `
DTSTART:` + makeCalDavTimeFromTimeStamp(e.Start) + `
DTEND:` + makeCalDavTimeFromTimeStamp(e.End)
for _, a := range e.Alarms {
if a.Description == "" {
a.Description = e.Summary
}
caldavevents += `
BEGIN:VALARM
TRIGGER:` + calcAlarmDateFromReminder(e.Start, a.Time) + `
ACTION:DISPLAY
DESCRIPTION:` + a.Description + `
END:VALARM`
}
caldavevents += `
END:VEVENT`
}
caldavevents += `
END:VCALENDAR` // Need a line break
return
}
func formatDuration(duration time.Duration) string { func formatDuration(duration time.Duration) string {
seconds := duration.Seconds() - duration.Minutes()*60 seconds := duration.Seconds() - duration.Minutes()*60
minutes := duration.Minutes() - duration.Hours()*60 minutes := duration.Minutes() - duration.Hours()*60
@ -209,9 +147,9 @@ STATUS:COMPLETED`
ORGANIZER;CN=:` + t.Organizer.Username ORGANIZER;CN=:` + t.Organizer.Username
} }
if t.RelatedToUID != "" { if t.RelatedToParentUID != "" {
caldavtodos += ` caldavtodos += `
RELATED-TO:` + t.RelatedToUID RELATED-TO;RELTYPE=PARENT:` + t.RelatedToParentUID
} }
if t.DueDate.Unix() > 0 { if t.DueDate.Unix() > 0 {
@ -246,7 +184,7 @@ CATEGORIES:` + strings.Join(t.Categories, ",")
caldavtodos += ` caldavtodos += `
LAST-MODIFIED:` + makeCalDavTimeFromTimeStamp(t.Updated) LAST-MODIFIED:` + makeCalDavTimeFromTimeStamp(t.Updated)
caldavtodos += ParseAlarms(t.Alarms, t.Summary)
caldavtodos += ` caldavtodos += `
END:VTODO` END:VTODO`
} }
@ -257,19 +195,42 @@ END:VCALENDAR` // Need a line break
return return
} }
func ParseAlarms(alarms []Alarm, taskDescription string) (caldavalarms string) {
for _, a := range alarms {
if a.Description == "" {
a.Description = taskDescription
}
caldavalarms += `
BEGIN:VALARM`
switch a.RelativeTo {
case models.ReminderRelationStartDate:
caldavalarms += `
TRIGGER;RELATED=START:` + makeCalDavDuration(a.Duration)
case models.ReminderRelationEndDate, models.ReminderRelationDueDate:
caldavalarms += `
TRIGGER;RELATED=END:` + makeCalDavDuration(a.Duration)
default:
caldavalarms += `
TRIGGER;VALUE=DATE-TIME:` + makeCalDavTimeFromTimeStamp(a.Time)
}
caldavalarms += `
ACTION:DISPLAY
DESCRIPTION:` + a.Description + `
END:VALARM`
}
return caldavalarms
}
func makeCalDavTimeFromTimeStamp(ts time.Time) (caldavtime string) { func makeCalDavTimeFromTimeStamp(ts time.Time) (caldavtime string) {
return ts.In(time.UTC).Format(DateFormat) + "Z" return ts.In(time.UTC).Format(DateFormat) + "Z"
} }
func calcAlarmDateFromReminder(eventStart, reminder time.Time) (alarmTime string) { func makeCalDavDuration(duration time.Duration) (caldavtime string) {
diff := reminder.Sub(eventStart) if duration < 0 {
diffStr := strings.ToUpper(diff.String()) duration = duration.Abs()
if diff < 0 { caldavtime = "-"
alarmTime += `-`
// We append the - at the beginning of the caldav flag, that would get in the way if the minutes
// themselves are also containing it
diffStr = diffStr[1:]
} }
alarmTime += `PT` + diffStr caldavtime += "PT" + strings.ToUpper(duration.Truncate(time.Millisecond).String())
return return
} }

View File

@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life. // Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved. // Copyright 2018-present Vikunja and contributors. All rights reserved.
// //
// This program is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU Affero General Public Licensee as published by
@ -26,275 +26,6 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestParseEvents(t *testing.T) {
type args struct {
config *Config
events []*Event
}
tests := []struct {
name string
args args
wantCaldavevents string
}{
{
name: "Test caldavparsing without reminders",
args: args{
config: &Config{
Name: "test",
ProdID: "RandomProdID which is not random",
Color: "ffffff",
},
events: []*Event{
{
Summary: "Event #1",
Description: "Lorem Ipsum",
UID: "randommduid",
Timestamp: time.Unix(1543626724, 0).In(config.GetTimeZone()),
Start: time.Unix(1543626724, 0).In(config.GetTimeZone()),
End: time.Unix(1543627824, 0).In(config.GetTimeZone()),
Color: "affffe",
},
{
Summary: "Event #2",
UID: "randommduidd",
Timestamp: time.Unix(1543726724, 0).In(config.GetTimeZone()),
Start: time.Unix(1543726724, 0).In(config.GetTimeZone()),
End: time.Unix(1543738724, 0).In(config.GetTimeZone()),
},
{
Summary: "Event #3 with empty uid",
UID: "20181202T0600242aaef4a81d770c1e775e26bc5abebc87f1d3d7bffaa83",
Timestamp: time.Unix(1543726824, 0).In(config.GetTimeZone()),
Start: time.Unix(1543726824, 0).In(config.GetTimeZone()),
End: time.Unix(1543727000, 0).In(config.GetTimeZone()),
},
},
},
wantCaldavevents: `BEGIN:VCALENDAR
VERSION:2.0
METHOD:PUBLISH
X-PUBLISHED-TTL:PT4H
X-WR-CALNAME:test
PRODID:-//RandomProdID which is not random//EN
X-APPLE-CALENDAR-COLOR:#ffffffFF
X-OUTLOOK-COLOR:#ffffffFF
X-FUNAMBOL-COLOR:#ffffffFF
BEGIN:VEVENT
UID:randommduid
SUMMARY:Event #1
X-APPLE-CALENDAR-COLOR:#affffeFF
X-OUTLOOK-COLOR:#affffeFF
X-FUNAMBOL-COLOR:#affffeFF
DESCRIPTION:Lorem Ipsum
DTSTAMP:20181201T011204Z
DTSTART:20181201T011204Z
DTEND:20181201T013024Z
END:VEVENT
BEGIN:VEVENT
UID:randommduidd
SUMMARY:Event #2
DESCRIPTION:
DTSTAMP:20181202T045844Z
DTSTART:20181202T045844Z
DTEND:20181202T081844Z
END:VEVENT
BEGIN:VEVENT
UID:20181202T0600242aaef4a81d770c1e775e26bc5abebc87f1d3d7bffaa83
SUMMARY:Event #3 with empty uid
DESCRIPTION:
DTSTAMP:20181202T050024Z
DTSTART:20181202T050024Z
DTEND:20181202T050320Z
END:VEVENT
END:VCALENDAR`,
},
{
name: "Test caldavparsing with reminders",
args: args{
config: &Config{
Name: "test2",
ProdID: "RandomProdID which is not random",
},
events: []*Event{
{
Summary: "Event #1",
Description: "Lorem Ipsum",
UID: "randommduid",
Timestamp: time.Unix(1543626724, 0).In(config.GetTimeZone()),
Start: time.Unix(1543626724, 0).In(config.GetTimeZone()),
End: time.Unix(1543627824, 0).In(config.GetTimeZone()),
Alarms: []Alarm{
{Time: time.Unix(1543626524, 0).In(config.GetTimeZone())},
{Time: time.Unix(1543626224, 0).In(config.GetTimeZone())},
{Time: time.Unix(1543626024, 0)},
},
},
{
Summary: "Event #2",
UID: "randommduidd",
Timestamp: time.Unix(1543726724, 0).In(config.GetTimeZone()),
Start: time.Unix(1543726724, 0).In(config.GetTimeZone()),
End: time.Unix(1543738724, 0).In(config.GetTimeZone()),
Alarms: []Alarm{
{Time: time.Unix(1543626524, 0).In(config.GetTimeZone())},
{Time: time.Unix(1543626224, 0).In(config.GetTimeZone())},
{Time: time.Unix(1543626024, 0).In(config.GetTimeZone())},
},
},
{
Summary: "Event #3 with empty uid",
Timestamp: time.Unix(1543726824, 0).In(config.GetTimeZone()),
Start: time.Unix(1543726824, 0).In(config.GetTimeZone()),
End: time.Unix(1543727000, 0).In(config.GetTimeZone()),
Alarms: []Alarm{
{Time: time.Unix(1543626524, 0).In(config.GetTimeZone())},
{Time: time.Unix(1543626224, 0).In(config.GetTimeZone())},
{Time: time.Unix(1543626024, 0).In(config.GetTimeZone())},
{Time: time.Unix(1543826824, 0).In(config.GetTimeZone())},
},
},
{
Summary: "Event #4 without any",
Timestamp: time.Unix(1543726824, 0),
Start: time.Unix(1543726824, 0),
End: time.Unix(1543727000, 0),
},
},
},
wantCaldavevents: `BEGIN:VCALENDAR
VERSION:2.0
METHOD:PUBLISH
X-PUBLISHED-TTL:PT4H
X-WR-CALNAME:test2
PRODID:-//RandomProdID which is not random//EN
BEGIN:VEVENT
UID:randommduid
SUMMARY:Event #1
DESCRIPTION:Lorem Ipsum
DTSTAMP:20181201T011204Z
DTSTART:20181201T011204Z
DTEND:20181201T013024Z
BEGIN:VALARM
TRIGGER:-PT3M20S
ACTION:DISPLAY
DESCRIPTION:Event #1
END:VALARM
BEGIN:VALARM
TRIGGER:-PT8M20S
ACTION:DISPLAY
DESCRIPTION:Event #1
END:VALARM
BEGIN:VALARM
TRIGGER:-PT11M40S
ACTION:DISPLAY
DESCRIPTION:Event #1
END:VALARM
END:VEVENT
BEGIN:VEVENT
UID:randommduidd
SUMMARY:Event #2
DESCRIPTION:
DTSTAMP:20181202T045844Z
DTSTART:20181202T045844Z
DTEND:20181202T081844Z
BEGIN:VALARM
TRIGGER:-PT27H50M0S
ACTION:DISPLAY
DESCRIPTION:Event #2
END:VALARM
BEGIN:VALARM
TRIGGER:-PT27H55M0S
ACTION:DISPLAY
DESCRIPTION:Event #2
END:VALARM
BEGIN:VALARM
TRIGGER:-PT27H58M20S
ACTION:DISPLAY
DESCRIPTION:Event #2
END:VALARM
END:VEVENT
BEGIN:VEVENT
UID:20181202T050024Z2aaef4a81d770c1e775e26bc5abebc87f1d3d7bffaa83
SUMMARY:Event #3 with empty uid
DESCRIPTION:
DTSTAMP:20181202T050024Z
DTSTART:20181202T050024Z
DTEND:20181202T050320Z
BEGIN:VALARM
TRIGGER:-PT27H51M40S
ACTION:DISPLAY
DESCRIPTION:Event #3 with empty uid
END:VALARM
BEGIN:VALARM
TRIGGER:-PT27H56M40S
ACTION:DISPLAY
DESCRIPTION:Event #3 with empty uid
END:VALARM
BEGIN:VALARM
TRIGGER:-PT28H0M0S
ACTION:DISPLAY
DESCRIPTION:Event #3 with empty uid
END:VALARM
BEGIN:VALARM
TRIGGER:PT27H46M40S
ACTION:DISPLAY
DESCRIPTION:Event #3 with empty uid
END:VALARM
END:VEVENT
BEGIN:VEVENT
UID:20181202T050024Zae7548ce9556df85038abe90dc674d4741a61ce74d1cf
SUMMARY:Event #4 without any
DESCRIPTION:
DTSTAMP:20181202T050024Z
DTSTART:20181202T050024Z
DTEND:20181202T050320Z
END:VEVENT
END:VCALENDAR`,
},
{
name: "Test caldavparsing with multiline description",
args: args{
config: &Config{
Name: "test",
ProdID: "RandomProdID which is not random",
},
events: []*Event{
{
Summary: "Event #1",
Description: `Lorem Ipsum
Dolor sit amet`,
UID: "randommduid",
Timestamp: time.Unix(1543626724, 0).In(config.GetTimeZone()),
Start: time.Unix(1543626724, 0).In(config.GetTimeZone()),
End: time.Unix(1543627824, 0).In(config.GetTimeZone()),
},
},
},
wantCaldavevents: `BEGIN:VCALENDAR
VERSION:2.0
METHOD:PUBLISH
X-PUBLISHED-TTL:PT4H
X-WR-CALNAME:test
PRODID:-//RandomProdID which is not random//EN
BEGIN:VEVENT
UID:randommduid
SUMMARY:Event #1
DESCRIPTION:Lorem Ipsum\nDolor sit amet
DTSTAMP:20181201T011204Z
DTSTART:20181201T011204Z
DTEND:20181201T013024Z
END:VEVENT
END:VCALENDAR`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gotCaldavevents := ParseEvents(tt.args.config, tt.args.events)
assert.Equal(t, gotCaldavevents, tt.wantCaldavevents)
})
}
}
func TestParseTodos(t *testing.T) { func TestParseTodos(t *testing.T) {
type args struct { type args struct {
config *Config config *Config
@ -520,6 +251,120 @@ X-FUNAMBOL-COLOR:#affffeFF
CATEGORIES:label1,label2 CATEGORIES:label1,label2
LAST-MODIFIED:00010101T000000Z LAST-MODIFIED:00010101T000000Z
END:VTODO END:VTODO
END:VCALENDAR`,
},
{
name: "with alarm",
args: args{
config: &Config{
Name: "test",
ProdID: "RandomProdID which is not random",
},
todos: []*Todo{
{
Summary: "Todo #1",
UID: "randommduid",
Timestamp: time.Unix(1543626724, 0).In(config.GetTimeZone()),
Alarms: []Alarm{
{
Time: time.Unix(1543626724, 0).In(config.GetTimeZone()),
},
{
Time: time.Unix(1543626724, 0).In(config.GetTimeZone()),
Description: "alarm description",
},
{
Duration: -2 * time.Hour,
RelativeTo: "due_date",
},
{
Duration: 1 * time.Hour,
RelativeTo: "start_date",
},
{
Duration: time.Duration(0),
RelativeTo: "end_date",
},
},
},
},
},
wantCaldavtasks: `BEGIN:VCALENDAR
VERSION:2.0
METHOD:PUBLISH
X-PUBLISHED-TTL:PT4H
X-WR-CALNAME:test
PRODID:-//RandomProdID which is not random//EN
BEGIN:VTODO
UID:randommduid
DTSTAMP:20181201T011204Z
SUMMARY:Todo #1
LAST-MODIFIED:00010101T000000Z
BEGIN:VALARM
TRIGGER;VALUE=DATE-TIME:20181201T011204Z
ACTION:DISPLAY
DESCRIPTION:Todo #1
END:VALARM
BEGIN:VALARM
TRIGGER;VALUE=DATE-TIME:20181201T011204Z
ACTION:DISPLAY
DESCRIPTION:alarm description
END:VALARM
BEGIN:VALARM
TRIGGER;RELATED=END:-PT2H0M0S
ACTION:DISPLAY
DESCRIPTION:Todo #1
END:VALARM
BEGIN:VALARM
TRIGGER;RELATED=START:PT1H0M0S
ACTION:DISPLAY
DESCRIPTION:Todo #1
END:VALARM
BEGIN:VALARM
TRIGGER;RELATED=END:PT0S
ACTION:DISPLAY
DESCRIPTION:Todo #1
END:VALARM
END:VTODO
END:VCALENDAR`,
},
{
name: "with parent task",
args: args{
config: &Config{
Name: "test",
ProdID: "RandomProdID which is not random",
Color: "ffffff",
},
todos: []*Todo{
{
Summary: "Todo #1",
UID: "randommduid",
Timestamp: time.Unix(1543626724, 0).In(config.GetTimeZone()),
Color: "affffe",
RelatedToParentUID: "another_random_uid",
},
},
},
wantCaldavtasks: `BEGIN:VCALENDAR
VERSION:2.0
METHOD:PUBLISH
X-PUBLISHED-TTL:PT4H
X-WR-CALNAME:test
PRODID:-//RandomProdID which is not random//EN
X-APPLE-CALENDAR-COLOR:#ffffffFF
X-OUTLOOK-COLOR:#ffffffFF
X-FUNAMBOL-COLOR:#ffffffFF
BEGIN:VTODO
UID:randommduid
DTSTAMP:20181201T011204Z
SUMMARY:Todo #1
X-APPLE-CALENDAR-COLOR:#affffeFF
X-OUTLOOK-COLOR:#affffeFF
X-FUNAMBOL-COLOR:#affffeFF
RELATED-TO;RELTYPE=PARENT:another_random_uid
LAST-MODIFIED:00010101T000000Z
END:VTODO
END:VCALENDAR`, END:VCALENDAR`,
}, },
} }

View File

@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life. // Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved. // Copyright 2018-present Vikunja and contributors. All rights reserved.
// //
// This program is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU Affero General Public Licensee as published by
@ -17,12 +17,15 @@
package caldav package caldav
import ( import (
"errors"
"strconv" "strconv"
"strings" "strings"
"time" "time"
"code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/log" "code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/models" "code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/utils"
ics "github.com/arran4/golang-ical" ics "github.com/arran4/golang-ical"
) )
@ -38,6 +41,20 @@ func GetCaldavTodosForTasks(project *models.ProjectWithTasksAndBuckets, projectT
for _, label := range t.Labels { for _, label := range t.Labels {
categories = append(categories, label.Title) categories = append(categories, label.Title)
} }
var alarms []Alarm
for _, reminder := range t.Reminders {
alarms = append(alarms, Alarm{
Time: reminder.Reminder,
Duration: time.Duration(reminder.RelativePeriod) * time.Second,
RelativeTo: reminder.RelativeTo,
})
}
// Find the UID of the parent task, if it exists:
var parentTaskUID string
if parentTasks, ok := t.RelatedTasks[models.RelationKindParenttask]; ok {
parentTaskUID = parentTasks[0].UID
}
caldavtodos = append(caldavtodos, &Todo{ caldavtodos = append(caldavtodos, &Todo{
Timestamp: t.Updated, Timestamp: t.Updated,
@ -46,16 +63,18 @@ func GetCaldavTodosForTasks(project *models.ProjectWithTasksAndBuckets, projectT
Description: t.Description, Description: t.Description,
Completed: t.DoneAt, Completed: t.DoneAt,
// Organizer: &t.CreatedBy, // Disabled until we figure out how this works // Organizer: &t.CreatedBy, // Disabled until we figure out how this works
Categories: categories, Categories: categories,
Priority: t.Priority, Priority: t.Priority,
Start: t.StartDate, RelatedToParentUID: parentTaskUID,
End: t.EndDate, Start: t.StartDate,
Created: t.Created, End: t.EndDate,
Updated: t.Updated, Created: t.Created,
DueDate: t.DueDate, Updated: t.Updated,
Duration: duration, DueDate: t.DueDate,
RepeatAfter: t.RepeatAfter, Duration: duration,
RepeatMode: t.RepeatMode, RepeatAfter: t.RepeatAfter,
RepeatMode: t.RepeatMode,
Alarms: alarms,
}) })
} }
@ -72,17 +91,21 @@ func ParseTaskFromVTODO(content string) (vTask *models.Task, err error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
vTodo, ok := parsed.Components[0].(*ics.VTodo)
// We put the task details in a map to be able to handle them more easily if !ok {
task := make(map[string]string) return nil, errors.New("VTODO element not found")
for _, c := range parsed.Components[0].UnknownPropertiesIANAProperties() { }
task[c.IANAToken] = c.Value var parsedProperties = vTodo.UnknownPropertiesIANAProperties()
// We put the vTodo details in a map to be able to handle them more easily
task := make(map[string]ics.IANAProperty)
for _, c := range parsedProperties {
task[c.IANAToken] = c
} }
// Parse the priority // Parse the priority
var priority int64 var priority int64
if _, ok := task["PRIORITY"]; ok { if _, ok := task["PRIORITY"]; ok {
priorityParsed, err := strconv.ParseInt(task["PRIORITY"], 10, 64) priorityParsed, err := strconv.ParseInt(task["PRIORITY"].Value, 10, 64)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -90,15 +113,35 @@ func ParseTaskFromVTODO(content string) (vTask *models.Task, err error) {
priority = parseVTODOPriority(priorityParsed) priority = parseVTODOPriority(priorityParsed)
} }
// Parse the enddate // Check if the task has a parent:
duration, _ := time.ParseDuration(task["DURATION"]) var parentTaskUID string
for _, c := range parsedProperties {
// Check if the entry is a relation:
if c.IANAToken != "RELATED-TO" {
continue
}
// Check if the relation has a type:
if _, ok := c.ICalParameters["RELTYPE"]; !ok {
continue
}
// Check that the type is "PARENT":
if len(c.ICalParameters["RELTYPE"]) != 1 || c.ICalParameters["RELTYPE"][0] != "PARENT" {
continue
}
description := strings.ReplaceAll(task["DESCRIPTION"], "\\,", ",") // We have the id of the parent task:
parentTaskUID = c.Value
}
// Parse the enddate
duration, _ := time.ParseDuration(task["DURATION"].Value)
description := strings.ReplaceAll(task["DESCRIPTION"].Value, "\\,", ",")
description = strings.ReplaceAll(description, "\\n", "\n") description = strings.ReplaceAll(description, "\\n", "\n")
var labels []*models.Label var labels []*models.Label
if val, ok := task["CATEGORIES"]; ok { if val, ok := task["CATEGORIES"]; ok {
categories := strings.Split(val, ",") categories := strings.Split(val.Value, ",")
labels = make([]*models.Label, 0, len(categories)) labels = make([]*models.Label, 0, len(categories))
for _, category := range categories { for _, category := range categories {
labels = append(labels, &models.Label{ labels = append(labels, &models.Label{
@ -108,8 +151,8 @@ func ParseTaskFromVTODO(content string) (vTask *models.Task, err error) {
} }
vTask = &models.Task{ vTask = &models.Task{
UID: task["UID"], UID: task["UID"].Value,
Title: task["SUMMARY"], Title: task["SUMMARY"].Value,
Description: description, Description: description,
Priority: priority, Priority: priority,
Labels: labels, Labels: labels,
@ -119,19 +162,87 @@ func ParseTaskFromVTODO(content string) (vTask *models.Task, err error) {
DoneAt: caldavTimeToTimestamp(task["COMPLETED"]), DoneAt: caldavTimeToTimestamp(task["COMPLETED"]),
} }
if task["STATUS"] == "COMPLETED" { if task["STATUS"].Value == "COMPLETED" {
vTask.Done = true vTask.Done = true
} }
// Check if the task has a parent and create a dummy relation if yes:
if parentTaskUID != "" {
var parentTaskUID = parentTaskUID
if vTask.RelatedTasks == nil {
vTask.RelatedTasks = make(models.RelatedTaskMap)
}
vTask.RelatedTasks[models.RelationKindParenttask] = append(vTask.RelatedTasks[models.RelationKindParenttask], &models.Task{
UID: parentTaskUID,
})
}
if duration > 0 && !vTask.StartDate.IsZero() { if duration > 0 && !vTask.StartDate.IsZero() {
vTask.EndDate = vTask.StartDate.Add(duration) vTask.EndDate = vTask.StartDate.Add(duration)
} }
for _, vAlarm := range vTodo.SubComponents() {
if vAlarm, ok := vAlarm.(*ics.VAlarm); ok {
vTask = parseVAlarm(vAlarm, vTask)
}
}
return return
} }
func parseVAlarm(vAlarm *ics.VAlarm, vTask *models.Task) *models.Task {
for _, property := range vAlarm.UnknownPropertiesIANAProperties() {
if property.IANAToken != "TRIGGER" {
continue
}
if contains(property.ICalParameters["VALUE"], "DATE-TIME") {
// Example: TRIGGER;VALUE=DATE-TIME:20181201T011210Z
vTask.Reminders = append(vTask.Reminders, &models.TaskReminder{
Reminder: caldavTimeToTimestamp(property),
})
continue
}
duration := utils.ParseISO8601Duration(property.Value)
if contains(property.ICalParameters["RELATED"], "END") {
// Example: TRIGGER;RELATED=END:-P2D
if vTask.EndDate.IsZero() {
vTask.Reminders = append(vTask.Reminders, &models.TaskReminder{
RelativePeriod: int64(duration.Seconds()),
RelativeTo: models.ReminderRelationDueDate})
} else {
vTask.Reminders = append(vTask.Reminders, &models.TaskReminder{
RelativePeriod: int64(duration.Seconds()),
RelativeTo: models.ReminderRelationEndDate})
}
continue
}
// Example: TRIGGER;RELATED=START:-P2D
// Example: TRIGGER:-PT60M
vTask.Reminders = append(vTask.Reminders, &models.TaskReminder{
RelativePeriod: int64(duration.Seconds()),
RelativeTo: models.ReminderRelationStartDate})
}
return vTask
}
func contains(array []string, str string) bool {
for _, value := range array {
if value == str {
return true
}
}
return false
}
// https://tools.ietf.org/html/rfc5545#section-3.3.5 // https://tools.ietf.org/html/rfc5545#section-3.3.5
func caldavTimeToTimestamp(tstring string) time.Time { func caldavTimeToTimestamp(ianaProperty ics.IANAProperty) time.Time {
tstring := ianaProperty.Value
if tstring == "" { if tstring == "" {
return time.Time{} return time.Time{}
} }
@ -146,7 +257,24 @@ func caldavTimeToTimestamp(tstring string) time.Time {
format = `20060102` format = `20060102`
} }
t, err := time.Parse(format, tstring) var t time.Time
var err error
tzParameter := ianaProperty.ICalParameters["TZID"]
if len(tzParameter) > 0 {
loc, err := time.LoadLocation(tzParameter[0])
if err != nil {
log.Warningf("Error while parsing caldav timezone %s: %s", tzParameter[0], err)
} else {
t, err = time.ParseInLocation(format, tstring, loc)
if err != nil {
log.Warningf("Error while parsing caldav time %s to TimeStamp: %s at location %s", tstring, loc, err)
} else {
t = t.In(config.GetTimeZone())
return t
}
}
}
t, err = time.Parse(format, tstring)
if err != nil { if err != nil {
log.Warningf("Error while parsing caldav time %s to TimeStamp: %s", tstring, err) log.Warningf("Error while parsing caldav time %s to TimeStamp: %s", tstring, err)
return time.Time{} return time.Time{}

View File

@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life. // Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved. // Copyright 2018-present Vikunja and contributors. All rights reserved.
// //
// This program is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU Affero General Public Licensee as published by
@ -118,6 +118,234 @@ END:VCALENDAR`,
Updated: time.Unix(1543626724, 0).In(config.GetTimeZone()), Updated: time.Unix(1543626724, 0).In(config.GetTimeZone()),
}, },
}, },
{
name: "With alarm (time trigger)",
args: args{content: `BEGIN:VCALENDAR
VERSION:2.0
METHOD:PUBLISH
X-PUBLISHED-TTL:PT4H
X-WR-CALNAME:test
PRODID:-//RandomProdID which is not random//EN
BEGIN:VTODO
UID:randomuid
DTSTAMP:20181201T011204
SUMMARY:Todo #1
DESCRIPTION:Lorem Ipsum
BEGIN:VALARM
TRIGGER;VALUE=DATE-TIME:20181201T011210Z
ACTION:DISPLAY
END:VALARM
END:VTODO
END:VCALENDAR`,
},
wantVTask: &models.Task{
Title: "Todo #1",
UID: "randomuid",
Description: "Lorem Ipsum",
Reminders: []*models.TaskReminder{
{
Reminder: time.Date(2018, 12, 1, 1, 12, 10, 0, config.GetTimeZone()),
},
},
Updated: time.Unix(1543626724, 0).In(config.GetTimeZone()),
},
},
{
name: "With alarm (relative trigger)",
args: args{content: `BEGIN:VCALENDAR
VERSION:2.0
METHOD:PUBLISH
X-PUBLISHED-TTL:PT4H
X-WR-CALNAME:test
PRODID:-//RandomProdID which is not random//EN
BEGIN:VTODO
UID:randomuid
DTSTAMP:20181201T011204
SUMMARY:Todo #1
DESCRIPTION:Lorem Ipsum
DTSTART:20230228T170000Z
DUE:20230304T150000Z
BEGIN:VALARM
TRIGGER:PT0S
ACTION:DISPLAY
END:VALARM
BEGIN:VALARM
TRIGGER;VALUE=DURATION:-PT60M
ACTION:DISPLAY
END:VALARM
BEGIN:VALARM
TRIGGER:-PT61M
ACTION:DISPLAY
END:VALARM
BEGIN:VALARM
TRIGGER;RELATED=START:-P1D
ACTION:DISPLAY
END:VALARM
BEGIN:VALARM
TRIGGER;RELATED=END:-PT30M
ACTION:DISPLAY
END:VALARM
END:VTODO
END:VCALENDAR`,
},
wantVTask: &models.Task{
Title: "Todo #1",
UID: "randomuid",
Description: "Lorem Ipsum",
StartDate: time.Date(2023, 2, 28, 17, 0, 0, 0, config.GetTimeZone()),
DueDate: time.Date(2023, 3, 4, 15, 0, 0, 0, config.GetTimeZone()),
Reminders: []*models.TaskReminder{
{
RelativeTo: models.ReminderRelationStartDate,
RelativePeriod: 0,
},
{
RelativeTo: models.ReminderRelationStartDate,
RelativePeriod: -3600,
},
{
RelativeTo: models.ReminderRelationStartDate,
RelativePeriod: -3660,
},
{
RelativeTo: models.ReminderRelationStartDate,
RelativePeriod: -86400,
},
{
RelativeTo: models.ReminderRelationDueDate,
RelativePeriod: -1800,
},
},
Updated: time.Unix(1543626724, 0).In(config.GetTimeZone()),
},
},
{
name: "example task from tasks.org app",
args: args{content: `BEGIN:VCALENDAR
VERSION:2.0
PRODID:+//IDN tasks.org//android-130102//EN
BEGIN:VTODO
DTSTAMP:20230402T074158Z
UID:4290517349243274514
CREATED:20230402T060451Z
LAST-MODIFIED:20230402T074154Z
SUMMARY:Test with tasks.org
PRIORITY:9
CATEGORIES:Vikunja
X-APPLE-SORT-ORDER:697384109
DUE;TZID=Europe/Berlin:20230402T170001
DTSTART;TZID=Europe/Berlin:20230401T090000
BEGIN:VALARM
TRIGGER;RELATED=END:PT0S
ACTION:DISPLAY
DESCRIPTION:Default Tasks.org description
END:VALARM
BEGIN:VALARM
TRIGGER;VALUE=DATE-TIME:20230402T100000Z
ACTION:DISPLAY
DESCRIPTION:Default Tasks.org description
END:VALARM
END:VTODO
BEGIN:VTIMEZONE
TZID:Europe/Berlin
LAST-MODIFIED:20220816T024022Z
BEGIN:DAYLIGHT
TZNAME:CEST
TZOFFSETFROM:+0100
TZOFFSETTO:+0200
DTSTART:19810329T020000
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
END:DAYLIGHT
BEGIN:STANDARD
TZNAME:CET
TZOFFSETFROM:+0200
TZOFFSETTO:+0100
DTSTART:19961027T030000
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
END:STANDARD
END:VTIMEZONE
END:VCALENDAR`,
},
wantVTask: &models.Task{
Updated: time.Date(2023, 4, 2, 7, 41, 58, 0, config.GetTimeZone()),
UID: "4290517349243274514",
Title: "Test with tasks.org",
Priority: 1,
Labels: []*models.Label{
{
Title: "Vikunja",
},
},
DueDate: time.Date(2023, 4, 2, 15, 0, 1, 0, config.GetTimeZone()),
StartDate: time.Date(2023, 4, 1, 7, 0, 0, 0, config.GetTimeZone()),
Reminders: []*models.TaskReminder{
{
RelativeTo: models.ReminderRelationDueDate,
RelativePeriod: 0,
},
{
Reminder: time.Date(2023, 4, 2, 10, 0, 0, 0, config.GetTimeZone()),
},
},
},
},
{
name: "With parent",
args: args{content: `BEGIN:VCALENDAR
VERSION:2.0
METHOD:PUBLISH
X-PUBLISHED-TTL:PT4H
X-WR-CALNAME:test
PRODID:-//RandomProdID which is not random//EN
BEGIN:VTODO
UID:randomuid
DTSTAMP:20181201T011204
SUMMARY:SubTask #1
DESCRIPTION:Lorem Ipsum
RELATED-TO;RELTYPE=PARENT:randomuid_parent
LAST-MODIFIED:00010101T000000
END:VTODO
END:VCALENDAR`,
},
wantVTask: &models.Task{
Title: "SubTask #1",
UID: "randomuid",
Description: "Lorem Ipsum",
Updated: time.Unix(1543626724, 0).In(config.GetTimeZone()),
RelatedTasks: map[models.RelationKind][]*models.Task{
models.RelationKindParenttask: {
{
UID: "randomuid_parent",
},
},
},
},
},
{
name: "With non-parent relation we ignore",
args: args{content: `BEGIN:VCALENDAR
VERSION:2.0
METHOD:PUBLISH
X-PUBLISHED-TTL:PT4H
X-WR-CALNAME:test
PRODID:-//RandomProdID which is not random//EN
BEGIN:VTODO
UID:randomuid
DTSTAMP:20181201T011204
SUMMARY:Parent task
DESCRIPTION:Lorem Ipsum
RELATED-TO;RELTYPE=CHILD:randomuid_child
LAST-MODIFIED:00010101T000000
END:VTODO
END:VCALENDAR`,
},
wantVTask: &models.Task{
Title: "Parent task",
UID: "randomuid",
Description: "Lorem Ipsum",
Updated: time.Unix(1543626724, 0).In(config.GetTimeZone()),
},
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
@ -127,7 +355,7 @@ END:VCALENDAR`,
return return
} }
if diff, equal := messagediff.PrettyDiff(got, tt.wantVTask); !equal { if diff, equal := messagediff.PrettyDiff(got, tt.wantVTask); !equal {
t.Errorf("ParseTaskFromVTODO() gotVTask = %v, want %v, diff = %s", got, tt.wantVTask, diff) t.Errorf("ParseTaskFromVTODO()\n gotVTask = %v\n want %v\n diff = %s", got, tt.wantVTask, diff)
} }
}) })
} }
@ -135,8 +363,8 @@ END:VCALENDAR`,
func TestGetCaldavTodosForTasks(t *testing.T) { func TestGetCaldavTodosForTasks(t *testing.T) {
type args struct { type args struct {
list *models.ProjectWithTasksAndBuckets project *models.ProjectWithTasksAndBuckets
tasks []*models.TaskWithComments tasks []*models.TaskWithComments
} }
tests := []struct { tests := []struct {
name string name string
@ -144,11 +372,11 @@ func TestGetCaldavTodosForTasks(t *testing.T) {
wantCaldav string wantCaldav string
}{ }{
{ {
name: "Format single Task as Caldav", name: "Format single Task as CalDAV",
args: args{ args: args{
list: &models.ProjectWithTasksAndBuckets{ project: &models.ProjectWithTasksAndBuckets{
Project: models.Project{ Project: models.Project{
Title: "List title", Title: "Project title",
}, },
}, },
tasks: []*models.TaskWithComments{ tasks: []*models.TaskWithComments{
@ -175,6 +403,16 @@ func TestGetCaldavTodosForTasks(t *testing.T) {
Title: "label2", Title: "label2",
}, },
}, },
Reminders: []*models.TaskReminder{
{
Reminder: time.Unix(1543626730, 0).In(config.GetTimeZone()),
},
{
Reminder: time.Unix(1543626731, 0).In(config.GetTimeZone()),
RelativePeriod: -3600,
RelativeTo: models.ReminderRelationDueDate,
},
},
}, },
}, },
}, },
@ -183,7 +421,7 @@ func TestGetCaldavTodosForTasks(t *testing.T) {
VERSION:2.0 VERSION:2.0
METHOD:PUBLISH METHOD:PUBLISH
X-PUBLISHED-TTL:PT4H X-PUBLISHED-TTL:PT4H
X-WR-CALNAME:List title X-WR-CALNAME:Project title
PRODID:-//Vikunja Todo App//EN PRODID:-//Vikunja Todo App//EN
BEGIN:VTODO BEGIN:VTODO
UID:randomuid UID:randomuid
@ -200,13 +438,137 @@ PRIORITY:3
RRULE:FREQ=SECONDLY;INTERVAL=86400 RRULE:FREQ=SECONDLY;INTERVAL=86400
CATEGORIES:label1,label2 CATEGORIES:label1,label2
LAST-MODIFIED:20181201T011205Z LAST-MODIFIED:20181201T011205Z
BEGIN:VALARM
TRIGGER;VALUE=DATE-TIME:20181201T011210Z
ACTION:DISPLAY
DESCRIPTION:Task 1
END:VALARM
BEGIN:VALARM
TRIGGER;RELATED=END:-PT1H0M0S
ACTION:DISPLAY
DESCRIPTION:Task 1
END:VALARM
END:VTODO
END:VCALENDAR`,
},
{
name: "Format tasks with relationship as Caldav",
args: args{
project: &models.ProjectWithTasksAndBuckets{
Project: models.Project{
Title: "Project title",
},
},
tasks: []*models.TaskWithComments{
{
Task: models.Task{
Title: "Parent task",
UID: "randomuid_parent",
Description: "This is a parent task",
Priority: 3,
Created: time.Unix(1543626721, 0).In(config.GetTimeZone()),
Updated: time.Unix(1543626725, 0).In(config.GetTimeZone()),
RelatedTasks: map[models.RelationKind][]*models.Task{
models.RelationKindSubtask: {
{
Title: "Subtask 1",
UID: "randomuid_child_1",
Description: "This is the first child task",
Created: time.Unix(1543626724, 0).In(config.GetTimeZone()),
Updated: time.Unix(1543626724, 0).In(config.GetTimeZone()),
},
{
Title: "Subtask 2",
UID: "randomuid_child_2",
Description: "This is the second child task",
Created: time.Unix(1543626724, 0).In(config.GetTimeZone()),
Updated: time.Unix(1543626724, 0).In(config.GetTimeZone()),
},
},
},
},
},
{
Task: models.Task{
Title: "Subtask 1",
UID: "randomuid_child_1",
Description: "This is the first child task",
Created: time.Unix(1543626724, 0).In(config.GetTimeZone()),
Updated: time.Unix(1543626724, 0).In(config.GetTimeZone()),
RelatedTasks: map[models.RelationKind][]*models.Task{
models.RelationKindParenttask: {
{
Title: "Parent task",
UID: "randomuid_parent",
Description: "This is a parent task",
Priority: 3,
Created: time.Unix(1543626721, 0).In(config.GetTimeZone()),
},
},
},
},
},
{
Task: models.Task{
Title: "Subtask 2",
UID: "randomuid_child_2",
Description: "This is the second child task",
Created: time.Unix(1543626724, 0).In(config.GetTimeZone()),
Updated: time.Unix(1543626724, 0).In(config.GetTimeZone()),
RelatedTasks: map[models.RelationKind][]*models.Task{
models.RelationKindParenttask: {
{
Title: "Parent task",
UID: "randomuid_parent",
Description: "This is a parent task",
Priority: 3,
Created: time.Unix(1543626721, 0).In(config.GetTimeZone()),
},
},
},
},
},
},
},
wantCaldav: `BEGIN:VCALENDAR
VERSION:2.0
METHOD:PUBLISH
X-PUBLISHED-TTL:PT4H
X-WR-CALNAME:Project title
PRODID:-//Vikunja Todo App//EN
BEGIN:VTODO
UID:randomuid_parent
DTSTAMP:20181201T011205Z
SUMMARY:Parent task
DESCRIPTION:This is a parent task
CREATED:20181201T011201Z
PRIORITY:3
LAST-MODIFIED:20181201T011205Z
END:VTODO
BEGIN:VTODO
UID:randomuid_child_1
DTSTAMP:20181201T011204Z
SUMMARY:Subtask 1
DESCRIPTION:This is the first child task
RELATED-TO;RELTYPE=PARENT:randomuid_parent
CREATED:20181201T011204Z
LAST-MODIFIED:20181201T011204Z
END:VTODO
BEGIN:VTODO
UID:randomuid_child_2
DTSTAMP:20181201T011204Z
SUMMARY:Subtask 2
DESCRIPTION:This is the second child task
RELATED-TO;RELTYPE=PARENT:randomuid_parent
CREATED:20181201T011204Z
LAST-MODIFIED:20181201T011204Z
END:VTODO END:VTODO
END:VCALENDAR`, END:VCALENDAR`,
}, },
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
got := GetCaldavTodosForTasks(tt.args.list, tt.args.tasks) got := GetCaldavTodosForTasks(tt.args.project, tt.args.tasks)
if diff, equal := messagediff.PrettyDiff(got, tt.wantCaldav); !equal { if diff, equal := messagediff.PrettyDiff(got, tt.wantCaldav); !equal {
t.Errorf("GetCaldavTodosForTasks() gotVTask = %v, want %v, diff = %s", got, tt.wantCaldav, diff) t.Errorf("GetCaldavTodosForTasks() gotVTask = %v, want %v, diff = %s", got, tt.wantCaldav, diff)
} }

View File

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

58
pkg/cmd/index.go Normal file
View File

@ -0,0 +1,58 @@
// 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. // Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved. // Copyright 2018-present Vikunja and contributors. All rights reserved.
// //
// This program is free software: you can redistribute it and/or modify // 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 // 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. // Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved. // Copyright 2018-present Vikunja and contributors. All rights reserved.
// //
// This program is free software: you can redistribute it and/or modify // 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 // 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.", Short: "Restores all vikunja data from a vikunja dump.",
Args: cobra.ExactArgs(1), Args: cobra.ExactArgs(1),
PreRun: func(cmd *cobra.Command, args []string) { PreRun: func(cmd *cobra.Command, args []string) {
initialize.FullInit() initialize.FullInitWithoutAsync()
}, },
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
if err := dump.Restore(args[0]); err != nil { 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. // Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved. // Copyright 2018-present Vikunja and contributors. All rights reserved.
// //
// This program is free software: you can redistribute it and/or modify // 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 // 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. // Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved. // Copyright 2018-present Vikunja and contributors. All rights reserved.
// //
// This program is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU Affero General Public Licensee as published by
@ -46,6 +46,7 @@ var (
userFlagEnableUser bool userFlagEnableUser bool
userFlagDisableUser bool userFlagDisableUser bool
userFlagDeleteNow bool userFlagDeleteNow bool
userFlagDeleteConfirm bool
) )
func init() { func init() {
@ -73,7 +74,10 @@ func init() {
// User deletion flags // User deletion flags
userDeleteCmd.Flags().BoolVarP(&userFlagDeleteNow, "now", "n", false, "If provided, deletes the user immediately instead of sending them an email first.") userDeleteCmd.Flags().BoolVarP(&userFlagDeleteNow, "now", "n", false, "If provided, deletes the user immediately instead of sending them an email first.")
userCmd.AddCommand(userProjectCmd, userCreateCmd, userUpdateCmd, userResetPasswordCmd, userChangeEnabledCmd, userDeleteCmd) // 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)
rootCmd.AddCommand(userCmd) rootCmd.AddCommand(userCmd)
} }
@ -100,12 +104,16 @@ func getPasswordFromFlagOrInput() (pw string) {
} }
func getUserFromArg(s *xorm.Session, arg string) *user.User { func getUserFromArg(s *xorm.Session, arg string) *user.User {
filter := user.User{}
id, err := strconv.ParseInt(arg, 10, 64) id, err := strconv.ParseInt(arg, 10, 64)
if err != nil { if err != nil {
log.Fatalf("Invalid user id: %s", err) log.Infof("Invalid user ID [%s], assuming username instead", arg)
filter.Username = arg
} else {
filter.ID = id
} }
u, err := user.GetUserWithEmail(s, &user.User{ID: id}) u, err := user.GetUserWithEmail(s, &filter)
if err != nil { if err != nil {
log.Fatalf("Could not get user: %s", err) log.Fatalf("Could not get user: %s", err)
} }
@ -117,9 +125,9 @@ var userCmd = &cobra.Command{
Short: "Manage users locally through the cli.", Short: "Manage users locally through the cli.",
} }
var userProjectCmd = &cobra.Command{ var userListCmd = &cobra.Command{
Use: "project", Use: "list",
Short: "Shows a project of all users.", Short: "Shows a list of all users.",
PreRun: func(cmd *cobra.Command, args []string) { PreRun: func(cmd *cobra.Command, args []string) {
initialize.FullInit() initialize.FullInit()
}, },
@ -127,7 +135,7 @@ var userProjectCmd = &cobra.Command{
s := db.NewSession() s := db.NewSession()
defer s.Close() defer s.Close()
users, err := user.ProjectAllUsers(s) users, err := user.ListAllUsers(s)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
log.Fatalf("Error getting users: %s", err) log.Fatalf("Error getting users: %s", err)
@ -188,10 +196,10 @@ var userCreateCmd = &cobra.Command{
log.Fatalf("Error creating new user: %s", err) log.Fatalf("Error creating new user: %s", err)
} }
err = models.CreateNewNamespaceForUser(s, newUser) err = models.CreateNewProjectForUser(s, newUser)
if err != nil { if err != nil {
_ = s.Rollback() _ = s.Rollback()
log.Fatalf("Error creating new namespace for user: %s", err) log.Fatalf("Error creating new project for user: %s", err)
} }
if err := s.Commit(); err != nil { if err := s.Commit(); err != nil {
@ -322,7 +330,7 @@ var userDeleteCmd = &cobra.Command{
initialize.FullInit() initialize.FullInit()
}, },
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
if userFlagDeleteNow { if userFlagDeleteNow && !userFlagDeleteConfirm {
fmt.Println("You requested to delete the user immediately. Are you sure?") fmt.Println("You requested to delete the user immediately. Are you sure?")
fmt.Println(`To confirm, please type "yes, I confirm" in all uppercase:`) 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. // Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved. // Copyright 2018-present Vikunja and contributors. All rights reserved.
// //
// This program is free software: you can redistribute it and/or modify // 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 // 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. // Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved. // Copyright 2018-present Vikunja and contributors. All rights reserved.
// //
// This program is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU Affero General Public Licensee as published by
@ -23,15 +23,14 @@ import (
"os/signal" "os/signal"
"time" "time"
"code.vikunja.io/api/pkg/cron"
"code.vikunja.io/api/pkg/config" "code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/cron"
"code.vikunja.io/api/pkg/initialize" "code.vikunja.io/api/pkg/initialize"
"code.vikunja.io/api/pkg/log" "code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/routes" "code.vikunja.io/api/pkg/routes"
"code.vikunja.io/api/pkg/swagger"
"code.vikunja.io/api/pkg/utils" "code.vikunja.io/api/pkg/utils"
"code.vikunja.io/api/pkg/version" "code.vikunja.io/api/pkg/version"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -76,9 +75,6 @@ var webCmd = &cobra.Command{
// Version notification // Version notification
log.Infof("Vikunja version %s", version.Version) log.Infof("Vikunja version %s", version.Version)
// Additional swagger information
swagger.SwaggerInfo.Version = version.Version
// Start the webserver // Start the webserver
e := routes.NewEcho() e := routes.NewEcho()
routes.RegisterRoutes(e) routes.RegisterRoutes(e)

View File

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

View File

@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life. // Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved. // Copyright 2018-present Vikunja and contributors. All rights reserved.
// //
// This program is free software: you can redistribute it and/or modify // 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 // 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. // Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved. // Copyright 2018-present Vikunja and contributors. All rights reserved.
// //
// This program is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU Affero General Public Licensee as published by
@ -17,7 +17,6 @@
package db package db
import ( import (
"encoding/gob"
"fmt" "fmt"
"net/url" "net/url"
"os" "os"
@ -27,9 +26,7 @@ import (
"code.vikunja.io/api/pkg/config" "code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/log" "code.vikunja.io/api/pkg/log"
xrc "gitea.com/xorm/xorm-redis-cache"
"xorm.io/xorm" "xorm.io/xorm"
"xorm.io/xorm/caches"
"xorm.io/xorm/names" "xorm.io/xorm/names"
"xorm.io/xorm/schemas" "xorm.io/xorm/schemas"
@ -82,41 +79,26 @@ func CreateDBEngine() (engine *xorm.Engine, err error) {
} }
engine.SetTZDatabase(loc) engine.SetTZDatabase(loc)
engine.SetMapper(names.GonicMapper{}) engine.SetMapper(names.GonicMapper{})
logger := log.NewXormLogger("") logger := log.NewXormLogger(config.LogEnabled.GetBool(), config.LogDatabase.GetString(), config.LogDatabaseLevel.GetString())
engine.SetLogger(logger) 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 x = engine
return return
} }
// RegisterTableStructsForCache registers tables in gob encoding for redis cache
func RegisterTableStructsForCache(val interface{}) {
gob.Register(val)
}
func initMysqlEngine() (engine *xorm.Engine, err error) { func initMysqlEngine() (engine *xorm.Engine, err error) {
// We're using utf8mb here instead of just utf8 because we want to use non-BMP characters. // 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. // See https://stackoverflow.com/a/30074553/10924593 for more info.
host := fmt.Sprintf("tcp(%s)", config.DatabaseHost.GetString())
if config.DatabaseHost.GetString()[0] == '/' { // looks like a unix socket
host = fmt.Sprintf("unix(%s)", config.DatabaseHost.GetString())
}
connStr := fmt.Sprintf( connStr := fmt.Sprintf(
"%s:%s@tcp(%s)/%s?charset=utf8mb4&parseTime=true&tls=%s", "%s:%s@%s/%s?charset=utf8mb4&parseTime=true&tls=%s",
config.DatabaseUser.GetString(), config.DatabaseUser.GetString(),
config.DatabasePassword.GetString(), config.DatabasePassword.GetString(),
config.DatabaseHost.GetString(), host,
config.DatabaseDatabase.GetString(), config.DatabaseDatabase.GetString(),
config.DatabaseTLS.GetString()) config.DatabaseTLS.GetString())
engine, err = xorm.NewEngine("mysql", connStr) engine, err = xorm.NewEngine("mysql", connStr)

View File

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

View File

@ -0,0 +1,30 @@
- 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,7 +18,6 @@
title: testbucket3 title: testbucket3
project_id: 1 project_id: 1
created_by_id: 1 created_by_id: 1
is_done_bucket: 1
position: 3 position: 3
created: 2020-04-18 21:13:52 created: 2020-04-18 21:13:52
updated: 2020-04-18 21:13:52 updated: 2020-04-18 21:13:52
@ -26,7 +25,6 @@
title: testbucket4 - other project title: testbucket4 - other project
project_id: 2 project_id: 2
created_by_id: 1 created_by_id: 1
is_done_bucket: 1
created: 2020-04-18 21:13:52 created: 2020-04-18 21:13:52
updated: 2020-04-18 21:13:52 updated: 2020-04-18 21:13:52
# The following are not or only partly owned by user 1 # The following are not or only partly owned by user 1
@ -40,6 +38,7 @@
title: testbucket6 title: testbucket6
project_id: 6 project_id: 6
created_by_id: 1 created_by_id: 1
position: 1
created: 2020-04-18 21:13:52 created: 2020-04-18 21:13:52
updated: 2020-04-18 21:13:52 updated: 2020-04-18 21:13:52
- id: 7 - id: 7
@ -137,6 +136,7 @@
title: testbucket22 title: testbucket22
project_id: 6 project_id: 6
created_by_id: 1 created_by_id: 1
position: 2
created: 2020-04-18 21:13:52 created: 2020-04-18 21:13:52
updated: 2020-04-18 21:13:52 updated: 2020-04-18 21:13:52
- id: 23 - id: 23
@ -220,7 +220,31 @@
updated: 2020-04-18 21:13:52 updated: 2020-04-18 21:13:52
- id: 36 - id: 36
title: testbucket36 title: testbucket36
project_id: 26 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
created_by_id: 15
created: 2020-04-18 21:13:52
updated: 2020-04-18 21:13:52
- id: 39
title: testbucket37
project_id: 37
created_by_id: 15
created: 2020-04-18 21:13:52
updated: 2020-04-18 21:13:52
- id: 40
title: testbucket38
project_id: 38
created_by_id: 15 created_by_id: 15
created: 2020-04-18 21:13:52 created: 2020-04-18 21:13:52
updated: 2020-04-18 21:13:52 updated: 2020-04-18 21:13:52

View File

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

View File

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

View File

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

View File

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

@ -34,3 +34,27 @@
relation_kind: 'related' relation_kind: 'related'
created_by_id: 1 created_by_id: 1
created: 2018-12-01 15:13:12 created: 2018-12-01 15:13:12
- id: 7
task_id: 41
other_task_id: 43
relation_kind: 'subtask'
created_by_id: 15
created: 2018-12-01 15:13:12
- id: 8
task_id: 43
other_task_id: 41
relation_kind: 'parenttask'
created_by_id: 15
created: 2018-12-01 15:13:12
- id: 9
task_id: 41
other_task_id: 44
relation_kind: 'subtask'
created_by_id: 15
created: 2018-12-01 15:13:12
- id: 10
task_id: 44
other_task_id: 41
relation_kind: 'parenttask'
created_by_id: 15
created: 2018-12-01 15:13:12

View File

@ -6,7 +6,13 @@
task_id: 27 task_id: 27
reminder: 2018-12-01 01:13:44 reminder: 2018-12-01 01:13:44
created: 2018-12-01 01:12:04 created: 2018-12-01 01:12:04
relative_to: 'start_date'
relative_period: -3600
- id: 3 - id: 3
task_id: 2 task_id: 2
reminder: 2018-12-01 01:13:44 reminder: 2018-12-01 01:13:44
created: 2018-12-01 01:12:04 created: 2018-12-01 01:12:04
- id: 4
task_id: 40
reminder: 2023-03-04 15:00:00
created: 2018-12-01 01:12:04

View File

@ -193,7 +193,7 @@
title: 'task #21' title: 'task #21'
done: false done: false
created_by_id: 6 created_by_id: 6
project_id: 12 project_id: 32
index: 1 index: 1
bucket_id: 12 bucket_id: 12
created: 2018-12-01 01:12:04 created: 2018-12-01 01:12:04
@ -202,18 +202,18 @@
title: 'task #22' title: 'task #22'
done: false done: false
created_by_id: 6 created_by_id: 6
project_id: 13 project_id: 33
index: 1 index: 1
bucket_id: 13 bucket_id: 36
created: 2018-12-01 01:12:04 created: 2018-12-01 01:12:04
updated: 2018-12-01 01:12:04 updated: 2018-12-01 01:12:04
- id: 23 - id: 23
title: 'task #23' title: 'task #23'
done: false done: false
created_by_id: 6 created_by_id: 6
project_id: 14 project_id: 34
index: 1 index: 1
bucket_id: 14 bucket_id: 37
created: 2018-12-01 01:12:04 created: 2018-12-01 01:12:04
updated: 2018-12-01 01:12:04 updated: 2018-12-01 01:12:04
- id: 24 - id: 24
@ -244,7 +244,7 @@
created: 2018-12-01 01:12:04 created: 2018-12-01 01:12:04
updated: 2018-12-01 01:12:04 updated: 2018-12-01 01:12:04
- id: 27 - id: 27
title: 'task #27 with reminders' title: 'task #27 with reminders and start_date'
done: false done: false
created_by_id: 1 created_by_id: 1
project_id: 1 project_id: 1
@ -252,6 +252,7 @@
bucket_id: 1 bucket_id: 1
created: 2018-12-01 01:12:04 created: 2018-12-01 01:12:04
updated: 2018-12-01 01:12:04 updated: 2018-12-01 01:12:04
start_date: 2018-11-30 22:25:24
- id: 28 - id: 28
title: 'task #28 with repeat after' title: 'task #28 with repeat after'
done: false done: false
@ -356,16 +357,78 @@
updated: 2018-12-01 01:12:04 updated: 2018-12-01 01:12:04
due_date: 2018-10-30 22:25:24 due_date: 2018-10-30 22:25:24
- id: 39 - 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' uid: 'uid-caldav-test'
title: 'Title Caldav Test' title: 'Title Caldav Test'
description: 'Description Caldav Test' description: 'Description Caldav Test'
priority: 3 priority: 3
done: false done: false
created_by_id: 15 created_by_id: 15
project_id: 26 project_id: 36
index: 39 index: 39
due_date: 2023-03-01 15:00:00 due_date: 2023-03-01 15:00:00
created: 2018-12-01 01:12:04 created: 2018-12-01 01:12:04
updated: 2018-12-01 01:12:04 updated: 2018-12-01 01:12:04
bucket_id: 1 bucket_id: 38
position: 39 position: 39
- id: 41
uid: 'uid-caldav-test-parent-task'
title: 'Parent task for Caldav Test'
description: 'Description Caldav Test'
priority: 3
done: false
created_by_id: 15
project_id: 36
index: 40
due_date: 2023-03-01 15:00:00
created: 2018-12-01 01:12:04
updated: 2018-12-01 01:12:04
bucket_id: 38
position: 40
- id: 42
uid: 'uid-caldav-test-parent-task-2'
title: 'Parent task for Caldav Test 2'
description: 'Description Caldav Test 2'
priority: 3
done: false
created_by_id: 15
project_id: 36
index: 41
due_date: 2023-03-01 15:00:00
created: 2018-12-01 01:12:04
updated: 2018-12-01 01:12:04
bucket_id: 38
position: 41
- id: 43
uid: 'uid-caldav-test-child-task'
title: 'Child task for Caldav Test'
description: 'Description Caldav Test'
priority: 3
done: false
created_by_id: 15
project_id: 36
index: 42
due_date: 2023-03-01 15:00:00
created: 2018-12-01 01:12:04
updated: 2018-12-01 01:12:04
bucket_id: 38
position: 42
- id: 44
uid: 'uid-caldav-test-child-task-2'
title: 'Child task for Caldav Test 2'
description: 'Description Caldav Test'
priority: 3
done: false
created_by_id: 15
project_id: 36
index: 43
due_date: 2023-03-01 15:00:00
created: 2018-12-01 01:12:04
updated: 2018-12-01 01:12:04
bucket_id: 38
position: 43

View File

@ -1,52 +0,0 @@
- 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,3 +52,45 @@
right: 0 right: 0
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
created: 2018-12-01 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,15 +11,6 @@
- id: 4 - id: 4
name: testteam4_admin_on_project8 name: testteam4_admin_on_project8
created_by_id: 1 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 - id: 8
name: testteam8 name: testteam8
created_by_id: 7 created_by_id: 7

View File

@ -12,6 +12,7 @@
password: '$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.' # 1234 password: '$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.' # 1234
email: 'user2@example.com' email: 'user2@example.com'
issuer: local issuer: local
default_project_id: 4
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12 created: 2018-12-01 15:13:12
- -
@ -20,6 +21,7 @@
password: '$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.' # 1234 password: '$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.' # 1234
email: 'user3@example.com' email: 'user3@example.com'
issuer: local issuer: local
default_project_id: 4
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12 created: 2018-12-01 15:13:12
- -
@ -116,3 +118,11 @@
issuer: local issuer: local
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
created: 2018-12-01 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

@ -1,52 +0,0 @@
- 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,8 +47,68 @@
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12 created: 2018-12-01 15:13:12
- id: 9 - 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 user_id: 15
project_id: 26 project_id: 26
right: 0 right: 0
updated: 2018-12-02 15:13:12 updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12 created: 2018-12-01 15:13:12
- id: 18
user_id: 15
project_id: 36
right: 0
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
- id: 19
user_id: 15
project_id: 38
right: 0
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12

View File

@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life. // Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved. // Copyright 2018-present Vikunja and contributors. All rights reserved.
// //
// This program is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU Affero General Public Licensee as published by
@ -22,7 +22,7 @@ import (
) )
// ILIKE returns an ILIKE query on postgres and a LIKE query on all other platforms. // ILIKE returns an ILIKE query on postgres and a LIKE query on all other platforms.
// Postgres' is case sensitive by default. // Postgres' is case-sensitive by default.
// To work around this, we're using ILIKE as opposed to normal LIKE statements. // To work around this, we're using ILIKE as opposed to normal LIKE statements.
// ILIKE is preferred over LOWER(text) LIKE for performance reasons. // ILIKE is preferred over LOWER(text) LIKE for performance reasons.
// See https://stackoverflow.com/q/7005302/10924593 // See https://stackoverflow.com/q/7005302/10924593

View File

@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life. // Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved. // Copyright 2018-present Vikunja and contributors. All rights reserved.
// //
// This program is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU Affero General Public Licensee as published by
@ -17,6 +17,7 @@
package db package db
import ( import (
"encoding/json"
"fmt" "fmt"
"os" "os"
"testing" "testing"
@ -51,7 +52,7 @@ func CreateTestEngine() (engine *xorm.Engine, err error) {
} }
engine.SetMapper(names.GonicMapper{}) engine.SetMapper(names.GonicMapper{})
logger := log.NewXormLogger("DEBUG") logger := log.NewXormLogger(config.LogEnabled.GetBool(), config.LogDatabase.GetString(), "DEBUG")
logger.ShowSQL(os.Getenv("UNIT_TESTS_VERBOSE") == "1") logger.ShowSQL(os.Getenv("UNIT_TESTS_VERBOSE") == "1")
engine.SetLogger(logger) engine.SetLogger(logger)
engine.SetTZLocation(config.GetTimeZone()) engine.SetTZLocation(config.GetTimeZone())
@ -93,7 +94,16 @@ func AssertExists(t *testing.T, table string, values map[string]interface{}, cus
exists, err = x.Table(table).Where(values).Get(&v) 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)) assert.NoError(t, err, fmt.Sprintf("Failed to assert entries exist in db, error was: %s", err))
assert.True(t, exists, fmt.Sprintf("Entries %v do not exist in table %s", values, table)) 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)))
}
} }
// AssertMissing checks and asserts the nonexiste nce of certain entries in the db // 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. // Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved. // Copyright 2018-present Vikunja and contributors. All rights reserved.
// //
// This program is free software: you can redistribute it and/or modify // 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 // 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. // Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved. // Copyright 2018-present Vikunja and contributors. All rights reserved.
// //
// This program is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU Affero General Public Licensee as published by
@ -21,6 +21,7 @@ import (
"encoding/json" "encoding/json"
"time" "time"
"code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/log" "code.vikunja.io/api/pkg/log"
vmetrics "code.vikunja.io/api/pkg/metrics" vmetrics "code.vikunja.io/api/pkg/metrics"
"github.com/ThreeDotsLabs/watermill" "github.com/ThreeDotsLabs/watermill"
@ -39,7 +40,7 @@ type Event interface {
// InitEvents sets up everything needed to work with events // InitEvents sets up everything needed to work with events
func InitEvents() (err error) { func InitEvents() (err error) {
logger := log.NewWatermillLogger() logger := log.NewWatermillLogger(config.LogEnabled.GetBool(), config.LogEvents.GetString(), config.LogEventsLevel.GetString())
router, err := message.NewRouter( router, err := message.NewRouter(
message.RouterConfig{}, message.RouterConfig{},

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