Compare commits

...

70 Commits

Author SHA1 Message Date
kolaente 6259dd8d42
fix(ci): sign drone config 2023-01-24 17:47:42 +01:00
kolaente 1d96d220eb
fix(ci): save generated .tags file to correctly tag docker releases 2023-01-24 17:47:12 +01:00
kolaente 65f8a816ff
chore: 0.20.2 release preperations 2023-01-24 17:12:59 +01:00
renovate 766f676bea
fix(deps): update module github.com/labstack/echo-jwt/v4 to v4.0.1 (#1369)
Reviewed-on: vikunja/api#1369
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-01-24 17:10:13 +01:00
kolaente 41d6df833c
fix(migration): use the proper authorization method for Todoist's api, fix issues with importing deleted items 2023-01-24 16:25:51 +01:00
kooshi 135b5fb24e
fix(migration): import TickTick data by column name instead of index (#1356)
Resolves: https://github.com/go-vikunja/api/issues/61
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#1356
Co-authored-by: kooshi <kolaente.dev@pat.de.com>
Co-committed-by: kooshi <kolaente.dev@pat.de.com>
2023-01-24 16:25:50 +01:00
kolaente 55c9c791ed
fix(user): make reset the user's name to empty actually work 2023-01-23 18:33:02 +01:00
kolaente cedc2c3634
fix(reminders): prevent duplicate reminders when updating task details 2023-01-23 18:33:02 +01:00
renovate adb6670e9e
fix(deps): update module github.com/spf13/viper to v1.15.0 (#1365)
Reviewed-on: vikunja/api#1365
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-01-19 23:49:18 +01:00
renovate f3d5993d82
fix(deps): update module src.techknowlogick.com/xgo to v1.7.0+1.19.5 (#1364)
Reviewed-on: vikunja/api#1364
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-01-19 23:49:17 +01:00
renovate f383c4e27b
fix(deps): update module github.com/getsentry/sentry-go to v0.17.0 (#1361)
Reviewed-on: vikunja/api#1361
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-01-19 23:49:15 +01:00
renovate 8d23a78647
chore(deps): update klakegg/hugo docker tag to v0.107.0 (#1272)
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#1272
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-01-19 23:49:14 +01:00
kolaente 75e9056d7b
fix(export): ignore file size for export files 2023-01-19 23:49:13 +01:00
kolaente 8ab083582e
fix(tasks): don't set a repeating task done when moving it do the done bucket 2023-01-19 23:49:11 +01:00
kolaente 65d26a162d
fix(ci): tagging logic for release docker images 2023-01-19 23:49:10 +01:00
renovate 5095f5603d
fix(deps): update module src.techknowlogick.com/xgo to v1.6.0+1.19.5 (#1358)
Reviewed-on: vikunja/api#1358
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-01-19 23:49:08 +01:00
Rein 05e2286dfc
fix(mailer): forcessl config (#60)
Co-authored-by: Rein-R3 <rein@reinsan.top>
Reviewed-At: https://github.com/go-vikunja/api/pull/60
2023-01-19 23:49:07 +01:00
renovate 91fc21679a
fix(deps): update module github.com/wneessen/go-mail to v0.3.8 (#1357)
Reviewed-on: vikunja/api#1357
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-01-19 23:49:05 +01:00
renovate 2d5a41bdcf
chore(deps): update goreleaser/nfpm docker tag to v2.23.0 (#1347)
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#1347
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-01-19 23:49:05 +01:00
Dominik Pschenitschni c0b12a60da
fix(drone): add type, fix pull, remove group (#1355)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#1355
Co-authored-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-committed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
2023-01-19 23:49:04 +01:00
TheDubliner 7224032ed8
fix(docs): fix a few minor typos (#59) 2023-01-19 23:49:03 +01:00
renovate f05ce69db8
fix(deps): update module golang.org/x/crypto to v0.5.0 (#1353)
Reviewed-on: vikunja/api#1353
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-01-19 23:48:58 +01:00
renovate c629fa0676
fix(deps): update module golang.org/x/term to v0.4.0 (#1352)
Reviewed-on: vikunja/api#1352
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-01-19 23:48:56 +01:00
renovate 3b03ab719e
fix(deps): update module golang.org/x/image to v0.3.0 (#1350)
Reviewed-on: vikunja/api#1350
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-01-19 23:48:54 +01:00
renovate 66e147b992
fix(deps): update module golang.org/x/sys to v0.4.0 (#1351)
Reviewed-on: vikunja/api#1351
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-01-19 23:48:52 +01:00
renovate c6613c4530
fix(deps): update module github.com/coreos/go-oidc/v3 to v3.5.0 (#1349)
Reviewed-on: vikunja/api#1349
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-01-19 23:48:50 +01:00
kolaente a0a1f4f9cb
fix(ci): set release path to /source
Should fix https://github.com/techknowlogick/xgo/issues/187
2023-01-19 23:48:48 +01:00
renovate 02f7f56ba2
fix(deps): update module github.com/wneessen/go-mail to v0.3.7 (#1348)
Reviewed-on: vikunja/api#1348
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-01-19 23:48:46 +01:00
kolaente 7b1e1c79e3
chore: remove custom gitea bug template in favor of githubs 2023-01-19 23:48:45 +01:00
konrad b5d322e4b6
feat(migrators): remove wunderlist (#1346)
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#1346
2023-01-19 23:48:43 +01:00
Dominik Pschenitschni 60dfce54ec
fix(docs): old helm charts url (#1344)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/api#1344
Co-authored-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-committed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
2023-01-19 23:48:41 +01:00
renovate 7fbaf3afff
fix(deps): update module github.com/labstack/echo/v4 to v4.10.0 (#1343)
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#1343
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-01-19 23:48:40 +01:00
renovate 1950692e85
fix(deps): update module github.com/wneessen/go-mail to v0.3.6 (#1342)
Reviewed-on: vikunja/api#1342
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-01-19 23:48:38 +01:00
kolaente 446525837c
fix(caldav): use const for repeat modes 2023-01-19 23:48:37 +01:00
kolaente bffbc9492f
fix(tasks): don't reset the kanban bucket when updating a task and not providing one
Resolves https://github.com/go-vikunja/api/issues/56
2023-01-19 23:47:38 +01:00
kolaente 75a57dafa6
fix(list): return lists for a namespace id even if that namespace is deleted
This fixes a problem where a namespace was deleted and its list were not.
Forum discussion: https://community.vikunja.io/t/list-deleted-but-tasks-showing-up-as-archived-and-overdue/1025
2023-01-19 23:47:36 +01:00
kolaente b59322dec4
fix(ci): pin nfpm container version and binary location 2023-01-19 23:47:34 +01:00
kolaente 4f39aa21e9
fix(migration): use Todoist v9 api to migrate tasks from them
Discussion: https://community.vikunja.io/t/importing-tasks-from-todoist/322/7
2023-01-19 23:47:32 +01:00
renovate 629d6a278f
fix(deps): update module github.com/swaggo/swag to v1.8.9 (#1327)
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#1327
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-01-19 23:47:30 +01:00
renovate c58584bd63
fix(deps): update module github.com/pquerna/otp to v1.4.0 (#1341)
Reviewed-on: vikunja/api#1341
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-01-19 23:47:28 +01:00
renovate b4f7e286f9
fix(deps): update module golang.org/x/crypto to v0.4.0 (#1339)
Reviewed-on: vikunja/api#1339
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-01-19 23:47:27 +01:00
renovate 06d822b547
fix(deps): update module github.com/getsentry/sentry-go to v0.16.0 (#1338)
Reviewed-on: vikunja/api#1338
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-01-19 23:47:25 +01:00
renovate 7e272a9abe
fix(deps): update module golang.org/x/oauth2 to v0.3.0 (#1337)
Reviewed-on: vikunja/api#1337
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-01-19 23:47:23 +01:00
renovate e8626ae543
fix(deps): update module golang.org/x/oauth2 to v0.2.0 (#1316)
Reviewed-on: vikunja/api#1316
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-01-19 23:47:21 +01:00
renovate 3875f9ff9d
fix(deps): update module golang.org/x/image to v0.2.0 (#1335)
Reviewed-on: vikunja/api#1335
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-01-19 23:47:19 +01:00
renovate 962c96fbb7
fix(deps): update module golang.org/x/term to v0.3.0 (#1336)
Reviewed-on: vikunja/api#1336
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-01-19 23:47:18 +01:00
kolaente 61a1e6d057
fix(caldav): add Z suffix to dates make it clear dates are in UTC 2023-01-19 23:47:16 +01:00
renovate 1cf4a3fdff
fix(deps): update module golang.org/x/sys to v0.3.0 (#1333)
Reviewed-on: vikunja/api#1333
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-01-19 23:47:14 +01:00
kolaente 1e0dcba7f9
feat(release): use compressed binaries for package releases
vikunja/api#1330
2023-01-19 23:47:12 +01:00
kolaente 42c45155fa
fix(restore): check if we're really dealing with a string 2023-01-19 23:47:10 +01:00
renovate 98b8744e03
fix(deps): update module github.com/go-sql-driver/mysql to v1.7.0 (#1332)
Reviewed-on: vikunja/api#1332
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-01-19 23:47:08 +01:00
kolaente b6073db9f2
fix(build): downgrade xgo to 1.19.2 so that builds work again
See https://github.com/techknowlogick/xgo/issues/187
2023-01-19 23:47:07 +01:00
kolaente e7f1f6cde7
fix(reminders): make sure an overdue reminder is sent when there is only one overdue task 2023-01-19 23:47:05 +01:00
kolaente 8ed7aec7a0
fix(reminders): overdue tasks join condition 2023-01-19 23:47:03 +01:00
kolaente 454ba32fe3
fix(tasks): don't include undone overdue tasks from archived lists or namespaces in notification mails
Resolves vikunja/api#1324
2023-01-19 23:47:01 +01:00
kolaente 94b08c0307
fix(dump): make sure null dates are properly set when restoring from a dump 2023-01-19 23:47:00 +01:00
kolaente 15811fd4d4
fix: restore notifications table from dump when it already had the correct format 2023-01-19 23:46:59 +01:00
renovate 642b2067bf
fix(deps): update module github.com/golang-jwt/jwt/v4 to v4.4.3 (#1328)
Reviewed-on: vikunja/api#1328
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-01-19 23:46:59 +01:00
renovate dab108ae86
fix(deps): update github.com/arran4/golang-ical digest to 1093469 (#1326)
Reviewed-on: vikunja/api#1326
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-01-19 23:46:58 +01:00
renovate 652b9f0776
fix(deps): update module github.com/wneessen/go-mail to v0.3.5 (#1325)
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#1325
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-01-19 23:46:57 +01:00
renovate 5e374e695e
fix(deps): update github.com/arran4/golang-ical digest to a677353 (#1323)
Reviewed-on: vikunja/api#1323
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-01-19 23:46:55 +01:00
renovate 93162715e3
fix(deps): update module golang.org/x/crypto to v0.3.0 (#1321)
Reviewed-on: vikunja/api#1321
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-01-19 23:46:53 +01:00
kolaente 6f841d2ee8
fix(docs): add docs about cli user delete 2023-01-19 23:46:52 +01:00
renovate 6ac8dd7145
fix(deps): update module github.com/spf13/afero to v1.9.3 (#1320)
Reviewed-on: vikunja/api#1320
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-01-19 23:46:51 +01:00
the-darkvoid dec7f75c65
Added Google & Google Workspace to OpenId examples (#1319)
Reviewed-on: vikunja/api#1319
Co-authored-by: the-darkvoid <darkvoid@gmail.com>
Co-committed-by: the-darkvoid <darkvoid@gmail.com>
2023-01-19 23:46:49 +01:00
kolaente 03c79da74e
fix(ci): pipeline dependency 2023-01-19 23:46:47 +01:00
kolaente 9bd6795266
feat: use docker buildx to build multiarch images 2023-01-19 23:46:45 +01:00
renovate 961f3e244f
fix(deps): update module golang.org/x/crypto to v0.2.0 (#1315)
Reviewed-on: vikunja/api#1315
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-01-19 23:46:44 +01:00
renovate 6ee0a605fe
fix(deps): update module github.com/yuin/goldmark to v1.5.3 (#1317)
Reviewed-on: vikunja/api#1317
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-01-19 23:46:44 +01:00
kolaente 41c9e3f9a4
fix: swagger docs 2023-01-19 23:46:44 +01:00
55 changed files with 1604 additions and 2655 deletions

View File

@ -1,4 +1,7 @@
files/
dist/
logs/
Dockerfile
docker-manifest.tmpl
docker-manifest-unstable.tmpl

View File

@ -1,5 +1,6 @@
---
kind: pipeline
type: docker
name: testing
workspace:
@ -111,7 +112,7 @@ steps:
# compiling the same magefile at the same time. It's also faster if each step does not need to compile it first.
- name: mage
image: vikunja/golang-build:latest
pull: true
pull: always
environment:
GOPROXY: 'https://goproxy.kolaente.de'
commands:
@ -122,7 +123,7 @@ steps:
- name: build
image: vikunja/golang-build:latest
pull: true
pull: always
environment:
GOPROXY: 'https://goproxy.kolaente.de'
depends_on: [ mage ]
@ -133,7 +134,7 @@ steps:
- name: lint
image: golang:1.19-alpine
pull: true
pull: always
environment:
GOPROXY: 'https://goproxy.kolaente.de'
depends_on: [ build ]
@ -147,7 +148,7 @@ steps:
- name: test-migration-prepare
image: kolaente/toolbox:latest
pull: true
pull: always
commands:
# Get the latest version
- wget https://dl.vikunja.io/api/unstable/vikunja-unstable-linux-amd64-full.zip -q -O vikunja-latest.zip
@ -155,7 +156,7 @@ steps:
- name: test-migration-sqlite
image: vikunja/golang-build:latest
pull: true
pull: always
depends_on: [ test-migration-prepare, build ]
environment:
VIKUNJA_DATABASE_TYPE: sqlite
@ -174,7 +175,7 @@ steps:
- name: test-migration-mysql
image: vikunja/golang-build:latest
pull: true
pull: always
depends_on: [ test-migration-prepare, build ]
environment:
VIKUNJA_DATABASE_TYPE: mysql
@ -193,7 +194,7 @@ steps:
- name: test-migration-psql
image: vikunja/golang-build:latest
pull: true
pull: always
depends_on: [ test-migration-prepare, build ]
environment:
VIKUNJA_DATABASE_TYPE: postgres
@ -213,7 +214,7 @@ steps:
- name: test
image: vikunja/golang-build:latest
pull: true
pull: always
environment:
GOPROXY: 'https://goproxy.kolaente.de'
commands:
@ -224,7 +225,7 @@ steps:
- name: test-sqlite
image: vikunja/golang-build:latest
pull: true
pull: always
environment:
GOPROXY: 'https://goproxy.kolaente.de'
VIKUNJA_TESTS_USE_CONFIG: 1
@ -241,7 +242,7 @@ steps:
- name: test-mysql
image: vikunja/golang-build:latest
pull: true
pull: always
environment:
GOPROXY: 'https://goproxy.kolaente.de'
VIKUNJA_TESTS_USE_CONFIG: 1
@ -258,7 +259,7 @@ steps:
- name: test-postgres
image: vikunja/golang-build:latest
pull: true
pull: always
environment:
GOPROXY: 'https://goproxy.kolaente.de'
VIKUNJA_TESTS_USE_CONFIG: 1
@ -276,7 +277,7 @@ steps:
- name: integration-test
image: vikunja/golang-build:latest
pull: true
pull: always
environment:
GOPROXY: 'https://goproxy.kolaente.de'
commands:
@ -287,7 +288,7 @@ steps:
- name: integration-test-sqlite
image: vikunja/golang-build:latest
pull: true
pull: always
environment:
GOPROXY: 'https://goproxy.kolaente.de'
VIKUNJA_TESTS_USE_CONFIG: 1
@ -304,7 +305,7 @@ steps:
- name: integration-test-mysql
image: vikunja/golang-build:latest
pull: true
pull: always
environment:
GOPROXY: 'https://goproxy.kolaente.de'
VIKUNJA_TESTS_USE_CONFIG: 1
@ -321,7 +322,7 @@ steps:
- name: integration-test-postgres
image: vikunja/golang-build:latest
pull: true
pull: always
environment:
GOPROXY: 'https://goproxy.kolaente.de'
VIKUNJA_TESTS_USE_CONFIG: 1
@ -343,14 +344,15 @@ steps:
########
kind: pipeline
type: docker
name: release
depends_on:
- testing
workspace:
base: /go
path: src/code.vikunja.io/api
base: /source
path: /
trigger:
ref:
@ -368,7 +370,7 @@ steps:
# compiling the same magefile at the same time. It's also faster if each step does not need to compile it first.
- name: mage
image: vikunja/golang-build:latest
pull: true
pull: always
environment:
GOPROXY: 'https://goproxy.kolaente.de'
commands:
@ -378,7 +380,7 @@ steps:
- name: before-static-build
image: techknowlogick/xgo:latest
pull: true
pull: always
commands:
- export PATH=$PATH:$GOPATH/bin
- go install github.com/magefile/mage
@ -387,7 +389,7 @@ steps:
- name: static-build-windows
image: techknowlogick/xgo:latest
pull: true
pull: always
environment:
# 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.
@ -400,7 +402,7 @@ steps:
- name: static-build-linux
image: techknowlogick/xgo:latest
pull: true
pull: always
environment:
# 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.
@ -413,7 +415,7 @@ steps:
- name: static-build-darwin
image: techknowlogick/xgo:latest
pull: true
pull: always
environment:
# 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.
@ -426,7 +428,7 @@ steps:
- name: after-build-compress
image: kolaente/upx
pull: true
pull: always
depends_on:
- static-build-windows
- static-build-linux
@ -436,7 +438,7 @@ steps:
- name: after-build-static
image: techknowlogick/xgo:latest
pull: true
pull: always
depends_on:
- after-build-compress
commands:
@ -448,7 +450,7 @@ steps:
- name: sign-release
image: plugins/gpgsign:1
pull: true
pull: always
depends_on: [ after-build-static ]
settings:
key:
@ -462,7 +464,7 @@ steps:
# Push the releases to our pseudo-s3-bucket
- name: release-latest
image: plugins/s3
pull: true
pull: always
settings:
bucket: vikunja-releases
access_key:
@ -484,7 +486,7 @@ steps:
- name: release-version
image: plugins/s3
pull: true
pull: always
settings:
bucket: vikunja-releases
access_key:
@ -504,8 +506,8 @@ steps:
# Build os packages and push it to our bucket
- name: build-os-packages-unstable
image: goreleaser/nfpm
pull: true
image: goreleaser/nfpm:v2.23.0
pull: always
commands:
- apk add git go
- ./mage-static release:packages
@ -517,11 +519,11 @@ steps:
- main
event:
- push
depends_on: [ static-build-linux ]
depends_on: [ after-build-compress ]
- name: build-os-packages-version
image: goreleaser/nfpm
pull: true
image: goreleaser/nfpm:v2.23.0
pull: always
commands:
- apk add git go
- ./mage-static release:packages
@ -531,12 +533,12 @@ steps:
when:
event:
- tag
depends_on: [ static-build-linux ]
depends_on: [ after-build-compress ]
# Push the os releases to our pseudo-s3-bucket
- name: release-os-latest
image: plugins/s3
pull: true
pull: always
settings:
bucket: vikunja-releases
access_key:
@ -558,7 +560,7 @@ steps:
- name: release-os-version
image: plugins/s3
pull: true
pull: always
settings:
bucket: vikunja-releases
access_key:
@ -578,6 +580,7 @@ steps:
---
kind: pipeline
type: docker
name: deploy-docs
workspace:
@ -596,8 +599,7 @@ trigger:
steps:
- name: theme
image: kolaente/toolbox
pull: true
group: build-static
pull: always
commands:
- mkdir docs/themes/vikunja -p
- cd docs/themes/vikunja
@ -605,8 +607,8 @@ steps:
- tar -xzf vikunja-theme.tar.gz
- name: build
image: klakegg/hugo:0.104.2
pull: true
image: klakegg/hugo:0.107.0
pull: always
commands:
- cd docs
- hugo
@ -614,7 +616,7 @@ steps:
- name: docker
image: plugins/docker
pull: true
pull: always
settings:
username:
from_secret: docker_username
@ -627,102 +629,11 @@ steps:
---
kind: pipeline
type: docker
name: docker-arm-release
name: docker-release
depends_on:
- testing
platform:
os: linux
arch: arm64
trigger:
ref:
- refs/heads/main
- "refs/tags/**"
steps:
- name: fetch-tags
image: docker:git
commands:
- git fetch --tags
- name: docker-arm-unstable
image: plugins/docker:linux-arm
pull: true
settings:
username:
from_secret: docker_username
password:
from_secret: docker_password
repo: vikunja/api
tags: unstable-linux-arm
dockerfile: Dockerfile.arm32
depends_on: [ fetch-tags ]
when:
ref:
- refs/heads/main
- name: docker-arm
image: plugins/docker:linux-arm
pull: true
settings:
username:
from_secret: docker_username
password:
from_secret: docker_password
repo: vikunja/api
auto_tag: true
auto_tag_suffix: linux-arm
dockerfile: Dockerfile.arm32
depends_on: [ fetch-tags ]
when:
ref:
- "refs/tags/**"
- name: docker-arm64-unstable
image: plugins/docker:linux-arm64
pull: true
settings:
username:
from_secret: docker_username
password:
from_secret: docker_password
repo: vikunja/api
tags: unstable-linux-arm64
depends_on: [ fetch-tags ]
when:
ref:
- refs/heads/main
- name: docker-arm64
image: plugins/docker:linux-arm64
pull: true
settings:
username:
from_secret: docker_username
password:
from_secret: docker_password
repo: vikunja/api
auto_tag: true
auto_tag_suffix: linux-arm64
depends_on: [ fetch-tags ]
when:
ref:
- "refs/tags/**"
---
kind: pipeline
type: docker
name: docker-amd64-release
depends_on:
- testing
platform:
os: linux
arch: amd64
trigger:
ref:
- refs/heads/main
@ -735,94 +646,55 @@ steps:
- git fetch --tags
- name: docker-unstable
image: plugins/docker:linux-amd64
pull: true
settings:
username:
from_secret: docker_username
password:
from_secret: docker_password
repo: vikunja/api
tags: unstable-linux-amd64
depends_on: [ fetch-tags ]
when:
ref:
- refs/heads/main
- name: docker
image: plugins/docker:linux-amd64
pull: true
settings:
username:
from_secret: docker_username
password:
from_secret: docker_password
repo: vikunja/api
auto_tag: true
auto_tag_suffix: linux-amd64
depends_on: [ fetch-tags ]
when:
ref:
- "refs/tags/**"
---
kind: pipeline
type: docker
name: docker-manifest
trigger:
ref:
- refs/heads/main
- "refs/tags/**"
depends_on:
- docker-amd64-release
- docker-arm-release
steps:
- name: manifest-unstable
image: thegeeklab/drone-docker-buildx
privileged: true
pull: always
image: plugins/manifest
settings:
username:
from_secret: docker_username
password:
from_secret: docker_password
repo: vikunja/api
tags: unstable
ignore_missing: true
spec: docker-manifest-unstable.tmpl
password:
from_secret: docker_password
username:
from_secret: docker_username
platforms:
- linux/386
- linux/amd64
- linux/arm/v6
- linux/arm/v7
- linux/arm64/v8
depends_on: [ fetch-tags ]
when:
ref:
- refs/heads/main
- name: manifest-release
pull: always
image: plugins/manifest
settings:
auto_tag: true
ignore_missing: true
spec: docker-manifest.tmpl
password:
from_secret: docker_password
username:
from_secret: docker_username
- name: generate-tags
image: thegeeklab/docker-autotag
environment:
DOCKER_AUTOTAG_VERSION: ${DRONE_TAG}
DOCKER_AUTOTAG_EXTRA_TAGS: latest
DOCKER_AUTOTAG_OUTPUT_FILE: .tags
depends_on: [ fetch-tags ]
when:
ref:
- "refs/tags/**"
- name: manifest-release-latest
- name: docker-release
image: thegeeklab/drone-docker-buildx
privileged: true
pull: always
image: plugins/manifest
depends_on:
- clone
settings:
tags: latest
ignore_missing: true
spec: docker-manifest.tmpl
password:
from_secret: docker_password
username:
from_secret: docker_username
password:
from_secret: docker_password
repo: vikunja/api
platforms:
- linux/386
- linux/amd64
- linux/arm/v6
- linux/arm/v7
- linux/arm64/v8
depends_on: [ generate-tags ]
when:
ref:
- "refs/tags/**"
@ -841,9 +713,7 @@ depends_on:
- testing
- release
- deploy-docs
- docker-arm-release
- docker-amd64-release
- docker-manifest
- docker-release
steps:
- name: notify
@ -861,6 +731,6 @@ steps:
- failure
---
kind: signature
hmac: 5500acb776acae4975592637767035c45af277f1f56c8d10ffd99f38761cd5c8
hmac: 148171b051fff279f249608111b56ef0fb5a09a8944ce7aee3e3cf7cf5b6fe4a
...

View File

@ -1,58 +0,0 @@
name: Bug Report
description: Found something you weren't expecting? Report it here!
labels: kind/bug
body:
- type: markdown
attributes:
value: |
NOTE: If your issue is a security concern, please send an email to security@vikunja.io instead of opening a public issue.
- type: markdown
attributes:
value: |
Please fill out this issue template to report a bug.
1. If you want to propose a new feature, please open a discussion thread in the forum: https://community.vikunja.io
2. Please ask questions or configuration/deploy problems on our [Matrix Room](https://matrix.to/#/#vikunja:matrix.org) or forum (https://community.vikunja.io).
3. Make sure you are using the latest release and
take a moment to check that your issue hasn't been reported before.
4. Please give all relevant information below for bug reports, because
incomplete details will be handled as an invalid report and closed.
- type: textarea
id: description
attributes:
label: Description
description: |
Please provide a description of your issue here, with a URL if you were able to reproduce the issue (see below).
- type: input
id: frontend-version
attributes:
label: Vikunja Frontend Version
description: Vikunja frontend version (or commit reference) of your instance
validations:
required: true
- type: input
id: api-version
attributes:
label: Vikunja API Version
description: Vikunja API version (or commit reference) of your instance
validations:
required: true
- type: input
id: browser-version
attributes:
label: Browser and version
description: If your issue is related to a frontend problem, please provide the browser and version you used to reproduce it.
- type: dropdown
id: can-reproduce
attributes:
label: Can you reproduce the bug on the Vikunja demo site?
options:
- "Yes"
- "No"
validations:
required: true
- type: textarea
id: screenshots
attributes:
label: Screenshots
description: If this issue involves the Web Interface, please provide one or more screenshots

View File

@ -7,6 +7,90 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
All releases can be found on https://code.vikunja.io/api/releases.
## [0.20.2] - 2023-01-24
### Bug Fixes
* *(build)* Downgrade xgo to 1.19.2 so that builds work again
* *(caldav)* Add Z suffix to dates make it clear dates are in UTC
* *(caldav)* Use const for repeat modes
* *(ci)* Pipeline dependency
* *(ci)* Pin nfpm container version and binary location
* *(ci)* Set release path to /source
* *(ci)* Tagging logic for release docker images
* *(docs)* Add docs about cli user delete
* *(docs)* Old helm charts url (#1344)
* *(docs)* Fix a few minor typos (#59)
* *(drone)* Add type, fix pull, remove group (#1355)
* *(dump)* Make sure null dates are properly set when restoring from a dump
* *(export)* Ignore file size for export files
* *(list)* Return lists for a namespace id even if that namespace is deleted
* *(mailer)* Forcessl config (#60)
* *(migration)* Use Todoist v9 api to migrate tasks from them
* *(migration)* Import TickTick data by column name instead of index (#1356)
* *(migration)* Use the proper authorization method for Todoist's api, fix issues with importing deleted items
* *(reminders)* Overdue tasks join condition
* *(reminders)* Make sure an overdue reminder is sent when there is only one overdue task
* *(reminders)* Prevent duplicate reminders when updating task details
* *(restore)* Check if we're really dealing with a string
* *(tasks)* Don't include undone overdue tasks from archived lists or namespaces in notification mails
* *(tasks)* Don't reset the kanban bucket when updating a task and not providing one
* *(tasks)* Don't set a repeating task done when moving it do the done bucket
* *(user)* Make reset the user's name to empty actually work* Swagger docs ([41c9e3f](41c9e3f9a47280887b56941280904aea6ef31f85))
* Restore notifications table from dump when it already had the correct format ([15811fd](15811fd4d4485cd25cf8d2f8fdd04ebfea8e6663))
### Dependencies
* *(deps)* Update module github.com/yuin/goldmark to v1.5.3 (#1317)
* *(deps)* Update module golang.org/x/crypto to v0.2.0 (#1315)
* *(deps)* Update module github.com/spf13/afero to v1.9.3 (#1320)
* *(deps)* Update module golang.org/x/crypto to v0.3.0 (#1321)
* *(deps)* Update github.com/arran4/golang-ical digest to a677353 (#1323)
* *(deps)* Update module github.com/wneessen/go-mail to v0.3.5 (#1325)
* *(deps)* Update github.com/arran4/golang-ical digest to 1093469 (#1326)
* *(deps)* Update module github.com/golang-jwt/jwt/v4 to v4.4.3 (#1328)
* *(deps)* Update module github.com/go-sql-driver/mysql to v1.7.0 (#1332)
* *(deps)* Update module golang.org/x/sys to v0.3.0 (#1333)
* *(deps)* Update module golang.org/x/term to v0.3.0 (#1336)
* *(deps)* Update module golang.org/x/image to v0.2.0 (#1335)
* *(deps)* Update module golang.org/x/oauth2 to v0.2.0 (#1316)
* *(deps)* Update module golang.org/x/oauth2 to v0.3.0 (#1337)
* *(deps)* Update module github.com/getsentry/sentry-go to v0.16.0 (#1338)
* *(deps)* Update module golang.org/x/crypto to v0.4.0 (#1339)
* *(deps)* Update module github.com/pquerna/otp to v1.4.0 (#1341)
* *(deps)* Update module github.com/swaggo/swag to v1.8.9 (#1327)
* *(deps)* Update module github.com/wneessen/go-mail to v0.3.6 (#1342)
* *(deps)* Update module github.com/labstack/echo/v4 to v4.10.0 (#1343)
* *(deps)* Update module github.com/wneessen/go-mail to v0.3.7 (#1348)
* *(deps)* Update module github.com/coreos/go-oidc/v3 to v3.5.0 (#1349)
* *(deps)* Update module golang.org/x/sys to v0.4.0 (#1351)
* *(deps)* Update module golang.org/x/image to v0.3.0 (#1350)
* *(deps)* Update module golang.org/x/term to v0.4.0 (#1352)
* *(deps)* Update module golang.org/x/crypto to v0.5.0 (#1353)
* *(deps)* Update goreleaser/nfpm docker tag to v2.23.0 (#1347)
* *(deps)* Update module github.com/wneessen/go-mail to v0.3.8 (#1357)
* *(deps)* Update module src.techknowlogick.com/xgo to v1.6.0+1.19.5 (#1358)
* *(deps)* Update klakegg/hugo docker tag to v0.107.0 (#1272)
* *(deps)* Update module github.com/getsentry/sentry-go to v0.17.0 (#1361)
* *(deps)* Update module src.techknowlogick.com/xgo to v1.7.0+1.19.5 (#1364)
* *(deps)* Update module github.com/spf13/viper to v1.15.0 (#1365)
* *(deps)* Update module github.com/labstack/echo-jwt/v4 to v4.0.1 (#1369)
### Features
* *(migrators)* Remove wunderlist (#1346)
* *(release)* Use compressed binaries for package releases
* Use docker buildx to build multiarch images ([9bd6795](9bd6795266fd54ae42664c20ed7633ac7daf6199))
### Miscellaneous Tasks
* Remove custom gitea bug template in favor of githubs ([7b1e1c7](7b1e1c79e358f3fcecb217259491f016402cdcc7))
### Other
* *(other)* Added Google & Google Workspace to OpenId examples (#1319)
## [0.20.1] - 2022-11-11
### Bug Fixes

View File

@ -1,9 +1,9 @@
##############
# Build stage
FROM golang:1.19-alpine AS build-env
FROM --platform=$BUILDPLATFORM techknowlogick/xgo:go-1.19.2 AS build-env
RUN apk --no-cache add build-base git && \
RUN \
go install github.com/magefile/mage@latest && \
mv /go/bin/mage /usr/local/go/bin
@ -13,9 +13,11 @@ ARG VIKUNJA_VERSION
COPY . /go/src/code.vikunja.io/api
WORKDIR /go/src/code.vikunja.io/api
ARG TARGETOS TARGETARCH TARGETVARIANT
# Checkout version if set
RUN if [ -n "${VIKUNJA_VERSION}" ]; then git checkout "${VIKUNJA_VERSION}"; fi \
&& mage build:clean build
RUN if [ -n "${VIKUNJA_VERSION}" ]; then git checkout "${VIKUNJA_VERSION}"; fi && \
mage build:clean && \
mage release:xgo $TARGETOS/$TARGETARCH/$TARGETVARIANT
###################
# The actual image
@ -25,7 +27,7 @@ FROM alpine:3.16
LABEL maintainer="maintainers@vikunja.io"
WORKDIR /app/vikunja/
COPY --from=build-env /go/src/code.vikunja.io/api/vikunja .
COPY --from=build-env /build/vikunja-* vikunja
ENV VIKUNJA_SERVICE_ROOTPATH=/app/vikunja/
# Dynamic permission changing stuff

View File

@ -1,48 +0,0 @@
##############
# Build stage
FROM golang:1.19-buster AS build-env
RUN go install github.com/magefile/mage@latest && \
mv /go/bin/mage /usr/local/go/bin
ARG VIKUNJA_VERSION
# Setup repo
COPY . /go/src/code.vikunja.io/api
WORKDIR /go/src/code.vikunja.io/api
# Checkout version if set
RUN if [ -n "${VIKUNJA_VERSION}" ]; then git checkout "${VIKUNJA_VERSION}"; fi \
&& mage build:clean build
###################
# The actual image
# Note: I wanted to use the scratch image here, but unfortunatly the go-sqlite bindings require cgo and
# because of this, the container would not start when I compiled the image without cgo.
# We're using debian as a base image here because the latest alpine image does not work with arm.
FROM debian:buster-slim
LABEL maintainer="maintainers@vikunja.io"
WORKDIR /app/vikunja/
COPY --from=build-env /go/src/code.vikunja.io/api/vikunja .
ENV VIKUNJA_SERVICE_ROOTPATH=/app/vikunja/
# Dynamic permission changing stuff
ENV PUID 1000
ENV PGID 1000
RUN addgroup --gid ${PGID} vikunja && \
chown ${PUID} -R /app/vikunja && \
useradd --shell /bin/sh --gid vikunja --uid ${PUID} --home-dir /app/vikunja vikunja
COPY run.sh /run.sh
# Fix time zone settings not working
RUN apt-get update && apt-get install -y tzdata && apt-get clean
# Files permissions
RUN mkdir /app/vikunja/files && \
chown -R vikunja /app/vikunja/files
VOLUME /app/vikunja/files
CMD ["/run.sh"]
EXPOSE 3456

View File

@ -2,7 +2,7 @@
[![Build Status](https://drone.kolaente.de/api/badges/vikunja/api/status.svg)](https://drone.kolaente.de/vikunja/api)
[![License: AGPL v3](https://img.shields.io/badge/License-AGPL%20v3-blue.svg)](LICENSE)
[![Download](https://img.shields.io/badge/download-v0.20.1-brightgreen.svg)](https://dl.vikunja.io)
[![Download](https://img.shields.io/badge/download-v0.20.2-brightgreen.svg)](https://dl.vikunja.io)
[![Docker Pulls](https://img.shields.io/docker/pulls/vikunja/api.svg)](https://hub.docker.com/r/vikunja/api/)
[![Swagger Docs](https://img.shields.io/badge/swagger-docs-brightgreen.svg)](https://try.vikunja.io/api/v1/docs)
[![Go Report Card](https://goreportcard.com/badge/kolaente.dev/vikunja/api)](https://goreportcard.com/report/kolaente.dev/vikunja/api)

View File

@ -191,21 +191,6 @@ files:
maxsize: 20MB
migration:
# These are the settings for the wunderlist migrator
wunderlist:
# Wheter to enable the wunderlist migrator or not
enable: false
# The client id, required for making requests to the wunderlist api
# You need to register your vikunja instance at https://developer.wunderlist.com/apps/new to get this
clientid:
# The client secret, also required for making requests to the wunderlist api
clientsecret:
# The url where clients are redirected after they authorized Vikunja to access their wunderlist stuff.
# This needs to match the url you entered when registering your Vikunja instance at wunderlist.
# This is usually the frontend url where the frontend then makes a request to /migration/wunderlist/migrate
# with the code obtained from the wunderlist api.
# Note that the vikunja frontend expects this to be /migrate/wunderlist
redirecturl:
todoist:
# Wheter to enable the todoist migrator or not
enable: false

View File

@ -1,17 +0,0 @@
image: vikunja/api:unstable
manifests:
-
image: vikunja/api:unstable-linux-amd64
platform:
architecture: amd64
os: linux
-
image: vikunja/api:unstable-linux-arm64
platform:
architecture: arm64
os: linux
-
image: vikunja/api:unstable-linux-arm
platform:
architecture: arm
os: linux

View File

@ -1,23 +0,0 @@
image: vikunja/api:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}}
{{#if build.tags}}
tags:
{{#each build.tags}}
- {{this}}
{{/each}}
{{/if}}
manifests:
-
image: vikunja/api:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-amd64
platform:
architecture: amd64
os: linux
-
image: vikunja/api:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm64
platform:
architecture: arm64
os: linux
-
image: vikunja/api:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm
platform:
architecture: arm
os: linux

View File

@ -969,17 +969,6 @@ Environment path: `VIKUNJA_FILES_MAXSIZE`
### wunderlist
These are the settings for the wunderlist migrator
Default: `<empty>`
Full path: `migration.wunderlist`
Environment path: `VIKUNJA_MIGRATION_WUNDERLIST`
### todoist
Default: `<empty>`

View File

@ -10,7 +10,7 @@ menu:
# Full docker example
This docker compose configuration will run Vikunja with backend and frontend with a mariadb as database.
This docker compose configuration will run Vikunja with backend and frontend with a mariadb database.
It uses an nginx container or traefik on the host to proxy backend and frontend into a single port.
For all available configuration options, see [configuration]({{< ref "config.md">}}).
@ -76,7 +76,7 @@ This example lets you host Vikunja without any reverse proxy in front of it. Thi
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.
Not 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.
@ -125,7 +125,7 @@ services:
This example assumes [traefik](https://traefik.io) version 2 installed and configured to [use docker as a configuration provider](https://docs.traefik.io/providers/docker/).
We also make a few assumtions here which you'll most likely need to adjust for your traefik setup:
We also make a few assumptions here which you'll most likely need to adjust for your traefik setup:
* Your domain is `vikunja.example.com`
* The entrypoint you want to make vikunja available from is called `https`
@ -398,7 +398,7 @@ docker main folders:
* vikunja
* mariadb
Synology has it's own GUI for managing Docker containers... But it's easier via docker compose.
Synology has its own GUI for managing Docker containers... But it's easier via docker compose.
To do that, you can
@ -407,7 +407,7 @@ To do that, you can
* 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"**
2. Give it the name Vikunja and paste the adapted docker compose file
3. Deploy the Stack with the "Delpoy Stack" button:
3. Deploy the Stack with the "Deploy Stack" button:
![Portainer Stack deploy](/docs/synology-proxy-2.png)

View File

@ -10,6 +10,6 @@ menu:
There are two third-party Helm-Charts which can be used to host Vikunja with k8s:
* [Truecharts](https://truecharts.org/docs/charts/stable/vikunja/)
* [Truecharts](https://truecharts.org/charts/stable/vikunja/)
* [k8s at Home](https://github.com/k8s-at-home/charts)

View File

@ -43,3 +43,26 @@ scopes:
- email
- profile
```
## Google / Google Workspace
Vikunja Config:
```yaml
openid:
enabled: true
redirecturl: https://vikunja.mydomain.com/auth/openid/ <---- slash at the end is important
providers:
- name: Google
authurl: https://accounts.google.com
clientid: <google-oauth-client-id>
clientsecret: <google-oauth-client-secret>
```
Google config:
- Navigate to https://console.cloud.google.com/apis/credentials in the target project
- Create a new OAuth client ID
- 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.

View File

@ -136,6 +136,21 @@ Flags:
* `-p`, `--password`: The password of the new user. You will be asked to enter it if not provided through the flag.
* `-u`, `--username`: The username of the new user.
#### `user delete`
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).
With the flag the user is deleted **immediately**.
**USE WITH CAUTION.**
{{< highlight bash >}}
$ vikunja user delete <id> <flags>
{{< /highlight >}}
Flags:
* `-n`, `--now` If provided, deletes the user immediately instead of emailing them first.
#### `user list`
Shows a list of all users.

53
go.mod
View File

@ -21,56 +21,58 @@ require (
gitea.com/xorm/xorm-redis-cache v0.2.0
github.com/ThreeDotsLabs/watermill v1.1.1
github.com/adlio/trello v1.10.0
github.com/arran4/golang-ical v0.0.0-20220517104411-fd89fefb0182
github.com/arran4/golang-ical v0.0.0-20221122102835-109346913e54
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef
github.com/bbrks/go-blurhash v1.1.1
github.com/c2h5oh/datasize v0.0.0-20220606134207-859f65c6625b
github.com/coreos/go-oidc/v3 v3.4.0
github.com/coreos/go-oidc/v3 v3.5.0
github.com/cweill/gotests v1.6.0
github.com/d4l3k/messagediff v1.2.1
github.com/disintegration/imaging v1.6.2
github.com/dustinkirkland/golang-petname v0.0.0-20191129215211-8e5a1ed0cff0
github.com/gabriel-vasile/mimetype v1.4.1
github.com/getsentry/sentry-go v0.15.0
github.com/getsentry/sentry-go v0.17.0
github.com/go-redis/redis/v8 v8.11.5
github.com/go-sql-driver/mysql v1.6.0
github.com/go-sql-driver/mysql v1.7.0
github.com/go-testfixtures/testfixtures/v3 v3.8.1
github.com/golang-jwt/jwt/v4 v4.4.2
github.com/gocarina/gocsv v0.0.0-20221216233619-1fea7ae8d380
github.com/golang-jwt/jwt/v4 v4.4.3
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
github.com/google/uuid v1.3.0
github.com/iancoleman/strcase v0.2.0
github.com/imdario/mergo v0.3.13
github.com/jinzhu/copier v0.3.5
github.com/labstack/echo/v4 v4.9.1
github.com/labstack/echo-jwt/v4 v4.0.1
github.com/labstack/echo/v4 v4.10.0
github.com/labstack/gommon v0.4.0
github.com/lib/pq v1.10.7
github.com/magefile/mage v1.14.0
github.com/mattn/go-sqlite3 v1.14.16
github.com/olekukonko/tablewriter v0.0.5
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
github.com/pquerna/otp v1.3.0
github.com/pquerna/otp v1.4.0
github.com/prometheus/client_golang v1.14.0
github.com/robfig/cron/v3 v3.0.1
github.com/samedi/caldav-go v3.0.0+incompatible
github.com/spf13/afero v1.9.2
github.com/spf13/afero v1.9.3
github.com/spf13/cobra v1.6.1
github.com/spf13/viper v1.14.0
github.com/spf13/viper v1.15.0
github.com/stretchr/testify v1.8.1
github.com/swaggo/swag v1.8.7
github.com/swaggo/swag v1.8.9
github.com/tkuchiki/go-timezone v0.2.2
github.com/ulule/limiter/v3 v3.10.0
github.com/vectordotdev/go-datemath v0.1.1-0.20211214182920-0a4ac8742b93
github.com/wneessen/go-mail v0.3.4
github.com/yuin/goldmark v1.5.2
golang.org/x/crypto v0.1.0
golang.org/x/image v0.1.0
golang.org/x/oauth2 v0.1.0
github.com/wneessen/go-mail v0.3.8
github.com/yuin/goldmark v1.5.3
golang.org/x/crypto v0.5.0
golang.org/x/image v0.3.0
golang.org/x/oauth2 v0.3.0
golang.org/x/sync v0.1.0
golang.org/x/sys v0.2.0
golang.org/x/term v0.2.0
golang.org/x/sys v0.4.0
golang.org/x/term v0.4.0
gopkg.in/d4l3k/messagediff.v1 v1.2.1
gopkg.in/yaml.v3 v3.0.1
src.techknowlogick.com/xgo v1.5.1-0.20220906164532-735bfdfb90d9
src.techknowlogick.com/xgo v1.7.1-0.20230117190652-94aee174ab86
src.techknowlogick.com/xormigrate v1.5.0
xorm.io/builder v0.3.12
xorm.io/xorm v1.3.2
@ -92,6 +94,7 @@ require (
github.com/garyburd/redigo v1.6.0 // indirect
github.com/ghodss/yaml v1.0.0 // indirect
github.com/go-chi/chi v4.0.2+incompatible // 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/jsonreference v0.19.6 // indirect
github.com/go-openapi/spec v0.20.4 // indirect
@ -108,7 +111,7 @@ require (
github.com/json-iterator/go v1.1.12 // indirect
github.com/laurent22/ical-go v0.1.1-0.20181107184520-7e5d6ade8eef // indirect
github.com/lithammer/shortuuid/v3 v3.0.4 // indirect
github.com/magiconair/properties v1.8.6 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
@ -119,7 +122,7 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/oklog/ulid v1.3.1 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.0.5 // indirect
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.3.0 // indirect
@ -129,15 +132,15 @@ require (
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.4.1 // indirect
github.com/subosito/gotenv v1.4.2 // indirect
github.com/syndtr/goleveldb v1.0.0 // indirect
github.com/urfave/cli/v2 v2.3.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.1 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
golang.org/x/net v0.1.0 // indirect
golang.org/x/text v0.4.0 // indirect
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect
golang.org/x/net v0.5.0 // indirect
golang.org/x/text v0.6.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.1.12 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.28.1 // indirect

142
go.sum
View File

@ -43,6 +43,7 @@ cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6m
cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s=
cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU=
cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U=
cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY=
@ -99,8 +100,8 @@ github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/arran4/golang-ical v0.0.0-20220517104411-fd89fefb0182 h1:mUsKridvWp4dgfkO/QWtgGwuLtZYpjKgsm15JRRik3o=
github.com/arran4/golang-ical v0.0.0-20220517104411-fd89fefb0182/go.mod h1:BSTTrYHuM12oAL8jDdcmPdw02SBThKYWNFHQlvEG6b0=
github.com/arran4/golang-ical v0.0.0-20221122102835-109346913e54 h1:HfAA5Vxbo64UTckj+EW/hfBjvvcUcbcwWCASvypy8JU=
github.com/arran4/golang-ical v0.0.0-20221122102835-109346913e54/go.mod h1:BSTTrYHuM12oAL8jDdcmPdw02SBThKYWNFHQlvEG6b0=
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef h1:46PFijGLmAjMPwCCCo7Jf0W6f9slllCkkv7vyc1yOSg=
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
@ -148,6 +149,8 @@ github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:z
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
github.com/coreos/go-oidc/v3 v3.4.0 h1:xz7elHb/LDwm/ERpwHd+5nb7wFHL32rsr6bBOgaeu6g=
github.com/coreos/go-oidc/v3 v3.4.0/go.mod h1:eHUXhZtXPQLgEaDrOVTgwbgmz1xGOkJNye6h3zkD2Pw=
github.com/coreos/go-oidc/v3 v3.5.0 h1:VxKtbccHZxs8juq7RdJntSqtXFtde9YpNpGn0yqgEHw=
github.com/coreos/go-oidc/v3 v3.5.0/go.mod h1:ecXRtV4romGPeO6ieExAsUK9cb/3fp9hXNz1tlv8PIM=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
@ -198,18 +201,16 @@ github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVB
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/gabriel-vasile/mimetype v1.4.1 h1:TRWk7se+TOjCYgRth7+1/OYLNiRNIotknkFtf/dnN7Q=
github.com/gabriel-vasile/mimetype v1.4.1/go.mod h1:05Vi0w3Y9c/lNvJOdmIwvrrAhX3rYhfQQCaf9VJcv7M=
github.com/garyburd/redigo v1.6.0 h1:0VruCpn7yAIIu7pWVClQC8wxCJEcG3nyzpMSHKi1PQc=
github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
github.com/getsentry/sentry-go v0.14.0 h1:rlOBkuFZRKKdUnKO+0U3JclRDQKlRu5vVQtkWSQvC70=
github.com/getsentry/sentry-go v0.14.0/go.mod h1:RZPJKSw+adu8PBNygiri/A98FqVr2HtRckJk9XVxJ9I=
github.com/getsentry/sentry-go v0.15.0 h1:CP9bmA7pralrVUedYZsmIHWpq/pBtXTSew7xvVpfLaA=
github.com/getsentry/sentry-go v0.15.0/go.mod h1:RZPJKSw+adu8PBNygiri/A98FqVr2HtRckJk9XVxJ9I=
github.com/getsentry/sentry-go v0.16.0 h1:owk+S+5XcgJLlGR/3+3s6N4d+uKwqYvh/eS0AIMjPWo=
github.com/getsentry/sentry-go v0.16.0/go.mod h1:ZXCloQLj0pG7mja5NK6NPf2V4A88YJ4pNlc2mOHwh6Y=
github.com/getsentry/sentry-go v0.17.0 h1:UustVWnOoDFHBS7IJUB2QK/nB5pap748ZEp0swnQJak=
github.com/getsentry/sentry-go v0.17.0/go.mod h1:B82dxtBvxG0KaPD8/hfSV+VcHD+Lg/xUS4JuQn1P4cM=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-chi/chi v4.0.2+incompatible h1:maB6vn6FqCxrpz4FqWdh4+lwpyZIQS7YEAUcHlgXVRs=
@ -218,6 +219,8 @@ github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxI
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo=
github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
@ -242,11 +245,14 @@ github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-testfixtures/testfixtures/v3 v3.8.1 h1:uonwvepqRvSgddcrReZQhojTlWlmOlHkYAb9ZaOMWgU=
github.com/go-testfixtures/testfixtures/v3 v3.8.1/go.mod h1:Kdu7YeMC0KRXVHdaQ91Vmx3pcjoTF63h4f1qTJDdXLA=
github.com/gocarina/gocsv v0.0.0-20221216233619-1fea7ae8d380 h1:JJq8YZiS07gFIMYZxkbbiMrXIglG3k5JPPtdvckcnfQ=
github.com/gocarina/gocsv v0.0.0-20221216233619-1fea7ae8d380/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI=
github.com/goccy/go-json v0.8.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk=
github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
@ -258,8 +264,8 @@ github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs=
github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v4 v4.4.3 h1:Hxl6lhQFj4AnOX6MLrsCb/+7tCj7DxP7VA+2rDIq5AU=
github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
@ -501,9 +507,13 @@ github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/labstack/echo-jwt/v4 v4.0.0 h1:MFdURJRtBNWzADUdXYlj++71UZ5MmjUtce7nSsCH8NY=
github.com/labstack/echo-jwt/v4 v4.0.0/go.mod h1:DHSSaL6cTgczdPXjf8qrTHRbrau2flcddV7CPMs2U/Y=
github.com/labstack/echo-jwt/v4 v4.0.1 h1:rxFj0gUPv+1EEhbyfpv463FunuNvW+6MDRGYve7LUxM=
github.com/labstack/echo-jwt/v4 v4.0.1/go.mod h1:DHSSaL6cTgczdPXjf8qrTHRbrau2flcddV7CPMs2U/Y=
github.com/labstack/echo/v4 v4.1.16/go.mod h1:awO+5TzAjvL8XpibdsfXxPgHr+orhtXZJZIQCVjogKI=
github.com/labstack/echo/v4 v4.9.1 h1:GliPYSpzGKlyOhqIbG8nmHBo3i1saKWFOgh41AN3b+Y=
github.com/labstack/echo/v4 v4.9.1/go.mod h1:Pop5HLc+xoc4qhTZ1ip6C0RtP7Z+4VzRLWZZFKqbbjo=
github.com/labstack/echo/v4 v4.10.0 h1:5CiyngihEO4HXsz3vVsJn7f8xAlWwRr3aY6Ih280ZKA=
github.com/labstack/echo/v4 v4.10.0/go.mod h1:S/T/5fy/GigaXnHTkh0ZGe4LpkkQysvRjFMSUTkDRNQ=
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=
github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
@ -526,6 +536,8 @@ github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
@ -558,8 +570,6 @@ github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
@ -622,6 +632,8 @@ github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3v
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg=
github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas=
github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
@ -635,8 +647,8 @@ github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qR
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/pquerna/otp v1.3.0 h1:oJV/SkzR33anKXwQU3Of42rL4wbrffP4uvUf1SvS5Xs=
github.com/pquerna/otp v1.3.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg=
github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
@ -644,10 +656,6 @@ github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeD
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
github.com/prometheus/client_golang v1.13.0 h1:b71QUfeo5M8gq2+evJdTPfZhYMAU0uKPkyPJ7TPsloU=
github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ=
github.com/prometheus/client_golang v1.13.1 h1:3gMjIY2+/hzmqhtUC/aQNYldJA6DtH3CgQvwS+02K1c=
github.com/prometheus/client_golang v1.13.1/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ=
github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw=
github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
@ -655,7 +663,6 @@ github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
@ -709,13 +716,11 @@ github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw=
github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk=
github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v1.6.0 h1:42a0n6jwCot1pUmomAp4T7DeMD+20LFv4Q54pxLf2LI=
github.com/spf13/cobra v1.6.0/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
@ -723,10 +728,10 @@ github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.13.0 h1:BWSJ/M+f+3nmdz9bxB+bWX28kkALN2ok11D0rSo8EJU=
github.com/spf13/viper v1.13.0/go.mod h1:Icm2xNL3/8uyh/wFuB1jI7TiTNKp8632Nwegu+zgdYw=
github.com/spf13/viper v1.14.0 h1:Rg7d3Lo706X9tHsJMUjdiwMpHB7W8WnSVOssIY+JElU=
github.com/spf13/viper v1.14.0/go.mod h1:WT//axPky3FdvXHzGw33dNdXXXfFQqmEalje+egj8As=
github.com/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU=
github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA=
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
@ -742,14 +747,15 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs=
github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
github.com/swaggo/swag v1.8.7 h1:2K9ivTD3teEO+2fXV6zrZKDqk5IuU2aJtBDo8U7omWU=
github.com/swaggo/swag v1.8.7/go.mod h1:ezQVUUhly8dludpVk+/PuwJWvLLanB13ygV5Pr9enSk=
github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
github.com/swaggo/swag v1.8.9 h1:kHtaBe/Ob9AZzAANfcn5c6RyCke9gG9QpH0jky0I/sA=
github.com/swaggo/swag v1.8.9/go.mod h1:ezQVUUhly8dludpVk+/PuwJWvLLanB13ygV5Pr9enSk=
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
github.com/tkuchiki/go-timezone v0.2.2 h1:MdHR65KwgVTwWFQrota4SKzc4L5EfuH5SdZZGtk/P2Q=
@ -766,18 +772,17 @@ github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyC
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
github.com/valyala/fasttemplate v1.1.0/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
github.com/valyala/fasttemplate v1.2.0/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4=
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/vectordotdev/go-datemath v0.1.1-0.20211214182920-0a4ac8742b93 h1:bT0ZMfsMi2Xh8dopgxhFT+OJH88QITHpdppdkG1rXJQ=
github.com/vectordotdev/go-datemath v0.1.1-0.20211214182920-0a4ac8742b93/go.mod h1:PnwzbSst7KD3vpBzzlntZU5gjVa455Uqa5QPiKSYJzQ=
github.com/wneessen/go-mail v0.3.1 h1:9hSthi8T57gDHMI0fgl58O667OQJm9wZ1EHyuy3hclA=
github.com/wneessen/go-mail v0.3.1/go.mod h1:m25lkU2GYQnlVr6tdwK533/UXxo57V0kLOjaFYmub0E=
github.com/wneessen/go-mail v0.3.2 h1:nTjAF4Ek2+JG7qunyk6oImf5YKrAE5a7A3uIazYsdM0=
github.com/wneessen/go-mail v0.3.2/go.mod h1:m25lkU2GYQnlVr6tdwK533/UXxo57V0kLOjaFYmub0E=
github.com/wneessen/go-mail v0.3.3 h1:mhqM18uWiBFA3TdfO2IyFfQ6dEj4kGKW1KQJJNSKLME=
github.com/wneessen/go-mail v0.3.3/go.mod h1:m25lkU2GYQnlVr6tdwK533/UXxo57V0kLOjaFYmub0E=
github.com/wneessen/go-mail v0.3.4 h1:75G6lojt3CxwSq73csMduxF7DJ3hLF2s2KJXJVDOr0k=
github.com/wneessen/go-mail v0.3.4/go.mod h1:m25lkU2GYQnlVr6tdwK533/UXxo57V0kLOjaFYmub0E=
github.com/wneessen/go-mail v0.3.6 h1:hT8PMIBdcTkoiDwoUGJssPYOe1Gg1/cUcp2o9+ls63o=
github.com/wneessen/go-mail v0.3.6/go.mod h1:m25lkU2GYQnlVr6tdwK533/UXxo57V0kLOjaFYmub0E=
github.com/wneessen/go-mail v0.3.7 h1:loEAGLvsDZLSiE6c+keBfg0gpias/R3ocFU8Eoh3Pq4=
github.com/wneessen/go-mail v0.3.7/go.mod h1:m25lkU2GYQnlVr6tdwK533/UXxo57V0kLOjaFYmub0E=
github.com/wneessen/go-mail v0.3.8 h1:ja5D/o/RVwrtRIYFlrO7GmtcjDNeMakGQuwQRZYv0JM=
github.com/wneessen/go-mail v0.3.8/go.mod h1:m25lkU2GYQnlVr6tdwK533/UXxo57V0kLOjaFYmub0E=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@ -785,8 +790,8 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.5.2 h1:ALmeCk/px5FSm1MAcFBAsVKZjDuMVj8Tm7FFIlMJnqU=
github.com/yuin/goldmark v1.5.2/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.5.3 h1:3HUJmBFbQW9fhQOzMgseU134xfi6hU+mjWywx5Ty+/M=
github.com/yuin/goldmark v1.5.3/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
@ -833,8 +838,10 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8=
golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80=
golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -848,8 +855,10 @@ golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMk
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.1.0 h1:r8Oj8ZA2Xy12/b5KZYj3tuv7NG/fBz3TwQVvpJ9l8Rk=
golang.org/x/image v0.1.0/go.mod h1:iyPr49SD/G/TBxYVB/9RRtGUT5eNbo2u4NamWeQcD5c=
golang.org/x/image v0.2.0 h1:/DcQ0w3VHKCC5p0/P2B0JpAZ9Z++V2KOo2fyU89CXBQ=
golang.org/x/image v0.2.0/go.mod h1:la7oBXb9w3YFjBqaAwtynVioc1ZvOnNteUNrifGNmAI=
golang.org/x/image v0.3.0 h1:HTDXbdK9bjfSWkPzDJIw89W8CAtfFGduujWs33NLLsg=
golang.org/x/image v0.3.0/go.mod h1:fXd9211C/0VTlYuAcOhW8dY/RtEJqODXOWBDpmYBf+A=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@ -931,8 +940,11 @@ golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU=
golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -954,8 +966,8 @@ golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE=
golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
golang.org/x/oauth2 v0.1.0 h1:isLCZuhj4v+tYv7eskaN4v/TM+A1begWWgyVJDdl1+Y=
golang.org/x/oauth2 v0.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A=
golang.org/x/oauth2 v0.3.0 h1:6l90koy8/LaBLmLu8jpHeHexzMwEita0zFfYlggy2F8=
golang.org/x/oauth2 v0.3.0/go.mod h1:rQrIauxkUhJ6CuwEXwymO2/eh4xz2ZWF1nBkcxS+tGk=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -1064,17 +1076,17 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0 h1:z85xZCsEl7bi/KwbNADeBYoOP0++7W1ipu+aGnpwzRM=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI=
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
golang.org/x/term v0.4.0 h1:O7UWfv5+A2qiuulQk30kVinPoMtoIPeVaKLEgLpVkvg=
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -1084,15 +1096,17 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af h1:Yx9k8YCG3dvF87UAn2tu2HQLf2dt/eR1bXxpLMWeH+Y=
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -1510,6 +1524,10 @@ sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
src.techknowlogick.com/xgo v1.5.1-0.20220906164532-735bfdfb90d9 h1:7u6pOCURebyXlDy0OhWdnsEBf/KgVKnExA9/w/yCT0A=
src.techknowlogick.com/xgo v1.5.1-0.20220906164532-735bfdfb90d9/go.mod h1:31CE1YKtDOrKTk9PSnjTpe6YbO6W/0LTYZ1VskL09oU=
src.techknowlogick.com/xgo v1.6.1-0.20230110184414-1fd3d5b59de3 h1:y5MVUua3J82o91nQk1gBGrJA1FJn1X6sLXar4Ec08W8=
src.techknowlogick.com/xgo v1.6.1-0.20230110184414-1fd3d5b59de3/go.mod h1:31CE1YKtDOrKTk9PSnjTpe6YbO6W/0LTYZ1VskL09oU=
src.techknowlogick.com/xgo v1.7.1-0.20230117190652-94aee174ab86 h1:VybPMHRdCLbdCttI8fMXOaGpoJGSG9+W/5cfRgr1Xjc=
src.techknowlogick.com/xgo v1.7.1-0.20230117190652-94aee174ab86/go.mod h1:31CE1YKtDOrKTk9PSnjTpe6YbO6W/0LTYZ1VskL09oU=
src.techknowlogick.com/xormigrate v1.5.0 h1:6mWTh8d0sWjMTLUgJqiLe0e0Teu+1j+RgI7ErAeOEV0=
src.techknowlogick.com/xormigrate v1.5.0/go.mod h1:QOCnBeWralVncPn9eZlM4w/rglFK8o1vYpemzPenkBM=
xorm.io/builder v0.3.7/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=

View File

@ -546,6 +546,20 @@ func (Release) Darwin() error {
return runXgo("darwin-10.15/*")
}
func (Release) Xgo(target string) error {
parts := strings.Split(target, "/")
if len(parts) < 2 {
return fmt.Errorf("invalid target")
}
variant := ""
if len(parts) > 2 && parts[2] != "" {
variant = "-" + strings.ReplaceAll(parts[2], "v", "")
}
return runXgo(parts[0] + "/" + parts[1] + variant)
}
// Compresses the built binaries in dist/binaries/ to reduce their filesize
func (Release) Compress(ctx context.Context) error {
// $(foreach file,$(filter-out $(wildcard $(wildcard $(DIST)/binaries/$(EXECUTABLE)-*mips*)),$(wildcard $(DIST)/binaries/$(EXECUTABLE)-*)), upx -9 $(file);)
@ -689,7 +703,7 @@ func (Release) Packages() error {
binpath := "nfpm"
err = exec.Command(binpath).Run()
if err != nil && strings.Contains(err.Error(), "executable file not found") {
binpath = "/nfpm"
binpath = "/usr/bin/nfpm"
err = exec.Command(binpath).Run()
}
if err != nil && strings.Contains(err.Error(), "executable file not found") {
@ -698,7 +712,7 @@ func (Release) Packages() error {
os.Exit(1)
}
// Because nfpm does not support templating, we replace the values in the config file and restore it after running
// Because nfpm does not support templating, we replace the values in the config file and restore it after running
nfpmConfigPath := RootPath + "/nfpm.yaml"
nfpmconfig, err := os.ReadFile(nfpmConfigPath)
if err != nil {

View File

@ -22,7 +22,8 @@ import (
"strings"
"time"
"code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/user"
"code.vikunja.io/api/pkg/utils"
)
@ -58,10 +59,12 @@ type Todo struct {
RelatedToUID string
Color string
Start time.Time
End time.Time
DueDate time.Time
Duration time.Duration
Start time.Time
End time.Time
DueDate time.Time
Duration time.Duration
RepeatAfter int64
RepeatMode models.TaskRepeatMode
Created time.Time
Updated time.Time // last-mod
@ -226,6 +229,16 @@ CREATED:` + makeCalDavTimeFromTimeStamp(t.Created)
PRIORITY:` + strconv.Itoa(mapPriorityToCaldav(t.Priority))
}
if t.RepeatAfter > 0 || t.RepeatMode == models.TaskRepeatModeMonth {
if t.RepeatMode == models.TaskRepeatModeMonth {
caldavtodos += `
RRULE:FREQ=MONTHLY;BYMONTHDAY=` + t.DueDate.Format("02") // Day of the month
} else {
caldavtodos += `
RRULE:FREQ=SECONDLY;INTERVAL=` + strconv.FormatInt(t.RepeatAfter, 10)
}
}
caldavtodos += `
LAST-MODIFIED:` + makeCalDavTimeFromTimeStamp(t.Updated)
@ -240,7 +253,7 @@ END:VCALENDAR` // Need a line break
}
func makeCalDavTimeFromTimeStamp(ts time.Time) (caldavtime string) {
return ts.In(config.GetTimeZone()).Format(DateFormat)
return ts.In(time.UTC).Format(DateFormat) + "Z"
}
func calcAlarmDateFromReminder(eventStart, reminder time.Time) (alarmTime string) {

View File

@ -20,6 +20,8 @@ import (
"testing"
"time"
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/config"
"github.com/stretchr/testify/assert"
)
@ -84,25 +86,25 @@ X-APPLE-CALENDAR-COLOR:#affffeFF
X-OUTLOOK-COLOR:#affffeFF
X-FUNAMBOL-COLOR:#affffeFF
DESCRIPTION:Lorem Ipsum
DTSTAMP:20181201T011204
DTSTART:20181201T011204
DTEND:20181201T013024
DTSTAMP:20181201T011204Z
DTSTART:20181201T011204Z
DTEND:20181201T013024Z
END:VEVENT
BEGIN:VEVENT
UID:randommduidd
SUMMARY:Event #2
DESCRIPTION:
DTSTAMP:20181202T045844
DTSTART:20181202T045844
DTEND:20181202T081844
DTSTAMP:20181202T045844Z
DTSTART:20181202T045844Z
DTEND:20181202T081844Z
END:VEVENT
BEGIN:VEVENT
UID:20181202T0600242aaef4a81d770c1e775e26bc5abebc87f1d3d7bffaa83
SUMMARY:Event #3 with empty uid
DESCRIPTION:
DTSTAMP:20181202T050024
DTSTART:20181202T050024
DTEND:20181202T050320
DTSTAMP:20181202T050024Z
DTSTART:20181202T050024Z
DTEND:20181202T050320Z
END:VEVENT
END:VCALENDAR`,
},
@ -169,9 +171,9 @@ BEGIN:VEVENT
UID:randommduid
SUMMARY:Event #1
DESCRIPTION:Lorem Ipsum
DTSTAMP:20181201T011204
DTSTART:20181201T011204
DTEND:20181201T013024
DTSTAMP:20181201T011204Z
DTSTART:20181201T011204Z
DTEND:20181201T013024Z
BEGIN:VALARM
TRIGGER:-PT3M20S
ACTION:DISPLAY
@ -192,9 +194,9 @@ BEGIN:VEVENT
UID:randommduidd
SUMMARY:Event #2
DESCRIPTION:
DTSTAMP:20181202T045844
DTSTART:20181202T045844
DTEND:20181202T081844
DTSTAMP:20181202T045844Z
DTSTART:20181202T045844Z
DTEND:20181202T081844Z
BEGIN:VALARM
TRIGGER:-PT27H50M0S
ACTION:DISPLAY
@ -212,12 +214,12 @@ DESCRIPTION:Event #2
END:VALARM
END:VEVENT
BEGIN:VEVENT
UID:20181202T0500242aaef4a81d770c1e775e26bc5abebc87f1d3d7bffaa83
UID:20181202T050024Z2aaef4a81d770c1e775e26bc5abebc87f1d3d7bffaa83
SUMMARY:Event #3 with empty uid
DESCRIPTION:
DTSTAMP:20181202T050024
DTSTART:20181202T050024
DTEND:20181202T050320
DTSTAMP:20181202T050024Z
DTSTART:20181202T050024Z
DTEND:20181202T050320Z
BEGIN:VALARM
TRIGGER:-PT27H51M40S
ACTION:DISPLAY
@ -240,12 +242,12 @@ DESCRIPTION:Event #3 with empty uid
END:VALARM
END:VEVENT
BEGIN:VEVENT
UID:20181202T050024ae7548ce9556df85038abe90dc674d4741a61ce74d1cf
UID:20181202T050024Zae7548ce9556df85038abe90dc674d4741a61ce74d1cf
SUMMARY:Event #4 without any
DESCRIPTION:
DTSTAMP:20181202T050024
DTSTART:20181202T050024
DTEND:20181202T050320
DTSTAMP:20181202T050024Z
DTSTART:20181202T050024Z
DTEND:20181202T050320Z
END:VEVENT
END:VCALENDAR`,
},
@ -278,9 +280,9 @@ BEGIN:VEVENT
UID:randommduid
SUMMARY:Event #1
DESCRIPTION:Lorem Ipsum\nDolor sit amet
DTSTAMP:20181201T011204
DTSTART:20181201T011204
DTEND:20181201T013024
DTSTAMP:20181201T011204Z
DTSTART:20181201T011204Z
DTEND:20181201T013024Z
END:VEVENT
END:VCALENDAR`,
},
@ -333,13 +335,13 @@ X-OUTLOOK-COLOR:#ffffffFF
X-FUNAMBOL-COLOR:#ffffffFF
BEGIN:VTODO
UID:randommduid
DTSTAMP:20181201T011204
DTSTAMP:20181201T011204Z
SUMMARY:Todo #1
X-APPLE-CALENDAR-COLOR:#affffeFF
X-OUTLOOK-COLOR:#affffeFF
X-FUNAMBOL-COLOR:#affffeFF
DESCRIPTION:Lorem Ipsum\nDolor sit amet
LAST-MODIFIED:00010101T000000
LAST-MODIFIED:00010101T000000Z
END:VTODO
END:VCALENDAR`,
},
@ -368,12 +370,12 @@ X-WR-CALNAME:test
PRODID:-//RandomProdID which is not random//EN
BEGIN:VTODO
UID:randommduid
DTSTAMP:20181201T011204
DTSTAMP:20181201T011204Z
SUMMARY:Todo #1
DESCRIPTION:Lorem Ipsum
COMPLETED:20181201T013024
COMPLETED:20181201T013024Z
STATUS:COMPLETED
LAST-MODIFIED:00010101T000000
LAST-MODIFIED:00010101T000000Z
END:VTODO
END:VCALENDAR`,
},
@ -402,11 +404,82 @@ X-WR-CALNAME:test
PRODID:-//RandomProdID which is not random//EN
BEGIN:VTODO
UID:randommduid
DTSTAMP:20181201T011204
DTSTAMP:20181201T011204Z
SUMMARY:Todo #1
DESCRIPTION:Lorem Ipsum
PRIORITY:9
LAST-MODIFIED:00010101T000000
LAST-MODIFIED:00010101T000000Z
END:VTODO
END:VCALENDAR`,
},
{
name: "with repeating monthly",
args: args{
config: &Config{
Name: "test",
ProdID: "RandomProdID which is not random",
},
todos: []*Todo{
{
Summary: "Todo #1",
Description: "Lorem Ipsum",
UID: "randommduid",
Timestamp: time.Unix(1543626724, 0).In(config.GetTimeZone()),
RepeatMode: models.TaskRepeatModeMonth,
DueDate: time.Unix(1543626724, 0).In(config.GetTimeZone()),
},
},
},
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
DESCRIPTION:Lorem Ipsum
DUE:20181201T011204Z
RRULE:FREQ=MONTHLY;BYMONTHDAY=01
LAST-MODIFIED:00010101T000000Z
END:VTODO
END:VCALENDAR`,
},
{
name: "with repeat mode default",
args: args{
config: &Config{
Name: "test",
ProdID: "RandomProdID which is not random",
},
todos: []*Todo{
{
Summary: "Todo #1",
Description: "Lorem Ipsum",
UID: "randommduid",
Timestamp: time.Unix(1543626724, 0).In(config.GetTimeZone()),
RepeatMode: models.TaskRepeatModeDefault,
DueDate: time.Unix(1543626724, 0).In(config.GetTimeZone()),
RepeatAfter: 435,
},
},
},
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
DESCRIPTION:Lorem Ipsum
DUE:20181201T011204Z
RRULE:FREQ=SECONDLY;INTERVAL=435
LAST-MODIFIED:00010101T000000Z
END:VTODO
END:VCALENDAR`,
},

View File

@ -42,13 +42,15 @@ func GetCaldavTodosForTasks(list *models.ListWithTasksAndBuckets, listTasks []*m
Description: t.Description,
Completed: t.DoneAt,
// Organizer: &t.CreatedBy, // Disabled until we figure out how this works
Priority: t.Priority,
Start: t.StartDate,
End: t.EndDate,
Created: t.Created,
Updated: t.Updated,
DueDate: t.DueDate,
Duration: duration,
Priority: t.Priority,
Start: t.StartDate,
End: t.EndDate,
Created: t.Created,
Updated: t.Updated,
DueDate: t.DueDate,
Duration: duration,
RepeatAfter: t.RepeatAfter,
RepeatMode: t.RepeatMode,
})
}

View File

@ -225,7 +225,7 @@ var userUpdateCmd = &cobra.Command{
u.AvatarProvider = userFlagAvatar
}
_, err := user.UpdateUser(s, u)
_, err := user.UpdateUser(s, u, false)
if err != nil {
_ = s.Rollback()
log.Fatalf("Error updating the user: %s", err)
@ -299,7 +299,7 @@ var userChangeEnabledCmd = &cobra.Command{
u.Status = user.StatusActive
}
}
_, err := user.UpdateUser(s, u)
_, err := user.UpdateUser(s, u, false)
if err != nil {
_ = s.Rollback()
log.Fatalf("Could not enable the user")

View File

@ -128,10 +128,6 @@ const (
FilesBasePath Key = `files.basepath`
FilesMaxSize Key = `files.maxsize`
MigrationWunderlistEnable Key = `migration.wunderlist.enable`
MigrationWunderlistClientID Key = `migration.wunderlist.clientid`
MigrationWunderlistClientSecret Key = `migration.wunderlist.clientsecret`
MigrationWunderlistRedirectURL Key = `migration.wunderlist.redirecturl`
MigrationTodoistEnable Key = `migration.todoist.enable`
MigrationTodoistClientID Key = `migration.todoist.clientid`
MigrationTodoistClientSecret Key = `migration.todoist.clientsecret`
@ -369,7 +365,6 @@ func InitDefaultConfig() {
CorsOrigins.setDefault([]string{"*"})
CorsMaxAge.setDefault(0)
// Migration
MigrationWunderlistEnable.setDefault(false)
MigrationTodoistEnable.setDefault(false)
MigrationTrelloEnable.setDefault(false)
MigrationMicrosoftTodoEnable.setDefault(false)

View File

@ -18,6 +18,7 @@ package db
import (
"encoding/json"
"strings"
"code.vikunja.io/api/pkg/log"
@ -53,7 +54,35 @@ func Restore(table string, contents []map[string]interface{}) (err error) {
return err
}
meta, err := x.DBMetas()
if err != nil {
return err
}
var metaForCurrentTable *schemas.Table
for _, m := range meta {
if m.Name == table {
metaForCurrentTable = m
break
}
}
if metaForCurrentTable == nil {
log.Fatalf("Could not find table definition for table %s", table)
}
for _, content := range contents {
for colName, value := range content {
// Date fields might get restored as 0001-01-01 from null dates. This can have unintended side-effects like
// users being scheduled for deletion after a restore.
// To avoid this, we set these dates to nil so that they'll end up as null in the db.
col := metaForCurrentTable.GetColumn(colName)
strVal, is := value.(string)
if is && col.SQLType.IsTime() && (strVal == "" || strings.HasPrefix(strVal, "0001-")) {
content[colName] = nil
}
}
if _, err := x.Table(table).Insert(content); err != nil {
return err
}

View File

@ -337,6 +337,7 @@
bucket_id: 20
created: 2018-12-01 01:12:04
updated: 2018-12-01 01:12:04
due_date: 2018-10-30 22:25:24
- id: 37
title: 'task #37'
done: false

View File

@ -83,7 +83,7 @@ func CreateWithMime(f io.Reader, realname string, realsize uint64, a web.Auth, m
s := db.NewSession()
defer s.Close()
file, err = CreateWithMimeAndSession(s, f, realname, realsize, a, mime)
file, err = CreateWithMimeAndSession(s, f, realname, realsize, a, mime, true)
if err != nil {
_ = s.Rollback()
return
@ -91,14 +91,14 @@ func CreateWithMime(f io.Reader, realname string, realsize uint64, a web.Auth, m
return
}
func CreateWithMimeAndSession(s *xorm.Session, f io.Reader, realname string, realsize uint64, a web.Auth, mime string) (file *File, err error) {
func CreateWithMimeAndSession(s *xorm.Session, f io.Reader, realname string, realsize uint64, a web.Auth, mime string, checkFileSizeLimit bool) (file *File, err error) {
// Get and parse the configured file size
var maxSize datasize.ByteSize
err = maxSize.UnmarshalText([]byte(config.FilesMaxSize.GetString()))
if err != nil {
return nil, err
}
if realsize > maxSize.Bytes() {
if realsize > maxSize.Bytes() && checkFileSizeLimit {
return nil, ErrFileIsTooLarge{Size: realsize}
}

View File

@ -58,6 +58,10 @@ func getClient() (*mail.Client, error) {
mail.WithTimeout((config.MailerQueueTimeout.GetDuration() + 3) * time.Second), // 3s more for us to close before mail server timeout
}
if config.MailerForceSSL.GetBool() {
opts = append(opts, mail.WithSSL())
}
if config.MailerUsername.GetString() != "" && config.MailerPassword.GetString() != "" {
opts = append(opts, mail.WithSMTPAuth(authType))
}

View File

@ -85,7 +85,7 @@ func getMessage(opts *Opts) *mail.Msg {
m.Subject(opts.Subject)
for _, h := range opts.Headers {
m.SetHeader(h.Field, h.Content)
m.SetGenHeader(h.Field, h.Content)
}
for name, content := range opts.Embeds {

View File

@ -97,7 +97,7 @@ func ExportUserData(s *xorm.Session, u *user.User) (err error) {
return err
}
exportFile, err := files.CreateWithMimeAndSession(s, exported, tmpFilename, uint64(stat.Size()), u, "application/zip")
exportFile, err := files.CreateWithMimeAndSession(s, exported, tmpFilename, uint64(stat.Size()), u, "application/zip", false)
if err != nil {
return err
}

View File

@ -156,7 +156,7 @@ func GetListsByNamespaceID(s *xorm.Session, nID int64, doer *user.User) (lists [
Alias("l").
Join("LEFT", []string{"namespaces", "n"}, "l.namespace_id = n.id").
Where("l.is_archived = false").
Where("n.is_archived = false").
Where("n.is_archived = false OR n.is_archived IS NULL").
Where("namespace_id = ?", nID).
Find(&lists)
}

View File

@ -37,7 +37,9 @@ func getUndoneOverdueTasks(s *xorm.Session, now time.Time) (usersWithTasks map[i
var tasks []*Task
err = s.
Where("due_date is not null and due_date < ?", nextMinute.Add(time.Hour*14).Format(dbTimeFormat)).
Where("due_date is not null AND due_date < ? AND lists.is_archived = false AND namespaces.is_archived = false", nextMinute.Add(time.Hour*14).Format(dbTimeFormat)).
Join("LEFT", "lists", "lists.id = tasks.list_id").
Join("LEFT", "namespaces", "lists.namespace_id = namespaces.id").
And("done = false").
Find(&tasks)
if err != nil {
@ -138,9 +140,13 @@ func RegisterOverdueReminderCron() {
}
if len(ut.tasks) == 1 {
n = &UndoneTaskOverdueNotification{
User: ut.user,
Task: ut.tasks[0],
// We know there's only one entry in the map so this is actually O(1) and we can use it to get the
// first entry without knowing the key of it.
for _, t := range ut.tasks {
n = &UndoneTaskOverdueNotification{
User: ut.user,
Task: t,
}
}
}

View File

@ -202,7 +202,7 @@ func (rel *TaskRelation) Create(s *xorm.Session, a web.Auth) error {
// @Param relation body models.TaskRelation true "The relation object"
// @Param taskID path int true "Task ID"
// @Param relationKind path string true "The kind of the relation. See the TaskRelation type for more info."
// @Param otherTaskID path int true "The id of the other task."
// @Param otherTaskId path int true "The id of the other task."
// @Success 200 {object} models.Message "The task relation was successfully deleted."
// @Failure 400 {object} web.HTTPError "Invalid task relation object provided."
// @Failure 404 {object} web.HTTPError "The task relation was not found."

View File

@ -817,23 +817,28 @@ func checkBucketLimit(s *xorm.Session, t *Task, bucket *Bucket) (err error) {
}
// Contains all the task logic to figure out what bucket to use for this task.
func setTaskBucket(s *xorm.Session, task *Task, originalTask *Task, doCheckBucketLimit bool) (err error) {
func setTaskBucket(s *xorm.Session, task *Task, originalTask *Task, doCheckBucketLimit bool) (targetBucket *Bucket, err error) {
// Make sure we have a bucket
var bucket *Bucket
if task.Done && originalTask != nil && !originalTask.Done {
bucket, err := getDoneBucketForList(s, task.ListID)
if err != nil {
return err
return nil, err
}
if bucket != nil {
task.BucketID = bucket.ID
}
}
if task.BucketID == 0 && originalTask != nil && originalTask.BucketID != 0 {
task.BucketID = originalTask.BucketID
}
// Either no bucket was provided or the task was moved between lists
if task.BucketID == 0 || (originalTask != nil && task.ListID != 0 && originalTask.ListID != task.ListID) {
bucket, err = getDefaultBucket(s, task.ListID)
if err != nil {
return err
return
}
task.BucketID = bucket.ID
}
@ -841,7 +846,7 @@ func setTaskBucket(s *xorm.Session, task *Task, originalTask *Task, doCheckBucke
if bucket == nil {
bucket, err = getBucketByID(s, task.BucketID)
if err != nil {
return err
return
}
}
@ -855,7 +860,7 @@ func setTaskBucket(s *xorm.Session, task *Task, originalTask *Task, doCheckBucke
// Only check the bucket limit if the task is being moved between buckets, allow reordering the task within a bucket
if doCheckBucketLimit {
if err := checkBucketLimit(s, task, bucket); err != nil {
return err
return nil, err
}
}
@ -863,7 +868,7 @@ func setTaskBucket(s *xorm.Session, task *Task, originalTask *Task, doCheckBucke
task.Done = true
}
return nil
return bucket, nil
}
func calculateDefaultPosition(entityID int64, position float64) float64 {
@ -932,7 +937,7 @@ func createTask(s *xorm.Session, t *Task, a web.Auth, updateAssignees bool) (err
}
// Get the default bucket and move the task there
err = setTaskBucket(s, t, nil, true)
_, err = setTaskBucket(s, t, nil, true)
if err != nil {
return
}
@ -1023,13 +1028,21 @@ func (t *Task) Update(s *xorm.Session, a web.Auth) (err error) {
ot.Reminders[i] = r.Reminder
}
// When a repeating task is marked as done, we update all deadlines and reminders and set it as undone
updateDone(&ot, t)
if err := setTaskBucket(s, t, &ot, t.BucketID != ot.BucketID); err != nil {
targetBucket, err := setTaskBucket(s, t, &ot, t.BucketID != 0 && t.BucketID != ot.BucketID)
if err != nil {
return err
}
// If the task was moved into the done bucket and the task has a repeating cycle we should not update
// the bucket.
if targetBucket.IsDoneBucket && t.RepeatAfter > 0 {
t.Done = true // This will trigger the correct re-scheduling of the task (happening in updateDone later)
t.BucketID = ot.BucketID
}
// When a repeating task is marked as done, we update all deadlines and reminders and set it as undone
updateDone(&ot, t)
// Update the assignees
if err := ot.updateTaskAssignees(s, t.Assignees, a); err != nil {
return err
@ -1406,9 +1419,9 @@ func (t *Task) updateReminders(s *xorm.Session, reminders []time.Time) (err erro
}
// Resolve duplicates and sort them
reminderMap := make(map[string]time.Time, len(reminders))
reminderMap := make(map[int64]time.Time, len(reminders))
for _, reminder := range reminders {
reminderMap[reminder.UTC().String()] = reminder
reminderMap[reminder.UTC().Unix()] = reminder
}
// Loop through all reminders and add them

View File

@ -243,6 +243,33 @@ func TestTask_Update(t *testing.T) {
"bucket_id": 3,
}, false)
})
t.Run("moving a repeating task to the done bucket", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
s := db.NewSession()
defer s.Close()
task := &Task{
ID: 28,
Title: "test updated",
ListID: 1,
BucketID: 3, // Bucket 3 is the done bucket
RepeatAfter: 3600,
}
err := task.Update(s, u)
assert.NoError(t, err)
err = s.Commit()
assert.NoError(t, err)
assert.False(t, task.Done)
assert.Equal(t, int64(1), task.BucketID) // Bucket should not be updated
db.AssertExists(t, "tasks", map[string]interface{}{
"id": 28,
"done": false,
"title": "test updated",
"list_id": 1,
"bucket_id": 1,
}, false)
})
t.Run("default bucket when moving a task between lists", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
s := db.NewSession()
@ -308,8 +335,9 @@ func TestTask_Update(t *testing.T) {
defer s.Close()
task := &Task{
ID: 28,
Done: true,
ID: 28,
Done: true,
RepeatAfter: 3600,
}
err := task.Update(s, u)
assert.NoError(t, err)

View File

@ -266,7 +266,7 @@ func getOrCreateUser(s *xorm.Session, cl *claims, issuer, subject string) (u *us
Name: u.Name,
Issuer: issuer,
Subject: subject,
})
}, false)
if err != nil {
return nil, err
}

View File

@ -47,7 +47,7 @@ func unsplashImage(url string, c echo.Context) error {
// @Description Get an unsplash image. **Returns json on error.**
// @tags list
// @Produce octet-stream
// @Param thumb path int true "Unsplash Image ID"
// @Param image path int true "Unsplash Image ID"
// @Security JWTKeyAuth
// @Success 200 {} string "The image"
// @Failure 404 {object} models.Message "The image does not exist."
@ -67,7 +67,7 @@ func ProxyUnsplashImage(c echo.Context) error {
// @Description Get an unsplash thumbnail image. The thumbnail is cropped to a max width of 200px. **Returns json on error.**
// @tags list
// @Produce octet-stream
// @Param thumb path int true "Unsplash Image ID"
// @Param image path int true "Unsplash Image ID"
// @Security JWTKeyAuth
// @Success 200 {} string "The thumbnail"
// @Failure 404 {object} models.Message "The image does not exist."

View File

@ -22,6 +22,7 @@ import (
"bytes"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"os"
@ -144,11 +145,16 @@ func Restore(filename string) error {
// FIXME: There has to be a general way to do this but this works for now.
if table == "notifications" {
for i := range content {
decoded, err := base64.StdEncoding.DecodeString(content[i]["notification"].(string))
if err != nil {
var decoded []byte
decoded, err = base64.StdEncoding.DecodeString(content[i]["notification"].(string))
if err != nil && !errors.Is(err, base64.CorruptInputError(0)) {
return fmt.Errorf("could not decode notification %s: %w", content[i]["notification"], err)
}
if err != nil && errors.Is(err, base64.CorruptInputError(0)) {
decoded = []byte(content[i]["notification"].(string))
}
content[i]["notification"] = string(decoded)
}
}

View File

@ -56,12 +56,22 @@ func DownloadFileWithHeaders(url string, headers http.Header) (buf *bytes.Buffer
// DoPost makes a form encoded post request
func DoPost(url string, form url.Values) (resp *http.Response, err error) {
return DoPostWithHeaders(url, form, map[string]string{})
}
// DoPostWithHeaders does an api request and allows to pass in arbitrary headers
func DoPostWithHeaders(url string, form url.Values, headers map[string]string) (resp *http.Response, err error) {
req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, url, strings.NewReader(form.Encode()))
if err != nil {
return
}
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
for key, value := range headers {
req.Header.Add(key, value)
}
hc := http.Client{}
return hc.Do(req)
}

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -27,10 +27,11 @@ import (
"time"
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/modules/migration"
"code.vikunja.io/api/pkg/user"
"github.com/gocarina/gocsv"
)
const timeISO = "2006-01-02T15:04:05-0700"
@ -39,23 +40,39 @@ type Migrator struct {
}
type tickTickTask struct {
FolderName string
ListName string
Title string
Tags []string
Content string
IsChecklist bool
StartDate time.Time
DueDate time.Time
Reminder time.Duration
Repeat string
Priority int
Status string
CreatedTime time.Time
CompletedTime time.Time
Order float64
TaskID int64
ParentID int64
FolderName string `csv:"Folder Name"`
ListName string `csv:"List Name"`
Title string `csv:"Title"`
TagsList string `csv:"Tags"`
Tags []string `csv:"-"`
Content string `csv:"Content"`
IsChecklistString string `csv:"Is Check list"`
IsChecklist bool `csv:"-"`
StartDate tickTickTime `csv:"Start Date"`
DueDate tickTickTime `csv:"Due Date"`
ReminderDuration string `csv:"Reminder"`
Reminder time.Duration `csv:"-"`
Repeat string `csv:"Repeat"`
Priority int `csv:"Priority"`
Status string `csv:"Status"`
CreatedTime tickTickTime `csv:"Created Time"`
CompletedTime tickTickTime `csv:"Completed Time"`
Order float64 `csv:"Order"`
TaskID int64 `csv:"taskId"`
ParentID int64 `csv:"parentId"`
}
type tickTickTime struct {
time.Time
}
func (date *tickTickTime) UnmarshalCSV(csv string) (err error) {
date.Time = time.Time{}
if csv == "" {
return nil
}
date.Time, err = time.Parse(timeISO, csv)
return err
}
// Copied from https://stackoverflow.com/a/57617885
@ -119,19 +136,22 @@ func convertTickTickToVikunja(tasks []*tickTickTask) (result []*models.Namespace
ID: t.TaskID,
Title: t.Title,
Description: t.Content,
StartDate: t.StartDate,
EndDate: t.DueDate,
DueDate: t.DueDate,
Reminders: []time.Time{
t.DueDate.Add(t.Reminder * -1),
},
Done: t.Status == "1",
DoneAt: t.CompletedTime,
Position: t.Order,
Labels: labels,
StartDate: t.StartDate.Time,
EndDate: t.DueDate.Time,
DueDate: t.DueDate.Time,
Done: t.Status == "1",
DoneAt: t.CompletedTime.Time,
Position: t.Order,
Labels: labels,
},
}
if !t.DueDate.IsZero() && t.Reminder > 0 {
task.Task.Reminders = []time.Time{
t.DueDate.Add(t.Reminder * -1),
}
}
if t.ParentID != 0 {
task.RelatedTasks = map[models.RelationKind][]*models.Task{
models.RelationKindParenttask: {{ID: t.ParentID}},
@ -165,6 +185,22 @@ func (m *Migrator) Name() string {
return "ticktick"
}
func newLineSkipDecoder(r io.Reader, linesToSkip int) gocsv.SimpleDecoder {
reader := csv.NewReader(r)
// reader.FieldsPerRecord = -1
for i := 0; i < linesToSkip; i++ {
_, err := reader.Read()
if err != nil {
if errors.Is(err, io.EOF) {
break
}
log.Debugf("[TickTick Migration] CSV parse error: %s", err)
}
}
reader.FieldsPerRecord = 0
return gocsv.NewSimpleDecoderFromCSVReader(reader)
}
// Migrate takes a ticktick export, parses it and imports everything in it into Vikunja.
// @Summary Import all lists, tasks etc. from a TickTick backup export
// @Description Imports all projects, tasks, notes, reminders, subtasks and files from a TickTick backup export into Vikunja.
@ -178,85 +214,26 @@ func (m *Migrator) Name() string {
// @Router /migration/ticktick/migrate [post]
func (m *Migrator) Migrate(user *user.User, file io.ReaderAt, size int64) error {
fr := io.NewSectionReader(file, 0, size)
r := csv.NewReader(fr)
//r := csv.NewReader(fr)
allTasks := []*tickTickTask{}
line := 0
for {
decode := newLineSkipDecoder(fr, 3)
err := gocsv.UnmarshalDecoder(decode, &allTasks)
if err != nil {
return err
}
record, err := r.Read()
if err != nil {
if errors.Is(err, io.EOF) {
break
}
log.Debugf("[TickTick Migration] CSV parse error: %s", err)
for _, task := range allTasks {
if task.IsChecklistString == "Y" {
task.IsChecklist = true
}
line++
if line <= 4 {
continue
reminder := parseDuration(task.ReminderDuration)
if reminder > 0 {
task.Reminder = reminder
}
priority, err := strconv.Atoi(record[10])
if err != nil {
return err
}
order, err := strconv.ParseFloat(record[14], 64)
if err != nil {
return err
}
taskID, err := strconv.ParseInt(record[21], 10, 64)
if err != nil {
return err
}
parentID, err := strconv.ParseInt(record[21], 10, 64)
if err != nil {
return err
}
reminder := parseDuration(record[8])
t := &tickTickTask{
ListName: record[1],
Title: record[2],
Tags: strings.Split(record[3], ", "),
Content: record[4],
IsChecklist: record[5] == "Y",
Reminder: reminder,
Repeat: record[9],
Priority: priority,
Status: record[11],
Order: order,
TaskID: taskID,
ParentID: parentID,
}
if record[6] != "" {
t.StartDate, err = time.Parse(timeISO, record[6])
if err != nil {
return err
}
}
if record[7] != "" {
t.DueDate, err = time.Parse(timeISO, record[7])
if err != nil {
return err
}
}
if record[12] != "" {
t.StartDate, err = time.Parse(timeISO, record[12])
if err != nil {
return err
}
}
if record[13] != "" {
t.CompletedTime, err = time.Parse(timeISO, record[13])
if err != nil {
return err
}
}
allTasks = append(allTasks, t)
task.Tags = strings.Split(task.TagsList, ", ")
}
vikunjaTasks := convertTickTickToVikunja(allTasks)

View File

@ -26,12 +26,15 @@ import (
)
func TestConvertTicktickTasksToVikunja(t *testing.T) {
time1, err := time.Parse(time.RFC3339Nano, "2022-11-18T03:00:00.4770000Z")
t1, err := time.Parse(time.RFC3339Nano, "2022-11-18T03:00:00.4770000Z")
require.NoError(t, err)
time2, err := time.Parse(time.RFC3339Nano, "2022-12-18T03:00:00.4770000Z")
time1 := tickTickTime{Time: t1}
t2, err := time.Parse(time.RFC3339Nano, "2022-12-18T03:00:00.4770000Z")
require.NoError(t, err)
time3, err := time.Parse(time.RFC3339Nano, "2022-12-10T03:00:00.4770000Z")
time2 := tickTickTime{Time: t2}
t3, err := time.Parse(time.RFC3339Nano, "2022-12-10T03:00:00.4770000Z")
require.NoError(t, err)
time3 := tickTickTime{Time: t3}
duration, err := time.ParseDuration("24h")
require.NoError(t, err)
@ -91,9 +94,9 @@ func TestConvertTicktickTasksToVikunja(t *testing.T) {
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[0].Title, tickTickTasks[0].Title)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[0].Description, tickTickTasks[0].Content)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[0].StartDate, tickTickTasks[0].StartDate)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[0].EndDate, tickTickTasks[0].DueDate)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[0].DueDate, tickTickTasks[0].DueDate)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[0].StartDate, tickTickTasks[0].StartDate.Time)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[0].EndDate, tickTickTasks[0].DueDate.Time)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[0].DueDate, tickTickTasks[0].DueDate.Time)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[0].Labels, []*models.Label{
{Title: "label1"},
{Title: "label2"},
@ -105,7 +108,7 @@ func TestConvertTicktickTasksToVikunja(t *testing.T) {
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[1].Title, tickTickTasks[1].Title)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[1].Position, tickTickTasks[1].Order)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[1].Done, true)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[1].DoneAt, tickTickTasks[1].CompletedTime)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[1].DoneAt, tickTickTasks[1].CompletedTime.Time)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[1].RelatedTasks, models.RelatedTaskMap{
models.RelationKindParenttask: []*models.Task{
{
@ -116,9 +119,9 @@ func TestConvertTicktickTasksToVikunja(t *testing.T) {
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[2].Title, tickTickTasks[2].Title)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[2].Description, tickTickTasks[2].Content)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[2].StartDate, tickTickTasks[2].StartDate)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[2].EndDate, tickTickTasks[2].DueDate)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[2].DueDate, tickTickTasks[2].DueDate)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[2].StartDate, tickTickTasks[2].StartDate.Time)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[2].EndDate, tickTickTasks[2].DueDate.Time)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[2].DueDate, tickTickTasks[2].DueDate.Time)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[2].Labels, []*models.Label{
{Title: "label1"},
{Title: "label2"},

View File

@ -20,6 +20,7 @@ import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/url"
"sort"
"strconv"
@ -45,28 +46,25 @@ type apiTokenResponse struct {
}
type label struct {
ID int64 `json:"id"`
ID string `json:"id"`
Name string `json:"name"`
Color int64 `json:"color"`
Color string `json:"color"`
ItemOrder int64 `json:"item_order"`
IsDeleted int64 `json:"is_deleted"`
IsFavorite int64 `json:"is_favorite"`
IsDeleted bool `json:"is_deleted"`
IsFavorite bool `json:"is_favorite"`
}
type project struct {
ID int64 `json:"id"`
LegacyID int64 `json:"legacy_id"`
Name string `json:"name"`
Color int64 `json:"color"`
ParentID int64 `json:"parent_id"`
ChildOrder int64 `json:"child_order"`
Collapsed int64 `json:"collapsed"`
Shared bool `json:"shared"`
LegacyParentID int64 `json:"legacy_parent_id"`
SyncID int64 `json:"sync_id"`
IsDeleted int64 `json:"is_deleted"`
IsArchived int64 `json:"is_archived"`
IsFavorite int64 `json:"is_favorite"`
ID string `json:"id"`
Name string `json:"name"`
Color string `json:"color"`
ParentID string `json:"parent_id"`
ChildOrder int64 `json:"child_order"`
Collapsed bool `json:"collapsed"`
Shared bool `json:"shared"`
IsDeleted bool `json:"is_deleted"`
IsArchived bool `json:"is_archived"`
IsFavorite bool `json:"is_favorite"`
}
type dueDate struct {
@ -78,31 +76,25 @@ type dueDate struct {
}
type item struct {
ID int64 `json:"id"`
LegacyID int64 `json:"legacy_id"`
UserID int64 `json:"user_id"`
ProjectID int64 `json:"project_id"`
LegacyProjectID int64 `json:"legacy_project_id"`
Content string `json:"content"`
Priority int64 `json:"priority"`
Due *dueDate `json:"due"`
ParentID int64 `json:"parent_id"`
LegacyParentID int64 `json:"legacy_parent_id"`
ChildOrder int64 `json:"child_order"`
SectionID int64 `json:"section_id"`
DayOrder int64 `json:"day_order"`
Collapsed int64 `json:"collapsed"`
Children interface{} `json:"children"`
Labels []int64 `json:"labels"`
AddedByUID int64 `json:"added_by_uid"`
AssignedByUID int64 `json:"assigned_by_uid"`
ResponsibleUID int64 `json:"responsible_uid"`
Checked int64 `json:"checked"`
InHistory int64 `json:"in_history"`
IsDeleted int64 `json:"is_deleted"`
DateAdded time.Time `json:"date_added"`
HasMoreNotes bool `json:"has_more_notes"`
DateCompleted time.Time `json:"date_completed"`
ID string `json:"id"`
UserID string `json:"user_id"`
ProjectID string `json:"project_id"`
Content string `json:"content"`
Priority int64 `json:"priority"`
Due *dueDate `json:"due"`
ParentID string `json:"parent_id"`
ChildOrder int64 `json:"child_order"`
SectionID string `json:"section_id"`
Children interface{} `json:"children"`
Labels []string `json:"labels"`
AddedByUID string `json:"added_by_uid"`
AssignedByUID string `json:"assigned_by_uid"`
ResponsibleUID string `json:"responsible_uid"`
Checked bool `json:"checked"`
IsDeleted bool `json:"is_deleted"`
DateAdded time.Time `json:"added_at"`
HasMoreNotes bool `json:"has_more_notes"`
DateCompleted time.Time `json:"completed_at"`
}
type itemWrapper struct {
@ -110,12 +102,11 @@ type itemWrapper struct {
}
type doneItem struct {
CompletedDate time.Time `json:"completed_date"`
CompletedDate time.Time `json:"completed_at"`
Content string `json:"content"`
ID int64 `json:"id"`
ProjectID int64 `json:"project_id"`
TaskID int64 `json:"task_id"`
UserID int `json:"user_id"`
ID string `json:"id"`
ProjectID string `json:"project_id"`
TaskID string `json:"task_id"`
}
type doneItemSync struct {
@ -132,18 +123,14 @@ type fileAttachment struct {
}
type note struct {
ID int64 `json:"id"`
LegacyID int64 `json:"legacy_id"`
PostedUID int64 `json:"posted_uid"`
ProjectID int64 `json:"project_id"`
LegacyProjectID int64 `json:"legacy_project_id"`
ItemID int64 `json:"item_id"`
LegacyItemID int64 `json:"legacy_item_id"`
Content string `json:"content"`
FileAttachment *fileAttachment `json:"file_attachment"`
UidsToNotify []int64 `json:"uids_to_notify"`
IsDeleted int64 `json:"is_deleted"`
Posted time.Time `json:"posted"`
ID string `json:"id"`
PostedUID int64 `json:"posted_uid"`
ProjectID string `json:"project_id"`
ItemID string `json:"item_id"`
Content string `json:"content"`
FileAttachment *fileAttachment `json:"file_attachment"`
IsDeleted bool `json:"is_deleted"`
Posted time.Time `json:"posted_at"`
}
type projectNote struct {
@ -153,15 +140,13 @@ type projectNote struct {
IsDeleted int64 `json:"is_deleted"`
Posted time.Time `json:"posted"`
PostedUID int64 `json:"posted_uid"`
ProjectID int64 `json:"project_id"`
ProjectID string `json:"project_id"`
UidsToNotify []int64 `json:"uids_to_notify"`
}
type reminder struct {
ID int64 `json:"id"`
NotifyUID int64 `json:"notify_uid"`
ItemID int64 `json:"item_id"`
Service string `json:"service"`
ID string `json:"id"`
ItemID string `json:"item_id"`
Type string `json:"type"`
Due *dueDate `json:"due"`
MmOffset int64 `json:"mm_offset"`
@ -169,11 +154,11 @@ type reminder struct {
}
type section struct {
ID int64 `json:"id"`
DateAdded time.Time `json:"date_added"`
ID string `json:"id"`
DateAdded time.Time `json:"added_at"`
IsDeleted bool `json:"is_deleted"`
Name string `json:"name"`
ProjectID int64 `json:"project_id"`
ProjectID string `json:"project_id"`
SectionOrder int64 `json:"section_order"`
}
@ -187,32 +172,32 @@ type sync struct {
Sections []*section `json:"sections"`
}
var todoistColors = map[int64]string{}
var todoistColors = map[string]string{}
func init() {
todoistColors = make(map[int64]string, 19)
// The todoists colors are static, taken from https://developer.todoist.com/sync/v8/#colors
todoistColors = map[int64]string{
30: "b8256f",
31: "db4035",
32: "ff9933",
33: "fad000",
34: "afb83b",
35: "7ecc49",
36: "299438",
37: "6accbc",
38: "158fad",
39: "14aaf5",
40: "96c3eb",
41: "4073ff",
42: "884dff",
43: "af38eb",
44: "eb96eb",
45: "e05194",
46: "ff8d85",
47: "808080",
48: "b8b8b8",
49: "ccac93",
todoistColors = make(map[string]string, 19)
// The todoists colors are static, taken from https://developer.todoist.com/guides/#colors
todoistColors = map[string]string{
"berry_red": "b8256f",
"red": "db4035",
"orange": "ff9933",
"yellow": "fad000",
"olive_green": "afb83b",
"lime_green": "7ecc49",
"green": "299438",
"mint_green": "6accbc",
"teal": "158fad",
"sky_blue": "14aaf5",
"light_blue": "96c3eb",
"blue": "4073ff",
"grape": "884dff",
"violet": "af38eb",
"lavender": "eb96eb",
"magenta": "e05194",
"salmon": "ff8d85",
"charcoal": "808080",
"grey": "b8b8b8",
"taupe": "ccac93",
}
}
@ -266,7 +251,7 @@ func parseDate(dateString string) (date time.Time, err error) {
return date, err
}
func convertTodoistToVikunja(sync *sync, doneItems map[int64]*doneItem) (fullVikunjaHierachie []*models.NamespaceWithListsAndTasks, err error) {
func convertTodoistToVikunja(sync *sync, doneItems map[string]*doneItem) (fullVikunjaHierachie []*models.NamespaceWithListsAndTasks, err error) {
newNamespace := &models.NamespaceWithListsAndTasks{
Namespace: models.Namespace{
@ -275,20 +260,22 @@ func convertTodoistToVikunja(sync *sync, doneItems map[int64]*doneItem) (fullVik
}
// A map for all vikunja lists with the project id they're coming from as key
lists := make(map[int64]*models.ListWithTasksAndBuckets, len(sync.Projects))
lists := make(map[string]*models.ListWithTasksAndBuckets, len(sync.Projects))
// A map for all vikunja tasks with the todoist task id as key to find them easily and add more data
tasks := make(map[int64]*models.TaskWithComments, len(sync.Items))
tasks := make(map[string]*models.TaskWithComments, len(sync.Items))
// A map for all vikunja labels with the todoist id as key to find them easier
labels := make(map[int64]*models.Label, len(sync.Labels))
labels := make(map[string]*models.Label, len(sync.Labels))
sections := make(map[string]int64)
for _, p := range sync.Projects {
list := &models.ListWithTasksAndBuckets{
List: models.List{
Title: p.Name,
HexColor: todoistColors[p.Color],
IsArchived: p.IsArchived == 1,
IsArchived: p.IsArchived,
},
}
@ -301,32 +288,40 @@ func convertTodoistToVikunja(sync *sync, doneItems map[int64]*doneItem) (fullVik
return sync.Sections[i].SectionOrder < sync.Sections[j].SectionOrder
})
var fabricatedSectionID int64 = 1
for _, section := range sync.Sections {
if section.IsDeleted || section.ProjectID == 0 {
if section.IsDeleted || section.ProjectID == "" {
continue
}
lists[section.ProjectID].Buckets = append(lists[section.ProjectID].Buckets, &models.Bucket{
ID: section.ID,
ID: fabricatedSectionID,
Title: section.Name,
Created: section.DateAdded,
})
sections[section.ID] = fabricatedSectionID
}
for _, label := range sync.Labels {
labels[label.ID] = &models.Label{
labels[label.Name] = &models.Label{
Title: label.Name,
HexColor: todoistColors[label.Color],
}
}
for _, i := range sync.Items {
if i == nil {
// This should never happen
continue
}
task := &models.TaskWithComments{
Task: models.Task{
Title: i.Content,
Created: i.DateAdded.In(config.GetTimeZone()),
Done: i.Checked == 1,
BucketID: i.SectionID,
Done: i.Checked,
BucketID: sections[i.SectionID],
},
}
@ -357,29 +352,31 @@ func convertTodoistToVikunja(sync *sync, doneItems map[int64]*doneItem) (fullVik
}
// Put all labels together from earlier
for _, lID := range i.Labels {
task.Labels = append(task.Labels, labels[lID])
for _, lName := range i.Labels {
task.Labels = append(task.Labels, labels[lName])
}
tasks[i.ID] = task
if _, exists := lists[i.ProjectID]; !exists {
log.Debugf("[Todoist Migration] Tried to put item %d in project %d but the project does not exist", i.ID, i.ProjectID)
log.Debugf("[Todoist Migration] Tried to put item %s in project %s but the project does not exist", i.ID, i.ProjectID)
continue
}
lists[i.ProjectID].Tasks = append(lists[i.ProjectID].Tasks, task)
fabricatedSectionID++
}
// If the parenId of a task is not 0, create a task relation
// We're looping again here to make sure we have seem all tasks before and have them in our map
for _, i := range sync.Items {
if i.ParentID == 0 {
if i.ParentID == "" {
continue
}
if _, exists := tasks[i.ParentID]; !exists {
log.Debugf("[Todoist Migration] Could not find task %d in tasks map while trying to get resolve subtasks for task %d", i.ParentID, i.ID)
log.Debugf("[Todoist Migration] Could not find task %s in tasks map while trying to get resolve subtasks for task %s", i.ParentID, i.ID)
continue
}
@ -407,7 +404,7 @@ func convertTodoistToVikunja(sync *sync, doneItems map[int64]*doneItem) (fullVik
// FIXME: Should be comments
for _, n := range sync.Notes {
if _, exists := tasks[n.ItemID]; !exists {
log.Debugf("[Todoist Migration] Could not find task %d for note %d", n.ItemID, n.ID)
log.Debugf("[Todoist Migration] Could not find task %s for note %s", n.ItemID, n.ID)
continue
}
@ -460,7 +457,7 @@ func convertTodoistToVikunja(sync *sync, doneItems map[int64]*doneItem) (fullVik
}
if _, exists := tasks[r.ItemID]; !exists {
log.Debugf("Could not find task %d for reminder %d while trying to resolve reminders", r.ItemID, r.ID)
log.Debugf("Could not find task %s for reminder %s while trying to resolve reminders", r.ItemID, r.ID)
continue
}
@ -533,11 +530,14 @@ func (m *Migration) Migrate(u *user.User) (err error) {
// Get everything with the sync api
form := url.Values{
"token": []string{token},
"sync_token": []string{"*"},
"resource_types": []string{"[\"all\"]"},
}
resp, err := migration.DoPost("https://api.todoist.com/sync/v8/sync", form)
bearerHeader := map[string]string{
"Authorization": "Bearer " + token,
}
resp, err := migration.DoPostWithHeaders("https://api.todoist.com/sync/v9/sync", form, bearerHeader)
if err != nil {
return
}
@ -553,10 +553,10 @@ func (m *Migration) Migrate(u *user.User) (err error) {
// Get all done tasks and projects
offset := 0
doneItems := make(map[int64]*doneItem)
doneItems := make(map[string]*doneItem)
for {
resp, err = migration.DoPost("https://api.todoist.com/sync/v8/completed/get_all?limit=200&offset="+strconv.Itoa(offset), form)
resp, err = migration.DoPostWithHeaders("https://api.todoist.com/sync/v9/completed/get_all?limit=200&offset="+strconv.Itoa(offset), form, bearerHeader)
if err != nil {
return
}
@ -580,21 +580,26 @@ func (m *Migration) Migrate(u *user.User) (err error) {
doneItems[i.TaskID] = i
// need to get done item data
resp, err = migration.DoPost("https://api.todoist.com/sync/v8/items/get", url.Values{
"token": []string{token},
"item_id": []string{strconv.FormatInt(i.TaskID, 10)},
})
resp, err = migration.DoPostWithHeaders("https://api.todoist.com/sync/v9/items/get", url.Values{
"item_id": []string{i.TaskID},
}, bearerHeader)
if err != nil {
return
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusNotFound {
// Done items of deleted projects may show up here but since the project is already deleted
// we can't show them individually and the api returns a 404.
continue
}
doneI := &itemWrapper{}
err = json.NewDecoder(resp.Body).Decode(doneI)
if err != nil {
return
}
log.Debugf("[Todoist Migration] Retrieved full task data for done task %d", i.TaskID)
log.Debugf("[Todoist Migration] Retrieved full task data for done task %s", i.ID)
syncResponse.Items = append(syncResponse.Items, doneI.Item)
}
@ -609,7 +614,7 @@ func (m *Migration) Migrate(u *user.User) (err error) {
log.Debugf("[Todoist Migration] Getting archived projects for user %d", u.ID)
// Get all archived projects
resp, err = migration.DoPost("https://api.todoist.com/sync/v8/projects/get_archived", form)
resp, err = migration.DoPostWithHeaders("https://api.todoist.com/sync/v9/projects/get_archived", form, bearerHeader)
if err != nil {
return
}
@ -625,10 +630,9 @@ func (m *Migration) Migrate(u *user.User) (err error) {
log.Debugf("[Todoist Migration] Got %d archived projects for user %d", len(archivedProjects), u.ID)
log.Debugf("[Todoist Migration] Getting data for archived projects for user %d", u.ID)
// Project data is not included in the regular sync for archived projects so we need to get all of those by hand
//https://api.todoist.com/sync/v8/projects/get_data\?project_id\=2269005399
// Project data is not included in the regular sync for archived projects, so we need to get all of those by hand
for _, p := range archivedProjects {
resp, err = migration.DoPost("https://api.todoist.com/sync/v8/projects/get_data?project_id="+strconv.FormatInt(p.ID, 10), form)
resp, err = migration.DoPostWithHeaders("https://api.todoist.com/sync/v9/projects/get_data?project_id="+p.ID, form, bearerHeader)
if err != nil {
return
}

View File

@ -18,7 +18,6 @@ package todoist
import (
"os"
"strconv"
"testing"
"time"
@ -47,33 +46,32 @@ func TestConvertTodoistToVikunja(t *testing.T) {
dueTimeWithTime = dueTimeWithTime.In(config.GetTimeZone())
nilTime, err := time.Parse(time.RFC3339Nano, "0001-01-01T00:00:00Z")
assert.NoError(t, err)
exampleFile, err := os.ReadFile(config.ServiceRootpath.GetString() + "/pkg/modules/migration/wunderlist/testimage.jpg")
exampleFile, err := os.ReadFile(config.ServiceRootpath.GetString() + "/pkg/modules/migration/testimage.jpg")
assert.NoError(t, err)
makeTestItem := func(id, projectId int64, hasDueDate, hasLabels, done bool) *item {
makeTestItem := func(id, projectId string, hasDueDate, hasLabels, done bool) *item {
item := &item{
ID: id,
UserID: 1855589,
UserID: "1855589",
ProjectID: projectId,
Content: "Task" + strconv.FormatInt(id, 10),
Content: "Task" + id,
Priority: 1,
ParentID: 0,
ChildOrder: 1,
DateAdded: time1,
DateCompleted: nilTime,
}
if done {
item.Checked = 1
item.Checked = true
item.DateCompleted = time3
}
if hasLabels {
item.Labels = []int64{
80000,
80001,
80002,
80003,
item.Labels = []string{
"Label1",
"Label2",
"Label3",
"Label4",
}
}
@ -91,163 +89,163 @@ func TestConvertTodoistToVikunja(t *testing.T) {
testSync := &sync{
Projects: []*project{
{
ID: 396936926,
ID: "396936926",
Name: "Project1",
Color: 30,
Color: "berry_red",
ChildOrder: 1,
Collapsed: 0,
Collapsed: false,
Shared: false,
IsDeleted: 0,
IsArchived: 0,
IsFavorite: 0,
IsDeleted: false,
IsArchived: false,
IsFavorite: false,
},
{
ID: 396936927,
ID: "396936927",
Name: "Project2",
Color: 37,
Color: "mint_green",
ChildOrder: 1,
Collapsed: 0,
Collapsed: false,
Shared: false,
IsDeleted: 0,
IsArchived: 0,
IsFavorite: 0,
IsDeleted: false,
IsArchived: false,
IsFavorite: false,
},
{
ID: 396936928,
ID: "396936928",
Name: "Project3 - Archived",
Color: 37,
Color: "mint_green",
ChildOrder: 1,
Collapsed: 0,
Collapsed: false,
Shared: false,
IsDeleted: 0,
IsArchived: 1,
IsFavorite: 0,
IsDeleted: false,
IsArchived: true,
IsFavorite: false,
},
},
Items: []*item{
makeTestItem(400000000, 396936926, false, false, false),
makeTestItem(400000001, 396936926, false, false, false),
makeTestItem(400000002, 396936926, false, false, false),
makeTestItem(400000003, 396936926, true, true, true),
makeTestItem(400000004, 396936926, false, true, false),
makeTestItem(400000005, 396936926, true, false, true),
makeTestItem(400000006, 396936926, true, false, true),
makeTestItem("400000000", "396936926", false, false, false),
makeTestItem("400000001", "396936926", false, false, false),
makeTestItem("400000002", "396936926", false, false, false),
makeTestItem("400000003", "396936926", true, true, true),
makeTestItem("400000004", "396936926", false, true, false),
makeTestItem("400000005", "396936926", true, false, true),
makeTestItem("400000006", "396936926", true, false, true),
{
ID: 400000110,
UserID: 1855589,
ProjectID: 396936926,
ID: "400000110",
UserID: "1855589",
ProjectID: "396936926",
Content: "Task with parent",
Priority: 2,
ParentID: 400000006,
ParentID: "400000006",
ChildOrder: 1,
Checked: 0,
Checked: false,
DateAdded: time1,
},
{
ID: 400000106,
UserID: 1855589,
ProjectID: 396936926,
ID: "400000106",
UserID: "1855589",
ProjectID: "396936926",
Content: "Task400000106",
Priority: 1,
ParentID: 0,
ParentID: "",
ChildOrder: 1,
DateAdded: time1,
Checked: 1,
Checked: true,
DateCompleted: time3,
Due: &dueDate{
Date: "2021-01-31T19:00:00Z",
Timezone: nil,
IsRecurring: false,
},
Labels: []int64{
80000,
80001,
80002,
80003,
Labels: []string{
"Label1",
"Label2",
"Label3",
"Label4",
},
},
makeTestItem(400000107, 396936926, false, false, true),
makeTestItem(400000108, 396936926, false, false, true),
makeTestItem("400000107", "396936926", false, false, true),
makeTestItem("400000108", "396936926", false, false, true),
{
ID: 400000109,
UserID: 1855589,
ProjectID: 396936926,
ID: "400000109",
UserID: "1855589",
ProjectID: "396936926",
Content: "Task400000109",
Priority: 1,
ChildOrder: 1,
Checked: 1,
Checked: true,
DateAdded: time1,
DateCompleted: time3,
SectionID: 1234,
SectionID: "1234",
},
makeTestItem(400000007, 396936927, true, false, false),
makeTestItem(400000008, 396936927, true, false, false),
makeTestItem(400000009, 396936927, false, false, false),
makeTestItem(400000010, 396936927, false, false, true),
makeTestItem(400000101, 396936927, false, false, false),
makeTestItem(400000102, 396936927, true, true, false),
makeTestItem(400000103, 396936927, false, true, false),
makeTestItem(400000104, 396936927, false, true, false),
makeTestItem(400000105, 396936927, true, true, false),
makeTestItem("400000007", "396936927", true, false, false),
makeTestItem("400000008", "396936927", true, false, false),
makeTestItem("400000009", "396936927", false, false, false),
makeTestItem("400000010", "396936927", false, false, true),
makeTestItem("400000101", "396936927", false, false, false),
makeTestItem("400000102", "396936927", true, true, false),
makeTestItem("400000103", "396936927", false, true, false),
makeTestItem("400000104", "396936927", false, true, false),
makeTestItem("400000105", "396936927", true, true, false),
makeTestItem(400000111, 396936928, false, false, true),
makeTestItem("400000111", "396936928", false, false, true),
},
Labels: []*label{
{
ID: 80000,
ID: "80000",
Name: "Label1",
Color: 30,
Color: "berry_red",
},
{
ID: 80001,
ID: "80001",
Name: "Label2",
Color: 31,
Color: "red",
},
{
ID: 80002,
ID: "80002",
Name: "Label3",
Color: 32,
Color: "orange",
},
{
ID: 80003,
ID: "80003",
Name: "Label4",
Color: 33,
Color: "yellow",
},
},
Notes: []*note{
{
ID: 101476,
ID: "101476",
PostedUID: 1855589,
ItemID: 400000000,
ItemID: "400000000",
Content: "Lorem Ipsum dolor sit amet",
Posted: time1,
},
{
ID: 101477,
ID: "101477",
PostedUID: 1855589,
ItemID: 400000001,
ItemID: "400000001",
Content: "Lorem Ipsum dolor sit amet",
Posted: time1,
},
{
ID: 101478,
ID: "101478",
PostedUID: 1855589,
ItemID: 400000003,
ItemID: "400000003",
Content: "Lorem Ipsum dolor sit amet",
Posted: time1,
},
{
ID: 101479,
ID: "101479",
PostedUID: 1855589,
ItemID: 400000010,
ItemID: "400000010",
Content: "Lorem Ipsum dolor sit amet",
Posted: time1,
},
{
ID: 101480,
ID: "101480",
PostedUID: 1855589,
ItemID: 400000101,
ItemID: "400000101",
Content: "Lorem Ipsum dolor sit amet",
FileAttachment: &fileAttachment{
FileName: "file.md",
@ -263,43 +261,43 @@ func TestConvertTodoistToVikunja(t *testing.T) {
{
ID: 102000,
Content: "Lorem Ipsum dolor sit amet",
ProjectID: 396936926,
ProjectID: "396936926",
Posted: time3,
PostedUID: 1855589,
},
{
ID: 102001,
Content: "Lorem Ipsum dolor sit amet 2",
ProjectID: 396936926,
ProjectID: "396936926",
Posted: time3,
PostedUID: 1855589,
},
{
ID: 102002,
Content: "Lorem Ipsum dolor sit amet 3",
ProjectID: 396936926,
ProjectID: "396936926",
Posted: time3,
PostedUID: 1855589,
},
{
ID: 102003,
Content: "Lorem Ipsum dolor sit amet 4",
ProjectID: 396936927,
ProjectID: "396936927",
Posted: time3,
PostedUID: 1855589,
},
{
ID: 102004,
Content: "Lorem Ipsum dolor sit amet 5",
ProjectID: 396936927,
ProjectID: "396936927",
Posted: time3,
PostedUID: 1855589,
},
},
Reminders: []*reminder{
{
ID: 103000,
ItemID: 400000000,
ID: "103000",
ItemID: "400000000",
Due: &dueDate{
Date: "2020-06-15",
IsRecurring: false,
@ -307,40 +305,40 @@ func TestConvertTodoistToVikunja(t *testing.T) {
MmOffset: 180,
},
{
ID: 103001,
ItemID: 400000000,
ID: "103001",
ItemID: "400000000",
Due: &dueDate{
Date: "2020-06-16T07:00:00",
IsRecurring: false,
},
},
{
ID: 103002,
ItemID: 400000002,
ID: "103002",
ItemID: "400000002",
Due: &dueDate{
Date: "2020-07-15T07:00:00Z",
IsRecurring: true,
},
},
{
ID: 103003,
ItemID: 400000003,
ID: "103003",
ItemID: "400000003",
Due: &dueDate{
Date: "2020-06-15T07:00:00",
IsRecurring: false,
},
},
{
ID: 103004,
ItemID: 400000005,
ID: "103004",
ItemID: "400000005",
Due: &dueDate{
Date: "2020-06-15T07:00:00",
IsRecurring: false,
},
},
{
ID: 103006,
ItemID: 400000009,
ID: "103006",
ItemID: "400000009",
Due: &dueDate{
Date: "2020-06-15T07:00:00",
IsRecurring: false,
@ -349,9 +347,9 @@ func TestConvertTodoistToVikunja(t *testing.T) {
},
Sections: []*section{
{
ID: 1234,
ID: "1234",
Name: "Some Bucket",
ProjectID: 396936926,
ProjectID: "396936926",
},
},
}
@ -359,19 +357,19 @@ func TestConvertTodoistToVikunja(t *testing.T) {
vikunjaLabels := []*models.Label{
{
Title: "Label1",
HexColor: todoistColors[30],
HexColor: todoistColors["berry_red"],
},
{
Title: "Label2",
HexColor: todoistColors[31],
HexColor: todoistColors["red"],
},
{
Title: "Label3",
HexColor: todoistColors[32],
HexColor: todoistColors["orange"],
},
{
Title: "Label4",
HexColor: todoistColors[33],
HexColor: todoistColors["yellow"],
},
}
@ -385,11 +383,11 @@ func TestConvertTodoistToVikunja(t *testing.T) {
List: models.List{
Title: "Project1",
Description: "Lorem Ipsum dolor sit amet\nLorem Ipsum dolor sit amet 2\nLorem Ipsum dolor sit amet 3",
HexColor: todoistColors[30],
HexColor: todoistColors["berry_red"],
},
Buckets: []*models.Bucket{
{
ID: 1234,
ID: 1,
Title: "Some Bucket",
},
},
@ -510,7 +508,7 @@ func TestConvertTodoistToVikunja(t *testing.T) {
Done: true,
Created: time1,
DoneAt: time3,
BucketID: 1234,
BucketID: 1,
},
},
},
@ -519,7 +517,7 @@ func TestConvertTodoistToVikunja(t *testing.T) {
List: models.List{
Title: "Project2",
Description: "Lorem Ipsum dolor sit amet 4\nLorem Ipsum dolor sit amet 5",
HexColor: todoistColors[37],
HexColor: todoistColors["mint_green"],
},
Tasks: []*models.TaskWithComments{
{
@ -616,7 +614,7 @@ func TestConvertTodoistToVikunja(t *testing.T) {
{
List: models.List{
Title: "Project3 - Archived",
HexColor: todoistColors[37],
HexColor: todoistColors["mint_green"],
IsArchived: true,
},
Tasks: []*models.TaskWithComments{
@ -634,7 +632,7 @@ func TestConvertTodoistToVikunja(t *testing.T) {
},
}
doneItems := make(map[int64]*doneItem)
doneItems := make(map[string]*doneItem)
hierachie, err := convertTodoistToVikunja(testSync, doneItems)
assert.NoError(t, err)
assert.NotNil(t, hierachie)

View File

@ -36,7 +36,7 @@ func TestConvertTrelloToVikunja(t *testing.T) {
time1, err := time.Parse(time.RFC3339Nano, "2014-09-26T08:25:05Z")
assert.NoError(t, err)
exampleFile, err := os.ReadFile(config.ServiceRootpath.GetString() + "/pkg/modules/migration/wunderlist/testimage.jpg")
exampleFile, err := os.ReadFile(config.ServiceRootpath.GetString() + "/pkg/modules/migration/testimage.jpg")
assert.NoError(t, err)
trelloData := []*trello.Board{

View File

@ -1,512 +0,0 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
// 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 wunderlist
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strconv"
"time"
"code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/files"
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/modules/migration"
"code.vikunja.io/api/pkg/user"
"code.vikunja.io/api/pkg/utils"
)
// Migration represents the implementation of the migration for wunderlist
type Migration struct {
// Code is the code used to get a user api token
Code string `query:"code" json:"code"`
}
// This represents all necessary fields for getting an api token for the wunderlist api from a code
type wunderlistAuthRequest struct {
ClientID string `json:"client_id"`
ClientSecret string `json:"client_secret"`
Code string `json:"code"`
}
type wunderlistAuthToken struct {
AccessToken string `json:"access_token"`
}
type task struct {
AssigneeID int `json:"assignee_id"`
CreatedAt time.Time `json:"created_at"`
CreatedByID int `json:"created_by_id"`
Completed bool `json:"completed"`
CompletedAt time.Time `json:"completed_at"`
DueDate string `json:"due_date"`
ID int `json:"id"`
ListID int `json:"list_id"`
Revision int `json:"revision"`
Starred bool `json:"starred"`
Title string `json:"title"`
}
type list struct {
ID int `json:"id"`
CreatedAt time.Time `json:"created_at"`
Title string `json:"title"`
ListType string `json:"list_type"`
Type string `json:"type"`
Revision int `json:"revision"`
Migrated bool `json:"-"`
}
type folder struct {
ID int `json:"id"`
Title string `json:"title"`
ListIds []int `json:"list_ids"`
CreatedAt time.Time `json:"created_at"`
CreatedByRequestID string `json:"created_by_request_id"`
UpdatedAt time.Time `json:"updated_at"`
Type string `json:"type"`
Revision int `json:"revision"`
}
type note struct {
ID int `json:"id"`
TaskID int `json:"task_id"`
Content string `json:"content"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Revision int `json:"revision"`
}
type file struct {
ID int `json:"id"`
URL string `json:"url"`
TaskID int `json:"task_id"`
ListID int `json:"list_id"`
UserID int `json:"user_id"`
FileName string `json:"file_name"`
ContentType string `json:"content_type"`
FileSize int `json:"file_size"`
LocalCreatedAt time.Time `json:"local_created_at"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
Type string `json:"type"`
Revision int `json:"revision"`
}
type reminder struct {
ID int `json:"id"`
Date time.Time `json:"date"`
TaskID int `json:"task_id"`
Revision int `json:"revision"`
Type string `json:"type"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type subtask struct {
ID int `json:"id"`
TaskID int `json:"task_id"`
CreatedAt time.Time `json:"created_at"`
CreatedByID int `json:"created_by_id"`
Revision int `json:"revision"`
Title string `json:"title"`
}
type wunderlistContents struct {
tasks []*task
lists []*list
folders []*folder
notes []*note
files []*file
reminders []*reminder
subtasks []*subtask
}
func convertListForFolder(listID int, list *list, content *wunderlistContents) (*models.ListWithTasksAndBuckets, error) {
l := &models.ListWithTasksAndBuckets{
List: models.List{
Title: list.Title,
Created: list.CreatedAt,
},
}
// Find all tasks belonging to this list and put them in
for _, t := range content.tasks {
if t.ListID == listID {
newTask := &models.Task{
Title: t.Title,
Created: t.CreatedAt,
Done: t.Completed,
}
// Set Done At
if newTask.Done {
newTask.DoneAt = t.CompletedAt.In(config.GetTimeZone())
}
// Parse the due date
if t.DueDate != "" {
dueDate, err := time.Parse("2006-01-02", t.DueDate)
if err != nil {
return nil, err
}
newTask.DueDate = dueDate.In(config.GetTimeZone())
}
// Find related notes
for _, n := range content.notes {
if n.TaskID == t.ID {
newTask.Description = n.Content
}
}
// Attachments
for _, f := range content.files {
if f.TaskID == t.ID {
// Download the attachment and put it in the file
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, f.URL, nil)
if err != nil {
return nil, err
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
buf := &bytes.Buffer{}
_, err = buf.ReadFrom(resp.Body)
if err != nil {
return nil, err
}
newTask.Attachments = append(newTask.Attachments, &models.TaskAttachment{
File: &files.File{
Name: f.FileName,
Mime: f.ContentType,
Size: uint64(f.FileSize),
Created: f.CreatedAt,
// We directly pass the file contents here to have a way to link the attachment to the file later.
// Because we don't have an ID for our task at this point of the migration, we cannot just throw all
// attachments in a slice and do the work of downloading and properly storing them later.
FileContent: buf.Bytes(),
},
Created: f.CreatedAt,
})
}
}
// Subtasks
for _, s := range content.subtasks {
if s.TaskID == t.ID {
if newTask.RelatedTasks[models.RelationKindSubtask] == nil {
newTask.RelatedTasks = make(models.RelatedTaskMap)
}
newTask.RelatedTasks[models.RelationKindSubtask] = append(newTask.RelatedTasks[models.RelationKindSubtask], &models.Task{
Title: s.Title,
})
}
}
// Reminders
for _, r := range content.reminders {
if r.TaskID == t.ID {
newTask.Reminders = append(newTask.Reminders, r.Date.In(config.GetTimeZone()))
}
}
l.Tasks = append(l.Tasks, &models.TaskWithComments{Task: *newTask})
}
}
return l, nil
}
func convertWunderlistToVikunja(content *wunderlistContents) (fullVikunjaHierachie []*models.NamespaceWithListsAndTasks, err error) {
// Make a map from the list with the key being list id for easier handling
listMap := make(map[int]*list, len(content.lists))
for _, l := range content.lists {
listMap[l.ID] = l
}
// First, we look through all folders and create namespaces for them.
for _, folder := range content.folders {
namespace := &models.NamespaceWithListsAndTasks{
Namespace: models.Namespace{
Title: folder.Title,
Created: folder.CreatedAt,
Updated: folder.UpdatedAt,
},
}
// Then find all lists for that folder
for _, listID := range folder.ListIds {
if list, exists := listMap[listID]; exists {
l, err := convertListForFolder(listID, list, content)
if err != nil {
return nil, err
}
namespace.Lists = append(namespace.Lists, l)
// And mark the list as migrated so we don't iterate over it again
list.Migrated = true
}
}
// And then finally put the namespace (which now has all the details) back in the full array.
fullVikunjaHierachie = append(fullVikunjaHierachie, namespace)
}
// At the end, loop over all lists which don't belong to a namespace and put them in a default namespace
if len(listMap) > 0 {
newNamespace := &models.NamespaceWithListsAndTasks{
Namespace: models.Namespace{
Title: "Migrated from wunderlist",
},
}
for _, list := range listMap {
if list.Migrated {
continue
}
l, err := convertListForFolder(list.ID, list, content)
if err != nil {
return nil, err
}
newNamespace.Lists = append(newNamespace.Lists, l)
}
fullVikunjaHierachie = append(fullVikunjaHierachie, newNamespace)
}
return
}
func makeAuthGetRequest(token *wunderlistAuthToken, urlPart string, v interface{}, urlParams url.Values) error {
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "https://a.wunderlist.com/api/v1/"+urlPart, nil)
if err != nil {
return err
}
req.Header.Set("X-Access-Token", token.AccessToken)
req.Header.Set("X-Client-ID", config.MigrationWunderlistClientID.GetString())
req.URL.RawQuery = urlParams.Encode()
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
buf := &bytes.Buffer{}
_, err = buf.ReadFrom(resp.Body)
if err != nil {
return err
}
if resp.StatusCode > 399 {
return fmt.Errorf("wunderlist API Error: Status Code: %d, Response was: %s", resp.StatusCode, buf.String())
}
// If the response is an empty json array, we need to exit here, otherwise this breaks the json parser since it
// expects a null for an empty slice
str := buf.String()
if str == "[]" {
return nil
}
return json.Unmarshal(buf.Bytes(), v)
}
// Migrate migrates a user's wunderlist lists, tasks, etc.
// @Summary Migrate all lists, tasks etc. from wunderlist
// @Description Migrates all folders, lists, tasks, notes, reminders, subtasks and files from wunderlist to vikunja.
// @tags migration
// @Accept json
// @Produce json
// @Security JWTKeyAuth
// @Param migrationCode body wunderlist.Migration true "The auth code previously obtained from the auth url. See the docs for /migration/wunderlist/auth."
// @Success 200 {object} models.Message "A message telling you everything was migrated successfully."
// @Failure 500 {object} models.Message "Internal server error"
// @Router /migration/wunderlist/migrate [post]
func (w *Migration) Migrate(user *user.User) (err error) {
log.Debugf("[Wunderlist migration] Starting wunderlist migration for user %d", user.ID)
// Struct init
wContent := &wunderlistContents{
tasks: []*task{},
lists: []*list{},
folders: []*folder{},
notes: []*note{},
files: []*file{},
reminders: []*reminder{},
subtasks: []*subtask{},
}
// 0. Get api token from oauth user token
authRequest := wunderlistAuthRequest{
ClientID: config.MigrationWunderlistClientID.GetString(),
ClientSecret: config.MigrationWunderlistClientSecret.GetString(),
Code: w.Code,
}
jsonAuth, err := json.Marshal(authRequest)
if err != nil {
return
}
req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, "https://www.wunderlist.com/oauth/access_token", bytes.NewBuffer(jsonAuth))
if err != nil {
return err
}
req.Header.Add("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return
}
defer resp.Body.Close()
authToken := &wunderlistAuthToken{}
err = json.NewDecoder(resp.Body).Decode(authToken)
if err != nil {
return
}
log.Debugf("[Wunderlist migration] Start getting all data from wunderlist for user %d", user.ID)
// 1. Get all folders
err = makeAuthGetRequest(authToken, "folders", &wContent.folders, nil)
if err != nil {
return
}
// 2. Get all lists
err = makeAuthGetRequest(authToken, "lists", &wContent.lists, nil)
if err != nil {
return
}
for _, l := range wContent.lists {
listQueryParam := url.Values{"list_id": []string{strconv.Itoa(l.ID)}}
// 3. Get all tasks for each list
tasks := []*task{}
err = makeAuthGetRequest(authToken, "tasks", &tasks, listQueryParam)
if err != nil {
return
}
wContent.tasks = append(wContent.tasks, tasks...)
// 3. Get all done tasks for each list
doneTasks := []*task{}
err = makeAuthGetRequest(authToken, "tasks", &doneTasks, url.Values{"list_id": []string{strconv.Itoa(l.ID)}, "completed": []string{"true"}})
if err != nil {
return
}
wContent.tasks = append(wContent.tasks, doneTasks...)
// 4. Get all notes for all lists
notes := []*note{}
err = makeAuthGetRequest(authToken, "notes", &notes, listQueryParam)
if err != nil {
return
}
wContent.notes = append(wContent.notes, notes...)
// 5. Get all files for all lists
fils := []*file{}
err = makeAuthGetRequest(authToken, "files", &fils, listQueryParam)
if err != nil {
return
}
wContent.files = append(wContent.files, fils...)
// 6. Get all reminders for all lists
reminders := []*reminder{}
err = makeAuthGetRequest(authToken, "reminders", &reminders, listQueryParam)
if err != nil {
return
}
wContent.reminders = append(wContent.reminders, reminders...)
// 7. Get all subtasks for all lists
subtasks := []*subtask{}
err = makeAuthGetRequest(authToken, "subtasks", &subtasks, listQueryParam)
if err != nil {
return
}
wContent.subtasks = append(wContent.subtasks, subtasks...)
}
log.Debugf("[Wunderlist migration] Got all data from wunderlist for user %d", user.ID)
log.Debugf("[Wunderlist migration] Migrating data to vikunja format for user %d", user.ID)
// Convert + Insert everything
fullVikunjaHierachie, err := convertWunderlistToVikunja(wContent)
if err != nil {
return
}
log.Debugf("[Wunderlist migration] Done migrating data to vikunja format for user %d", user.ID)
log.Debugf("[Wunderlist migration] Insert data into db for user %d", user.ID)
err = migration.InsertFromStructure(fullVikunjaHierachie, user)
if err != nil {
return err
}
log.Debugf("[Wunderlist migration] Done inserting data into db for user %d", user.ID)
log.Debugf("[Wunderlist migration] Wunderlist migration for user %d done", user.ID)
return nil
}
// AuthURL returns the url users need to authenticate against
// @Summary Get the auth url from wunderlist
// @Description Returns the auth url where the user needs to get its auth code. This code can then be used to migrate everything from wunderlist to Vikunja.
// @tags migration
// @Produce json
// @Security JWTKeyAuth
// @Success 200 {object} handler.AuthURL "The auth url."
// @Failure 500 {object} models.Message "Internal server error"
// @Router /migration/wunderlist/auth [get]
func (w *Migration) AuthURL() string {
return "https://www.wunderlist.com/oauth/authorize?client_id=" +
config.MigrationWunderlistClientID.GetString() +
"&redirect_uri=" +
config.MigrationWunderlistRedirectURL.GetString() +
"&state=" + utils.MakeRandomString(32)
}
// Name is used to get the name of the wunderlist migration
// @Summary Get migration status
// @Description Returns if the current user already did the migation or not. This is useful to show a confirmation message in the frontend if the user is trying to do the same migration again.
// @tags migration
// @Produce json
// @Security JWTKeyAuth
// @Success 200 {object} migration.Status "The migration status"
// @Failure 500 {object} models.Message "Internal server error"
// @Router /migration/wunderlist/status [get]
func (w *Migration) Name() string {
return "wunderlist"
}

View File

@ -1,386 +0,0 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
// 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 wunderlist
import (
"os"
"strconv"
"testing"
"time"
"code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/files"
"code.vikunja.io/api/pkg/models"
"github.com/stretchr/testify/assert"
"gopkg.in/d4l3k/messagediff.v1"
)
func TestWunderlistParsing(t *testing.T) {
config.InitConfig()
time1, err := time.Parse(time.RFC3339Nano, "2013-08-30T08:29:46.203Z")
assert.NoError(t, err)
time1 = time1.In(config.GetTimeZone())
time2, err := time.Parse(time.RFC3339Nano, "2013-08-30T08:36:13.273Z")
assert.NoError(t, err)
time2 = time2.In(config.GetTimeZone())
time3, err := time.Parse(time.RFC3339Nano, "2013-09-05T08:36:13.273Z")
assert.NoError(t, err)
time3 = time3.In(config.GetTimeZone())
time4, err := time.Parse(time.RFC3339Nano, "2013-08-02T11:58:55Z")
assert.NoError(t, err)
time4 = time4.In(config.GetTimeZone())
exampleFile, err := os.ReadFile(config.ServiceRootpath.GetString() + "/pkg/modules/migration/wunderlist/testimage.jpg")
assert.NoError(t, err)
createTestTask := func(id, listID int, done bool) *task {
completedAt, err := time.Parse(time.RFC3339Nano, "1970-01-01T00:00:00Z")
assert.NoError(t, err)
if done {
completedAt = time1
}
completedAt = completedAt.In(config.GetTimeZone())
return &task{
ID: id,
AssigneeID: 123,
CreatedAt: time1,
DueDate: "2013-09-05",
ListID: listID,
Title: "Ipsum" + strconv.Itoa(id),
Completed: done,
CompletedAt: completedAt,
}
}
createTestNote := func(id, taskID int) *note {
return &note{
ID: id,
TaskID: taskID,
Content: "Lorem Ipsum dolor sit amet",
CreatedAt: time3,
UpdatedAt: time2,
}
}
fixtures := &wunderlistContents{
folders: []*folder{
{
ID: 123,
Title: "Lorem Ipsum",
ListIds: []int{1, 2, 3, 4},
CreatedAt: time1,
UpdatedAt: time2,
},
},
lists: []*list{
{
ID: 1,
CreatedAt: time1,
Title: "Lorem1",
},
{
ID: 2,
CreatedAt: time1,
Title: "Lorem2",
},
{
ID: 3,
CreatedAt: time1,
Title: "Lorem3",
},
{
ID: 4,
CreatedAt: time1,
Title: "Lorem4",
},
{
ID: 5,
CreatedAt: time4,
Title: "List without a namespace",
},
},
tasks: []*task{
createTestTask(1, 1, false),
createTestTask(2, 1, false),
createTestTask(3, 2, true),
createTestTask(4, 2, false),
createTestTask(5, 3, false),
createTestTask(6, 3, true),
createTestTask(7, 3, true),
createTestTask(8, 3, false),
createTestTask(9, 4, true),
createTestTask(10, 4, true),
},
notes: []*note{
createTestNote(1, 1),
createTestNote(2, 2),
createTestNote(3, 3),
},
files: []*file{
{
ID: 1,
URL: "https://vikunja.io/testimage.jpg", // Using an image which we are hosting, so it'll still be up
TaskID: 1,
ListID: 1,
FileName: "file.md",
ContentType: "text/plain",
FileSize: 12345,
CreatedAt: time2,
UpdatedAt: time4,
},
{
ID: 2,
URL: "https://vikunja.io/testimage.jpg",
TaskID: 3,
ListID: 2,
FileName: "file2.md",
ContentType: "text/plain",
FileSize: 12345,
CreatedAt: time3,
UpdatedAt: time4,
},
},
reminders: []*reminder{
{
ID: 1,
Date: time4,
TaskID: 1,
CreatedAt: time4,
UpdatedAt: time4,
},
{
ID: 2,
Date: time3,
TaskID: 4,
CreatedAt: time3,
UpdatedAt: time3,
},
},
subtasks: []*subtask{
{
ID: 1,
TaskID: 2,
CreatedAt: time4,
Title: "LoremSub1",
},
{
ID: 2,
TaskID: 2,
CreatedAt: time4,
Title: "LoremSub2",
},
{
ID: 3,
TaskID: 4,
CreatedAt: time4,
Title: "LoremSub3",
},
},
}
expectedHierachie := []*models.NamespaceWithListsAndTasks{
{
Namespace: models.Namespace{
Title: "Lorem Ipsum",
Created: time1,
Updated: time2,
},
Lists: []*models.ListWithTasksAndBuckets{
{
List: models.List{
Created: time1,
Title: "Lorem1",
},
Tasks: []*models.TaskWithComments{
{
Task: models.Task{
Title: "Ipsum1",
DueDate: time.Unix(1378339200, 0).In(config.GetTimeZone()),
Created: time1,
Description: "Lorem Ipsum dolor sit amet",
Attachments: []*models.TaskAttachment{
{
File: &files.File{
Name: "file.md",
Mime: "text/plain",
Size: 12345,
Created: time2,
FileContent: exampleFile,
},
Created: time2,
},
},
Reminders: []time.Time{time4},
},
},
{
Task: models.Task{
Title: "Ipsum2",
DueDate: time.Unix(1378339200, 0).In(config.GetTimeZone()),
Created: time1,
Description: "Lorem Ipsum dolor sit amet",
RelatedTasks: map[models.RelationKind][]*models.Task{
models.RelationKindSubtask: {
{
Title: "LoremSub1",
},
{
Title: "LoremSub2",
},
},
},
},
},
},
},
{
List: models.List{
Created: time1,
Title: "Lorem2",
},
Tasks: []*models.TaskWithComments{
{
Task: models.Task{
Title: "Ipsum3",
Done: true,
DoneAt: time1,
DueDate: time.Unix(1378339200, 0).In(config.GetTimeZone()),
Created: time1,
Description: "Lorem Ipsum dolor sit amet",
Attachments: []*models.TaskAttachment{
{
File: &files.File{
Name: "file2.md",
Mime: "text/plain",
Size: 12345,
Created: time3,
FileContent: exampleFile,
},
Created: time3,
},
},
},
},
{
Task: models.Task{
Title: "Ipsum4",
DueDate: time.Unix(1378339200, 0).In(config.GetTimeZone()),
Created: time1,
Reminders: []time.Time{time3},
RelatedTasks: map[models.RelationKind][]*models.Task{
models.RelationKindSubtask: {
{
Title: "LoremSub3",
},
},
},
},
},
},
},
{
List: models.List{
Created: time1,
Title: "Lorem3",
},
Tasks: []*models.TaskWithComments{
{
Task: models.Task{
Title: "Ipsum5",
DueDate: time.Unix(1378339200, 0).In(config.GetTimeZone()),
Created: time1,
},
},
{
Task: models.Task{
Title: "Ipsum6",
DueDate: time.Unix(1378339200, 0).In(config.GetTimeZone()),
Created: time1,
Done: true,
DoneAt: time1,
},
},
{
Task: models.Task{
Title: "Ipsum7",
DueDate: time.Unix(1378339200, 0).In(config.GetTimeZone()),
Created: time1,
Done: true,
DoneAt: time1,
},
},
{
Task: models.Task{
Title: "Ipsum8",
DueDate: time.Unix(1378339200, 0).In(config.GetTimeZone()),
Created: time1,
},
},
},
},
{
List: models.List{
Created: time1,
Title: "Lorem4",
},
Tasks: []*models.TaskWithComments{
{
Task: models.Task{
Title: "Ipsum9",
DueDate: time.Unix(1378339200, 0).In(config.GetTimeZone()),
Created: time1,
Done: true,
DoneAt: time1,
},
},
{
Task: models.Task{
Title: "Ipsum10",
DueDate: time.Unix(1378339200, 0).In(config.GetTimeZone()),
Created: time1,
Done: true,
DoneAt: time1,
},
},
},
},
},
},
{
Namespace: models.Namespace{
Title: "Migrated from wunderlist",
},
Lists: []*models.ListWithTasksAndBuckets{
{
List: models.List{
Created: time4,
Title: "List without a namespace",
},
},
},
},
}
hierachie, err := convertWunderlistToVikunja(fixtures)
assert.NoError(t, err)
assert.NotNil(t, hierachie)
if diff, equal := messagediff.PrettyDiff(hierachie, expectedHierachie); !equal {
t.Errorf("converted wunderlist data = %v, want %v, diff: %v", hierachie, expectedHierachie, diff)
}
}

View File

@ -205,7 +205,7 @@ func UploadAvatar(c echo.Context) (err error) {
u.AvatarFileID = f.ID
u.AvatarProvider = "upload"
if _, err := user.UpdateUser(s, u); err != nil {
if _, err := user.UpdateUser(s, u, false); err != nil {
_ = s.Rollback()
return handler.HandleHTTPError(err, c)
}

View File

@ -27,7 +27,6 @@ import (
"code.vikunja.io/api/pkg/modules/migration/todoist"
"code.vikunja.io/api/pkg/modules/migration/trello"
vikunja_file "code.vikunja.io/api/pkg/modules/migration/vikunja-file"
"code.vikunja.io/api/pkg/modules/migration/wunderlist"
"code.vikunja.io/api/pkg/version"
"github.com/labstack/echo/v4"
@ -121,10 +120,6 @@ func Info(c echo.Context) error {
info.AuthInfo.OpenIDConnect.Providers = providers
// Migrators
if config.MigrationWunderlistEnable.GetBool() {
m := &wunderlist.Migration{}
info.AvailableMigrators = append(info.AvailableMigrators, m.Name())
}
if config.MigrationTodoistEnable.GetBool() {
m := &todoist.Migration{}
info.AvailableMigrators = append(info.AvailableMigrators, m.Name())

View File

@ -133,7 +133,7 @@ func ChangeUserAvatarProvider(c echo.Context) error {
user.AvatarProvider = uap.AvatarProvider
_, err = user2.UpdateUser(s, user)
_, err = user2.UpdateUser(s, user, false)
if err != nil {
_ = s.Rollback()
return handler.HandleHTTPError(err, c)
@ -199,7 +199,7 @@ func UpdateGeneralUserSettings(c echo.Context) error {
user.Timezone = us.Timezone
user.OverdueTasksRemindersTime = us.OverdueTasksRemindersTime
_, err = user2.UpdateUser(s, user)
_, err = user2.UpdateUser(s, user, true)
if err != nil {
_ = s.Rollback()
return handler.HandleHTTPError(err, c)

View File

@ -48,7 +48,6 @@ package routes
import (
"errors"
"fmt"
"net/url"
"strings"
"time"
@ -70,7 +69,6 @@ import (
"code.vikunja.io/api/pkg/modules/migration/todoist"
"code.vikunja.io/api/pkg/modules/migration/trello"
vikunja_file "code.vikunja.io/api/pkg/modules/migration/vikunja-file"
"code.vikunja.io/api/pkg/modules/migration/wunderlist"
apiv1 "code.vikunja.io/api/pkg/routes/api/v1"
"code.vikunja.io/api/pkg/routes/caldav"
_ "code.vikunja.io/api/pkg/swagger" // To generate swagger docs
@ -80,7 +78,7 @@ import (
"github.com/getsentry/sentry-go"
sentryecho "github.com/getsentry/sentry-go/echo"
"github.com/golang-jwt/jwt/v4"
echojwt "github.com/labstack/echo-jwt/v4"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
elog "github.com/labstack/gommon/log"
@ -273,29 +271,8 @@ func registerAPIRoutes(a *echo.Group) {
ur.POST("/shares/:share/auth", apiv1.AuthenticateLinkShare)
}
// ===== Routes with Authetication =====
// Authetification
a.Use(middleware.JWTWithConfig(middleware.JWTConfig{
// Custom parse function to make the middleware work with the github.com/golang-jwt/jwt/v4 package.
// See https://github.com/labstack/echo/pull/1916#issuecomment-878046299
ParseTokenFunc: func(auth string, c echo.Context) (interface{}, error) {
keyFunc := func(t *jwt.Token) (interface{}, error) {
if t.Method.Alg() != "HS256" {
return nil, fmt.Errorf("unexpected jwt signing method=%v", t.Header["alg"])
}
return []byte(config.ServiceJWTSecret.GetString()), nil
}
token, err := jwt.Parse(auth, keyFunc)
if err != nil {
return nil, err
}
if !token.Valid {
return nil, errors.New("invalid token")
}
return token, nil
},
}))
// ===== Routes with Authentication =====
a.Use(echojwt.JWT([]byte(config.ServiceJWTSecret.GetString())))
// Rate limit
setupRateLimit(a, config.RateLimitKind.GetString())
@ -612,16 +589,6 @@ func registerAPIRoutes(a *echo.Group) {
}
func registerMigrations(m *echo.Group) {
// Wunderlist
if config.MigrationWunderlistEnable.GetBool() {
wunderlistMigrationHandler := &migrationHandler.MigrationWeb{
MigrationStruct: func() migration.Migrator {
return &wunderlist.Migration{}
},
}
wunderlistMigrationHandler.RegisterRoutes(m)
}
// Todoist
if config.MigrationTodoistEnable.GetBool() {
todoistMigrationHandler := &migrationHandler.MigrationWeb{

View File

@ -95,7 +95,7 @@ const docTemplate = `{
{
"type": "integer",
"description": "Unsplash Image ID",
"name": "thumb",
"name": "image",
"in": "path",
"required": true
}
@ -141,7 +141,7 @@ const docTemplate = `{
{
"type": "integer",
"description": "Unsplash Image ID",
"name": "thumb",
"name": "image",
"in": "path",
"required": true
}
@ -3061,113 +3061,6 @@ const docTemplate = `{
}
}
},
"/migration/wunderlist/auth": {
"get": {
"security": [
{
"JWTKeyAuth": []
}
],
"description": "Returns the auth url where the user needs to get its auth code. This code can then be used to migrate everything from wunderlist to Vikunja.",
"produces": [
"application/json"
],
"tags": [
"migration"
],
"summary": "Get the auth url from wunderlist",
"responses": {
"200": {
"description": "The auth url.",
"schema": {
"$ref": "#/definitions/handler.AuthURL"
}
},
"500": {
"description": "Internal server error",
"schema": {
"$ref": "#/definitions/models.Message"
}
}
}
}
},
"/migration/wunderlist/migrate": {
"post": {
"security": [
{
"JWTKeyAuth": []
}
],
"description": "Migrates all folders, lists, tasks, notes, reminders, subtasks and files from wunderlist to vikunja.",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"migration"
],
"summary": "Migrate all lists, tasks etc. from wunderlist",
"parameters": [
{
"description": "The auth code previously obtained from the auth url. See the docs for /migration/wunderlist/auth.",
"name": "migrationCode",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/wunderlist.Migration"
}
}
],
"responses": {
"200": {
"description": "A message telling you everything was migrated successfully.",
"schema": {
"$ref": "#/definitions/models.Message"
}
},
"500": {
"description": "Internal server error",
"schema": {
"$ref": "#/definitions/models.Message"
}
}
}
}
},
"/migration/wunderlist/status": {
"get": {
"security": [
{
"JWTKeyAuth": []
}
],
"description": "Returns if the current user already did the migation or not. This is useful to show a confirmation message in the frontend if the user is trying to do the same migration again.",
"produces": [
"application/json"
],
"tags": [
"migration"
],
"summary": "Get migration status",
"responses": {
"200": {
"description": "The migration status",
"schema": {
"$ref": "#/definitions/migration.Status"
}
},
"500": {
"description": "Internal server error",
"schema": {
"$ref": "#/definitions/models.Message"
}
}
}
}
},
"/namespace/{id}": {
"post": {
"security": [
@ -5688,7 +5581,7 @@ const docTemplate = `{
{
"type": "integer",
"description": "The id of the other task.",
"name": "otherTaskID",
"name": "otherTaskId",
"in": "path",
"required": true
}
@ -7727,36 +7620,11 @@ const docTemplate = `{
},
"created_by": {
"description": "The user who initially created the bucket.",
"$ref": "#/definitions/user.User"
},
"filter_by": {
"description": "The field name of the field to filter by",
"type": "array",
"items": {
"type": "string"
}
},
"filter_comparator": {
"description": "The comparator for field and value",
"type": "array",
"items": {
"type": "string"
}
},
"filter_concat": {
"description": "The way all filter conditions are concatenated together, can be either \"and\" or \"or\".,",
"type": "string"
},
"filter_include_nulls": {
"description": "If set to true, the result will also include null values",
"type": "boolean"
},
"filter_value": {
"description": "The value of the field name to filter by",
"type": "array",
"items": {
"type": "string"
}
"allOf": [
{
"$ref": "#/definitions/user.User"
}
]
},
"id": {
"description": "The unique, numeric id of this bucket.",
@ -7775,24 +7643,10 @@ const docTemplate = `{
"description": "The list this bucket belongs to.",
"type": "integer"
},
"order_by": {
"description": "The query parameter to order the items by. This can be either asc or desc, with asc being the default.",
"type": "array",
"items": {
"type": "string"
}
},
"position": {
"description": "The position this bucket has when querying all buckets. See the tasks.position property on how to use this.",
"type": "number"
},
"sort_by": {
"description": "The query parameter to sort by. This is for ex. done, priority, etc.",
"type": "array",
"items": {
"type": "string"
}
},
"tasks": {
"description": "All tasks which belong to this bucket.",
"type": "array",
@ -7808,9 +7662,7 @@ const docTemplate = `{
"updated": {
"description": "A timestamp when this bucket was last updated. You cannot change this value.",
"type": "string"
},
"web.CRUDable": {},
"web.Rights": {}
}
}
},
"models.BulkAssignees": {
@ -7822,9 +7674,7 @@ const docTemplate = `{
"items": {
"$ref": "#/definitions/user.User"
}
},
"web.CRUDable": {},
"web.Rights": {}
}
}
},
"models.BulkTask": {
@ -7858,7 +7708,11 @@ const docTemplate = `{
},
"created_by": {
"description": "The user who initially created the task.",
"$ref": "#/definitions/user.User"
"allOf": [
{
"$ref": "#/definitions/user.User"
}
]
},
"description": {
"description": "The task description.",
@ -7930,7 +7784,11 @@ const docTemplate = `{
},
"related_tasks": {
"description": "All related tasks, grouped by their relation kind",
"$ref": "#/definitions/models.RelatedTaskMap"
"allOf": [
{
"$ref": "#/definitions/models.RelatedTaskMap"
}
]
},
"reminder_dates": {
"description": "An array of datetimes when the user wants to be reminded of the task.",
@ -7945,7 +7803,11 @@ const docTemplate = `{
},
"repeat_mode": {
"description": "Can have three possible values which will trigger when the task is marked as done: 0 = repeats after the amount specified in repeat_after, 1 = repeats all dates each months (ignoring repeat_after), 3 = repeats from the current date rather than the last set date.",
"type": "integer"
"allOf": [
{
"$ref": "#/definitions/models.TaskRepeatMode"
}
]
},
"start_date": {
"description": "When this task starts.",
@ -7953,7 +7815,11 @@ const docTemplate = `{
},
"subscription": {
"description": "The subscription status for the user reading this task. You can only read this property, use the subscription endpoints to modify it.\nWill only returned when retreiving one task.",
"$ref": "#/definitions/models.Subscription"
"allOf": [
{
"$ref": "#/definitions/models.Subscription"
}
]
},
"task_ids": {
"description": "A list of task ids to update",
@ -7970,9 +7836,7 @@ const docTemplate = `{
"updated": {
"description": "A timestamp when this task was last updated. You cannot change this value.",
"type": "string"
},
"web.CRUDable": {},
"web.Rights": {}
}
}
},
"models.DatabaseNotifications": {
@ -8000,9 +7864,7 @@ const docTemplate = `{
"read_at": {
"description": "When this notification is marked as read, this will be updated with the current timestamp.",
"type": "string"
},
"web.CRUDable": {},
"web.Rights": {}
}
}
},
"models.Label": {
@ -8014,7 +7876,11 @@ const docTemplate = `{
},
"created_by": {
"description": "The user who created this label",
"$ref": "#/definitions/user.User"
"allOf": [
{
"$ref": "#/definitions/user.User"
}
]
},
"description": {
"description": "The label description.",
@ -8038,9 +7904,7 @@ const docTemplate = `{
"updated": {
"description": "A timestamp when this label was last updated. You cannot change this value.",
"type": "string"
},
"web.CRUDable": {},
"web.Rights": {}
}
}
},
"models.LabelTask": {
@ -8053,9 +7917,7 @@ const docTemplate = `{
"label_id": {
"description": "The label id you want to associate with a task.",
"type": "integer"
},
"web.CRUDable": {},
"web.Rights": {}
}
}
},
"models.LabelTaskBulk": {
@ -8067,9 +7929,7 @@ const docTemplate = `{
"items": {
"$ref": "#/definitions/models.Label"
}
},
"web.CRUDable": {},
"web.Rights": {}
}
}
},
"models.LinkSharing": {
@ -8097,26 +7957,36 @@ const docTemplate = `{
},
"right": {
"description": "The right this list is shared with. 0 = Read only, 1 = Read \u0026 Write, 2 = Admin. See the docs for more details.",
"type": "integer",
"default": 0,
"maximum": 2
"maximum": 2,
"allOf": [
{
"$ref": "#/definitions/models.Right"
}
]
},
"shared_by": {
"description": "The user who shared this list",
"$ref": "#/definitions/user.User"
"allOf": [
{
"$ref": "#/definitions/user.User"
}
]
},
"sharing_type": {
"description": "The kind of this link. 0 = undefined, 1 = without password, 2 = with password.",
"type": "integer",
"default": 0,
"maximum": 2
"maximum": 2,
"allOf": [
{
"$ref": "#/definitions/models.SharingType"
}
]
},
"updated": {
"description": "A timestamp when this share was last updated. You cannot change this value.",
"type": "string"
},
"web.CRUDable": {},
"web.Rights": {}
}
}
},
"models.List": {
@ -8165,7 +8035,11 @@ const docTemplate = `{
},
"owner": {
"description": "The user who created this list.",
"$ref": "#/definitions/user.User"
"allOf": [
{
"$ref": "#/definitions/user.User"
}
]
},
"position": {
"description": "The position this list has when querying all lists. See the tasks.position property on how to use this.",
@ -8173,7 +8047,11 @@ const docTemplate = `{
},
"subscription": {
"description": "The subscription status for the user reading this list. You can only read this property, use the subscription endpoints to modify it.\nWill only returned when retreiving one list.",
"$ref": "#/definitions/models.Subscription"
"allOf": [
{
"$ref": "#/definitions/models.Subscription"
}
]
},
"title": {
"description": "The title of the list. You'll see this in the namespace overview.",
@ -8184,9 +8062,7 @@ const docTemplate = `{
"updated": {
"description": "A timestamp when this list was last updated. You cannot change this value.",
"type": "string"
},
"web.CRUDable": {},
"web.Rights": {}
}
}
},
"models.ListDuplicate": {
@ -8194,14 +8070,16 @@ const docTemplate = `{
"properties": {
"list": {
"description": "The copied list",
"$ref": "#/definitions/models.List"
"allOf": [
{
"$ref": "#/definitions/models.List"
}
]
},
"namespace_id": {
"description": "The target namespace ID",
"type": "integer"
},
"web.CRUDable": {},
"web.Rights": {}
}
}
},
"models.ListUser": {
@ -8217,9 +8095,13 @@ const docTemplate = `{
},
"right": {
"description": "The right this user has. 0 = Read only, 1 = Read \u0026 Write, 2 = Admin. See the docs for more details.",
"type": "integer",
"default": 0,
"maximum": 2
"maximum": 2,
"allOf": [
{
"$ref": "#/definitions/models.Right"
}
]
},
"updated": {
"description": "A timestamp when this relation was last updated. You cannot change this value.",
@ -8228,9 +8110,7 @@ const docTemplate = `{
"user_id": {
"description": "The username.",
"type": "string"
},
"web.CRUDable": {},
"web.Rights": {}
}
}
},
"models.Message": {
@ -8268,11 +8148,19 @@ const docTemplate = `{
},
"owner": {
"description": "The user who owns this namespace",
"$ref": "#/definitions/user.User"
"allOf": [
{
"$ref": "#/definitions/user.User"
}
]
},
"subscription": {
"description": "The subscription status for the user reading this namespace. You can only read this property, use the subscription endpoints to modify it.\nWill only returned when retreiving one namespace.",
"$ref": "#/definitions/models.Subscription"
"allOf": [
{
"$ref": "#/definitions/models.Subscription"
}
]
},
"title": {
"description": "The name of this namespace.",
@ -8283,9 +8171,7 @@ const docTemplate = `{
"updated": {
"description": "A timestamp when this namespace was last updated. You cannot change this value.",
"type": "string"
},
"web.CRUDable": {},
"web.Rights": {}
}
}
},
"models.NamespaceUser": {
@ -8301,9 +8187,13 @@ const docTemplate = `{
},
"right": {
"description": "The right this user has. 0 = Read only, 1 = Read \u0026 Write, 2 = Admin. See the docs for more details.",
"type": "integer",
"default": 0,
"maximum": 2
"maximum": 2,
"allOf": [
{
"$ref": "#/definitions/models.Right"
}
]
},
"updated": {
"description": "A timestamp when this relation was last updated. You cannot change this value.",
@ -8312,9 +8202,7 @@ const docTemplate = `{
"user_id": {
"description": "The username.",
"type": "string"
},
"web.CRUDable": {},
"web.Rights": {}
}
}
},
"models.NamespaceWithLists": {
@ -8349,11 +8237,19 @@ const docTemplate = `{
},
"owner": {
"description": "The user who owns this namespace",
"$ref": "#/definitions/user.User"
"allOf": [
{
"$ref": "#/definitions/user.User"
}
]
},
"subscription": {
"description": "The subscription status for the user reading this namespace. You can only read this property, use the subscription endpoints to modify it.\nWill only returned when retreiving one namespace.",
"$ref": "#/definitions/models.Subscription"
"allOf": [
{
"$ref": "#/definitions/models.Subscription"
}
]
},
"title": {
"description": "The name of this namespace.",
@ -8364,9 +8260,7 @@ const docTemplate = `{
"updated": {
"description": "A timestamp when this namespace was last updated. You cannot change this value.",
"type": "string"
},
"web.CRUDable": {},
"web.Rights": {}
}
}
},
"models.RelatedTaskMap": {
@ -8378,6 +8272,50 @@ const docTemplate = `{
}
}
},
"models.RelationKind": {
"type": "string",
"enum": [
"unknown",
"subtask",
"parenttask",
"related",
"duplicateof",
"duplicates",
"blocking",
"blocked",
"precedes",
"follows",
"copiedfrom",
"copiedto"
],
"x-enum-varnames": [
"RelationKindUnknown",
"RelationKindSubtask",
"RelationKindParenttask",
"RelationKindRelated",
"RelationKindDuplicateOf",
"RelationKindDuplicates",
"RelationKindBlocking",
"RelationKindBlocked",
"RelationKindPreceeds",
"RelationKindFollows",
"RelationKindCopiedFrom",
"RelationKindCopiedTo"
]
},
"models.Right": {
"type": "integer",
"enum": [
0,
1,
2
],
"x-enum-varnames": [
"RightRead",
"RightWrite",
"RightAdmin"
]
},
"models.SavedFilter": {
"type": "object",
"properties": {
@ -8391,7 +8329,11 @@ const docTemplate = `{
},
"filters": {
"description": "The actual filters this filter contains",
"$ref": "#/definitions/models.TaskCollection"
"allOf": [
{
"$ref": "#/definitions/models.TaskCollection"
}
]
},
"id": {
"description": "The unique numeric id of this saved filter",
@ -8403,7 +8345,11 @@ const docTemplate = `{
},
"owner": {
"description": "The user who owns this filter",
"$ref": "#/definitions/user.User"
"allOf": [
{
"$ref": "#/definitions/user.User"
}
]
},
"title": {
"description": "The title of the filter.",
@ -8414,11 +8360,22 @@ const docTemplate = `{
"updated": {
"description": "A timestamp when this filter was last updated. You cannot change this value.",
"type": "string"
},
"web.CRUDable": {},
"web.Rights": {}
}
}
},
"models.SharingType": {
"type": "integer",
"enum": [
0,
1,
2
],
"x-enum-varnames": [
"SharingTypeUnknown",
"SharingTypeWithoutPassword",
"SharingTypeWithPassword"
]
},
"models.Subscription": {
"type": "object",
"properties": {
@ -8439,10 +8396,12 @@ const docTemplate = `{
},
"user": {
"description": "The user who made this subscription",
"$ref": "#/definitions/user.User"
},
"web.CRUDable": {},
"web.Rights": {}
"allOf": [
{
"$ref": "#/definitions/user.User"
}
]
}
}
},
"models.Task": {
@ -8476,7 +8435,11 @@ const docTemplate = `{
},
"created_by": {
"description": "The user who initially created the task.",
"$ref": "#/definitions/user.User"
"allOf": [
{
"$ref": "#/definitions/user.User"
}
]
},
"description": {
"description": "The task description.",
@ -8548,7 +8511,11 @@ const docTemplate = `{
},
"related_tasks": {
"description": "All related tasks, grouped by their relation kind",
"$ref": "#/definitions/models.RelatedTaskMap"
"allOf": [
{
"$ref": "#/definitions/models.RelatedTaskMap"
}
]
},
"reminder_dates": {
"description": "An array of datetimes when the user wants to be reminded of the task.",
@ -8563,7 +8530,11 @@ const docTemplate = `{
},
"repeat_mode": {
"description": "Can have three possible values which will trigger when the task is marked as done: 0 = repeats after the amount specified in repeat_after, 1 = repeats all dates each months (ignoring repeat_after), 3 = repeats from the current date rather than the last set date.",
"type": "integer"
"allOf": [
{
"$ref": "#/definitions/models.TaskRepeatMode"
}
]
},
"start_date": {
"description": "When this task starts.",
@ -8571,7 +8542,11 @@ const docTemplate = `{
},
"subscription": {
"description": "The subscription status for the user reading this task. You can only read this property, use the subscription endpoints to modify it.\nWill only returned when retreiving one task.",
"$ref": "#/definitions/models.Subscription"
"allOf": [
{
"$ref": "#/definitions/models.Subscription"
}
]
},
"title": {
"description": "The task text. This is what you'll see in the list.",
@ -8581,9 +8556,7 @@ const docTemplate = `{
"updated": {
"description": "A timestamp when this task was last updated. You cannot change this value.",
"type": "string"
},
"web.CRUDable": {},
"web.Rights": {}
}
}
},
"models.TaskAssginee": {
@ -8594,9 +8567,7 @@ const docTemplate = `{
},
"user_id": {
"type": "integer"
},
"web.CRUDable": {},
"web.Rights": {}
}
}
},
"models.TaskAttachment": {
@ -8616,9 +8587,7 @@ const docTemplate = `{
},
"task_id": {
"type": "integer"
},
"web.CRUDable": {},
"web.Rights": {}
}
}
},
"models.TaskCollection": {
@ -8666,9 +8635,7 @@ const docTemplate = `{
"items": {
"type": "string"
}
},
"web.CRUDable": {},
"web.Rights": {}
}
}
},
"models.TaskComment": {
@ -8688,9 +8655,7 @@ const docTemplate = `{
},
"updated": {
"type": "string"
},
"web.CRUDable": {},
"web.Rights": {}
}
}
},
"models.TaskRelation": {
@ -8702,7 +8667,11 @@ const docTemplate = `{
},
"created_by": {
"description": "The user who created this relation",
"$ref": "#/definitions/user.User"
"allOf": [
{
"$ref": "#/definitions/user.User"
}
]
},
"other_task_id": {
"description": "The ID of the other task, the task which is being related.",
@ -8710,16 +8679,31 @@ const docTemplate = `{
},
"relation_kind": {
"description": "The kind of the relation.",
"type": "string"
"allOf": [
{
"$ref": "#/definitions/models.RelationKind"
}
]
},
"task_id": {
"description": "The ID of the \"base\" task, the task which has a relation to another.",
"type": "integer"
},
"web.CRUDable": {},
"web.Rights": {}
}
}
},
"models.TaskRepeatMode": {
"type": "integer",
"enum": [
0,
1,
2
],
"x-enum-varnames": [
"TaskRepeatModeDefault",
"TaskRepeatModeMonth",
"TaskRepeatModeFromCurrentDate"
]
},
"models.Team": {
"type": "object",
"properties": {
@ -8729,7 +8713,11 @@ const docTemplate = `{
},
"created_by": {
"description": "The user who created this team.",
"$ref": "#/definitions/user.User"
"allOf": [
{
"$ref": "#/definitions/user.User"
}
]
},
"description": {
"description": "The team's description.",
@ -8755,9 +8743,7 @@ const docTemplate = `{
"updated": {
"description": "A timestamp when this relation was last updated. You cannot change this value.",
"type": "string"
},
"web.CRUDable": {},
"web.Rights": {}
}
}
},
"models.TeamList": {
@ -8773,9 +8759,13 @@ const docTemplate = `{
},
"right": {
"description": "The right this team has. 0 = Read only, 1 = Read \u0026 Write, 2 = Admin. See the docs for more details.",
"type": "integer",
"default": 0,
"maximum": 2
"maximum": 2,
"allOf": [
{
"$ref": "#/definitions/models.Right"
}
]
},
"team_id": {
"description": "The team id.",
@ -8784,9 +8774,7 @@ const docTemplate = `{
"updated": {
"description": "A timestamp when this relation was last updated. You cannot change this value.",
"type": "string"
},
"web.CRUDable": {},
"web.Rights": {}
}
}
},
"models.TeamMember": {
@ -8807,9 +8795,7 @@ const docTemplate = `{
"username": {
"description": "The username of the member. We use this to prevent automated user id entering.",
"type": "string"
},
"web.CRUDable": {},
"web.Rights": {}
}
}
},
"models.TeamNamespace": {
@ -8825,9 +8811,13 @@ const docTemplate = `{
},
"right": {
"description": "The right this team has. 0 = Read only, 1 = Read \u0026 Write, 2 = Admin. See the docs for more details.",
"type": "integer",
"default": 0,
"maximum": 2
"maximum": 2,
"allOf": [
{
"$ref": "#/definitions/models.Right"
}
]
},
"team_id": {
"description": "The team id.",
@ -8836,9 +8826,7 @@ const docTemplate = `{
"updated": {
"description": "A timestamp when this relation was last updated. You cannot change this value.",
"type": "string"
},
"web.CRUDable": {},
"web.Rights": {}
}
}
},
"models.TeamUser": {
@ -8874,8 +8862,7 @@ const docTemplate = `{
"type": "string",
"maxLength": 250,
"minLength": 1
},
"web.Auth": {}
}
}
},
"models.TeamWithRight": {
@ -8887,7 +8874,11 @@ const docTemplate = `{
},
"created_by": {
"description": "The user who created this team.",
"$ref": "#/definitions/user.User"
"allOf": [
{
"$ref": "#/definitions/user.User"
}
]
},
"description": {
"description": "The team's description.",
@ -8911,16 +8902,12 @@ const docTemplate = `{
"minLength": 1
},
"right": {
"description": "The right this team has. 0 = Read only, 1 = Read \u0026 Write, 2 = Admin. See the docs for more details.",
"type": "integer",
"default": 0
"$ref": "#/definitions/models.Right"
},
"updated": {
"description": "A timestamp when this relation was last updated. You cannot change this value.",
"type": "string"
},
"web.CRUDable": {},
"web.Rights": {}
}
}
},
"models.UserWithRight": {
@ -8944,9 +8931,7 @@ const docTemplate = `{
"type": "string"
},
"right": {
"description": "The right this user has. 0 = Read only, 1 = Read \u0026 Write, 2 = Admin. See the docs for more details.",
"type": "integer",
"default": 0
"$ref": "#/definitions/models.Right"
},
"updated": {
"description": "A timestamp when this task was last updated. You cannot change this value.",
@ -8957,8 +8942,7 @@ const docTemplate = `{
"type": "string",
"maxLength": 250,
"minLength": 1
},
"web.Auth": {}
}
}
},
"notifications.DatabaseNotification": {
@ -9187,8 +9171,7 @@ const docTemplate = `{
"type": "string",
"maxLength": 250,
"minLength": 1
},
"web.Auth": {}
}
}
},
"v1.LinkShareAuth": {
@ -9396,15 +9379,6 @@ const docTemplate = `{
"type": "string"
}
}
},
"wunderlist.Migration": {
"type": "object",
"properties": {
"code": {
"description": "Code is the code used to get a user api token",
"type": "string"
}
}
}
},
"securityDefinitions": {

View File

@ -86,7 +86,7 @@
{
"type": "integer",
"description": "Unsplash Image ID",
"name": "thumb",
"name": "image",
"in": "path",
"required": true
}
@ -132,7 +132,7 @@
{
"type": "integer",
"description": "Unsplash Image ID",
"name": "thumb",
"name": "image",
"in": "path",
"required": true
}
@ -3052,113 +3052,6 @@
}
}
},
"/migration/wunderlist/auth": {
"get": {
"security": [
{
"JWTKeyAuth": []
}
],
"description": "Returns the auth url where the user needs to get its auth code. This code can then be used to migrate everything from wunderlist to Vikunja.",
"produces": [
"application/json"
],
"tags": [
"migration"
],
"summary": "Get the auth url from wunderlist",
"responses": {
"200": {
"description": "The auth url.",
"schema": {
"$ref": "#/definitions/handler.AuthURL"
}
},
"500": {
"description": "Internal server error",
"schema": {
"$ref": "#/definitions/models.Message"
}
}
}
}
},
"/migration/wunderlist/migrate": {
"post": {
"security": [
{
"JWTKeyAuth": []
}
],
"description": "Migrates all folders, lists, tasks, notes, reminders, subtasks and files from wunderlist to vikunja.",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"migration"
],
"summary": "Migrate all lists, tasks etc. from wunderlist",
"parameters": [
{
"description": "The auth code previously obtained from the auth url. See the docs for /migration/wunderlist/auth.",
"name": "migrationCode",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/wunderlist.Migration"
}
}
],
"responses": {
"200": {
"description": "A message telling you everything was migrated successfully.",
"schema": {
"$ref": "#/definitions/models.Message"
}
},
"500": {
"description": "Internal server error",
"schema": {
"$ref": "#/definitions/models.Message"
}
}
}
}
},
"/migration/wunderlist/status": {
"get": {
"security": [
{
"JWTKeyAuth": []
}
],
"description": "Returns if the current user already did the migation or not. This is useful to show a confirmation message in the frontend if the user is trying to do the same migration again.",
"produces": [
"application/json"
],
"tags": [
"migration"
],
"summary": "Get migration status",
"responses": {
"200": {
"description": "The migration status",
"schema": {
"$ref": "#/definitions/migration.Status"
}
},
"500": {
"description": "Internal server error",
"schema": {
"$ref": "#/definitions/models.Message"
}
}
}
}
},
"/namespace/{id}": {
"post": {
"security": [
@ -5679,7 +5572,7 @@
{
"type": "integer",
"description": "The id of the other task.",
"name": "otherTaskID",
"name": "otherTaskId",
"in": "path",
"required": true
}
@ -7718,36 +7611,11 @@
},
"created_by": {
"description": "The user who initially created the bucket.",
"$ref": "#/definitions/user.User"
},
"filter_by": {
"description": "The field name of the field to filter by",
"type": "array",
"items": {
"type": "string"
}
},
"filter_comparator": {
"description": "The comparator for field and value",
"type": "array",
"items": {
"type": "string"
}
},
"filter_concat": {
"description": "The way all filter conditions are concatenated together, can be either \"and\" or \"or\".,",
"type": "string"
},
"filter_include_nulls": {
"description": "If set to true, the result will also include null values",
"type": "boolean"
},
"filter_value": {
"description": "The value of the field name to filter by",
"type": "array",
"items": {
"type": "string"
}
"allOf": [
{
"$ref": "#/definitions/user.User"
}
]
},
"id": {
"description": "The unique, numeric id of this bucket.",
@ -7766,24 +7634,10 @@
"description": "The list this bucket belongs to.",
"type": "integer"
},
"order_by": {
"description": "The query parameter to order the items by. This can be either asc or desc, with asc being the default.",
"type": "array",
"items": {
"type": "string"
}
},
"position": {
"description": "The position this bucket has when querying all buckets. See the tasks.position property on how to use this.",
"type": "number"
},
"sort_by": {
"description": "The query parameter to sort by. This is for ex. done, priority, etc.",
"type": "array",
"items": {
"type": "string"
}
},
"tasks": {
"description": "All tasks which belong to this bucket.",
"type": "array",
@ -7799,9 +7653,7 @@
"updated": {
"description": "A timestamp when this bucket was last updated. You cannot change this value.",
"type": "string"
},
"web.CRUDable": {},
"web.Rights": {}
}
}
},
"models.BulkAssignees": {
@ -7813,9 +7665,7 @@
"items": {
"$ref": "#/definitions/user.User"
}
},
"web.CRUDable": {},
"web.Rights": {}
}
}
},
"models.BulkTask": {
@ -7849,7 +7699,11 @@
},
"created_by": {
"description": "The user who initially created the task.",
"$ref": "#/definitions/user.User"
"allOf": [
{
"$ref": "#/definitions/user.User"
}
]
},
"description": {
"description": "The task description.",
@ -7921,7 +7775,11 @@
},
"related_tasks": {
"description": "All related tasks, grouped by their relation kind",
"$ref": "#/definitions/models.RelatedTaskMap"
"allOf": [
{
"$ref": "#/definitions/models.RelatedTaskMap"
}
]
},
"reminder_dates": {
"description": "An array of datetimes when the user wants to be reminded of the task.",
@ -7936,7 +7794,11 @@
},
"repeat_mode": {
"description": "Can have three possible values which will trigger when the task is marked as done: 0 = repeats after the amount specified in repeat_after, 1 = repeats all dates each months (ignoring repeat_after), 3 = repeats from the current date rather than the last set date.",
"type": "integer"
"allOf": [
{
"$ref": "#/definitions/models.TaskRepeatMode"
}
]
},
"start_date": {
"description": "When this task starts.",
@ -7944,7 +7806,11 @@
},
"subscription": {
"description": "The subscription status for the user reading this task. You can only read this property, use the subscription endpoints to modify it.\nWill only returned when retreiving one task.",
"$ref": "#/definitions/models.Subscription"
"allOf": [
{
"$ref": "#/definitions/models.Subscription"
}
]
},
"task_ids": {
"description": "A list of task ids to update",
@ -7961,9 +7827,7 @@
"updated": {
"description": "A timestamp when this task was last updated. You cannot change this value.",
"type": "string"
},
"web.CRUDable": {},
"web.Rights": {}
}
}
},
"models.DatabaseNotifications": {
@ -7991,9 +7855,7 @@
"read_at": {
"description": "When this notification is marked as read, this will be updated with the current timestamp.",
"type": "string"
},
"web.CRUDable": {},
"web.Rights": {}
}
}
},
"models.Label": {
@ -8005,7 +7867,11 @@
},
"created_by": {
"description": "The user who created this label",
"$ref": "#/definitions/user.User"
"allOf": [
{
"$ref": "#/definitions/user.User"
}
]
},
"description": {
"description": "The label description.",
@ -8029,9 +7895,7 @@
"updated": {
"description": "A timestamp when this label was last updated. You cannot change this value.",
"type": "string"
},
"web.CRUDable": {},
"web.Rights": {}
}
}
},
"models.LabelTask": {
@ -8044,9 +7908,7 @@
"label_id": {
"description": "The label id you want to associate with a task.",
"type": "integer"
},
"web.CRUDable": {},
"web.Rights": {}
}
}
},
"models.LabelTaskBulk": {
@ -8058,9 +7920,7 @@
"items": {
"$ref": "#/definitions/models.Label"
}
},
"web.CRUDable": {},
"web.Rights": {}
}
}
},
"models.LinkSharing": {
@ -8088,26 +7948,36 @@
},
"right": {
"description": "The right this list is shared with. 0 = Read only, 1 = Read \u0026 Write, 2 = Admin. See the docs for more details.",
"type": "integer",
"default": 0,
"maximum": 2
"maximum": 2,
"allOf": [
{
"$ref": "#/definitions/models.Right"
}
]
},
"shared_by": {
"description": "The user who shared this list",
"$ref": "#/definitions/user.User"
"allOf": [
{
"$ref": "#/definitions/user.User"
}
]
},
"sharing_type": {
"description": "The kind of this link. 0 = undefined, 1 = without password, 2 = with password.",
"type": "integer",
"default": 0,
"maximum": 2
"maximum": 2,
"allOf": [
{
"$ref": "#/definitions/models.SharingType"
}
]
},
"updated": {
"description": "A timestamp when this share was last updated. You cannot change this value.",
"type": "string"
},
"web.CRUDable": {},
"web.Rights": {}
}
}
},
"models.List": {
@ -8156,7 +8026,11 @@
},
"owner": {
"description": "The user who created this list.",
"$ref": "#/definitions/user.User"
"allOf": [
{
"$ref": "#/definitions/user.User"
}
]
},
"position": {
"description": "The position this list has when querying all lists. See the tasks.position property on how to use this.",
@ -8164,7 +8038,11 @@
},
"subscription": {
"description": "The subscription status for the user reading this list. You can only read this property, use the subscription endpoints to modify it.\nWill only returned when retreiving one list.",
"$ref": "#/definitions/models.Subscription"
"allOf": [
{
"$ref": "#/definitions/models.Subscription"
}
]
},
"title": {
"description": "The title of the list. You'll see this in the namespace overview.",
@ -8175,9 +8053,7 @@
"updated": {
"description": "A timestamp when this list was last updated. You cannot change this value.",
"type": "string"
},
"web.CRUDable": {},
"web.Rights": {}
}
}
},
"models.ListDuplicate": {
@ -8185,14 +8061,16 @@
"properties": {
"list": {
"description": "The copied list",
"$ref": "#/definitions/models.List"
"allOf": [
{
"$ref": "#/definitions/models.List"
}
]
},
"namespace_id": {
"description": "The target namespace ID",
"type": "integer"
},
"web.CRUDable": {},
"web.Rights": {}
}
}
},
"models.ListUser": {
@ -8208,9 +8086,13 @@
},
"right": {
"description": "The right this user has. 0 = Read only, 1 = Read \u0026 Write, 2 = Admin. See the docs for more details.",
"type": "integer",
"default": 0,
"maximum": 2
"maximum": 2,
"allOf": [
{
"$ref": "#/definitions/models.Right"
}
]
},
"updated": {
"description": "A timestamp when this relation was last updated. You cannot change this value.",
@ -8219,9 +8101,7 @@
"user_id": {
"description": "The username.",
"type": "string"
},
"web.CRUDable": {},
"web.Rights": {}
}
}
},
"models.Message": {
@ -8259,11 +8139,19 @@
},
"owner": {
"description": "The user who owns this namespace",
"$ref": "#/definitions/user.User"
"allOf": [
{
"$ref": "#/definitions/user.User"
}
]
},
"subscription": {
"description": "The subscription status for the user reading this namespace. You can only read this property, use the subscription endpoints to modify it.\nWill only returned when retreiving one namespace.",
"$ref": "#/definitions/models.Subscription"
"allOf": [
{
"$ref": "#/definitions/models.Subscription"
}
]
},
"title": {
"description": "The name of this namespace.",
@ -8274,9 +8162,7 @@
"updated": {
"description": "A timestamp when this namespace was last updated. You cannot change this value.",
"type": "string"
},
"web.CRUDable": {},
"web.Rights": {}
}
}
},
"models.NamespaceUser": {
@ -8292,9 +8178,13 @@
},
"right": {
"description": "The right this user has. 0 = Read only, 1 = Read \u0026 Write, 2 = Admin. See the docs for more details.",
"type": "integer",
"default": 0,
"maximum": 2
"maximum": 2,
"allOf": [
{
"$ref": "#/definitions/models.Right"
}
]
},
"updated": {
"description": "A timestamp when this relation was last updated. You cannot change this value.",
@ -8303,9 +8193,7 @@
"user_id": {
"description": "The username.",
"type": "string"
},
"web.CRUDable": {},
"web.Rights": {}
}
}
},
"models.NamespaceWithLists": {
@ -8340,11 +8228,19 @@
},
"owner": {
"description": "The user who owns this namespace",
"$ref": "#/definitions/user.User"
"allOf": [
{
"$ref": "#/definitions/user.User"
}
]
},
"subscription": {
"description": "The subscription status for the user reading this namespace. You can only read this property, use the subscription endpoints to modify it.\nWill only returned when retreiving one namespace.",
"$ref": "#/definitions/models.Subscription"
"allOf": [
{
"$ref": "#/definitions/models.Subscription"
}
]
},
"title": {
"description": "The name of this namespace.",
@ -8355,9 +8251,7 @@
"updated": {
"description": "A timestamp when this namespace was last updated. You cannot change this value.",
"type": "string"
},
"web.CRUDable": {},
"web.Rights": {}
}
}
},
"models.RelatedTaskMap": {
@ -8369,6 +8263,50 @@
}
}
},
"models.RelationKind": {
"type": "string",
"enum": [
"unknown",
"subtask",
"parenttask",
"related",
"duplicateof",
"duplicates",
"blocking",
"blocked",
"precedes",
"follows",
"copiedfrom",
"copiedto"
],
"x-enum-varnames": [
"RelationKindUnknown",
"RelationKindSubtask",
"RelationKindParenttask",
"RelationKindRelated",
"RelationKindDuplicateOf",
"RelationKindDuplicates",
"RelationKindBlocking",
"RelationKindBlocked",
"RelationKindPreceeds",
"RelationKindFollows",
"RelationKindCopiedFrom",
"RelationKindCopiedTo"
]
},
"models.Right": {
"type": "integer",
"enum": [
0,
1,
2
],
"x-enum-varnames": [
"RightRead",
"RightWrite",
"RightAdmin"
]
},
"models.SavedFilter": {
"type": "object",
"properties": {
@ -8382,7 +8320,11 @@
},
"filters": {
"description": "The actual filters this filter contains",
"$ref": "#/definitions/models.TaskCollection"
"allOf": [
{
"$ref": "#/definitions/models.TaskCollection"
}
]
},
"id": {
"description": "The unique numeric id of this saved filter",
@ -8394,7 +8336,11 @@
},
"owner": {
"description": "The user who owns this filter",
"$ref": "#/definitions/user.User"
"allOf": [
{
"$ref": "#/definitions/user.User"
}
]
},
"title": {
"description": "The title of the filter.",
@ -8405,11 +8351,22 @@
"updated": {
"description": "A timestamp when this filter was last updated. You cannot change this value.",
"type": "string"
},
"web.CRUDable": {},
"web.Rights": {}
}
}
},
"models.SharingType": {
"type": "integer",
"enum": [
0,
1,
2
],
"x-enum-varnames": [
"SharingTypeUnknown",
"SharingTypeWithoutPassword",
"SharingTypeWithPassword"
]
},
"models.Subscription": {
"type": "object",
"properties": {
@ -8430,10 +8387,12 @@
},
"user": {
"description": "The user who made this subscription",
"$ref": "#/definitions/user.User"
},
"web.CRUDable": {},
"web.Rights": {}
"allOf": [
{
"$ref": "#/definitions/user.User"
}
]
}
}
},
"models.Task": {
@ -8467,7 +8426,11 @@
},
"created_by": {
"description": "The user who initially created the task.",
"$ref": "#/definitions/user.User"
"allOf": [
{
"$ref": "#/definitions/user.User"
}
]
},
"description": {
"description": "The task description.",
@ -8539,7 +8502,11 @@
},
"related_tasks": {
"description": "All related tasks, grouped by their relation kind",
"$ref": "#/definitions/models.RelatedTaskMap"
"allOf": [
{
"$ref": "#/definitions/models.RelatedTaskMap"
}
]
},
"reminder_dates": {
"description": "An array of datetimes when the user wants to be reminded of the task.",
@ -8554,7 +8521,11 @@
},
"repeat_mode": {
"description": "Can have three possible values which will trigger when the task is marked as done: 0 = repeats after the amount specified in repeat_after, 1 = repeats all dates each months (ignoring repeat_after), 3 = repeats from the current date rather than the last set date.",
"type": "integer"
"allOf": [
{
"$ref": "#/definitions/models.TaskRepeatMode"
}
]
},
"start_date": {
"description": "When this task starts.",
@ -8562,7 +8533,11 @@
},
"subscription": {
"description": "The subscription status for the user reading this task. You can only read this property, use the subscription endpoints to modify it.\nWill only returned when retreiving one task.",
"$ref": "#/definitions/models.Subscription"
"allOf": [
{
"$ref": "#/definitions/models.Subscription"
}
]
},
"title": {
"description": "The task text. This is what you'll see in the list.",
@ -8572,9 +8547,7 @@
"updated": {
"description": "A timestamp when this task was last updated. You cannot change this value.",
"type": "string"
},
"web.CRUDable": {},
"web.Rights": {}
}
}
},
"models.TaskAssginee": {
@ -8585,9 +8558,7 @@
},
"user_id": {
"type": "integer"
},
"web.CRUDable": {},
"web.Rights": {}
}
}
},
"models.TaskAttachment": {
@ -8607,9 +8578,7 @@
},
"task_id": {
"type": "integer"
},
"web.CRUDable": {},
"web.Rights": {}
}
}
},
"models.TaskCollection": {
@ -8657,9 +8626,7 @@
"items": {
"type": "string"
}
},
"web.CRUDable": {},
"web.Rights": {}
}
}
},
"models.TaskComment": {
@ -8679,9 +8646,7 @@
},
"updated": {
"type": "string"
},
"web.CRUDable": {},
"web.Rights": {}
}
}
},
"models.TaskRelation": {
@ -8693,7 +8658,11 @@
},
"created_by": {
"description": "The user who created this relation",
"$ref": "#/definitions/user.User"
"allOf": [
{
"$ref": "#/definitions/user.User"
}
]
},
"other_task_id": {
"description": "The ID of the other task, the task which is being related.",
@ -8701,16 +8670,31 @@
},
"relation_kind": {
"description": "The kind of the relation.",
"type": "string"
"allOf": [
{
"$ref": "#/definitions/models.RelationKind"
}
]
},
"task_id": {
"description": "The ID of the \"base\" task, the task which has a relation to another.",
"type": "integer"
},
"web.CRUDable": {},
"web.Rights": {}
}
}
},
"models.TaskRepeatMode": {
"type": "integer",
"enum": [
0,
1,
2
],
"x-enum-varnames": [
"TaskRepeatModeDefault",
"TaskRepeatModeMonth",
"TaskRepeatModeFromCurrentDate"
]
},
"models.Team": {
"type": "object",
"properties": {
@ -8720,7 +8704,11 @@
},
"created_by": {
"description": "The user who created this team.",
"$ref": "#/definitions/user.User"
"allOf": [
{
"$ref": "#/definitions/user.User"
}
]
},
"description": {
"description": "The team's description.",
@ -8746,9 +8734,7 @@
"updated": {
"description": "A timestamp when this relation was last updated. You cannot change this value.",
"type": "string"
},
"web.CRUDable": {},
"web.Rights": {}
}
}
},
"models.TeamList": {
@ -8764,9 +8750,13 @@
},
"right": {
"description": "The right this team has. 0 = Read only, 1 = Read \u0026 Write, 2 = Admin. See the docs for more details.",
"type": "integer",
"default": 0,
"maximum": 2
"maximum": 2,
"allOf": [
{
"$ref": "#/definitions/models.Right"
}
]
},
"team_id": {
"description": "The team id.",
@ -8775,9 +8765,7 @@
"updated": {
"description": "A timestamp when this relation was last updated. You cannot change this value.",
"type": "string"
},
"web.CRUDable": {},
"web.Rights": {}
}
}
},
"models.TeamMember": {
@ -8798,9 +8786,7 @@
"username": {
"description": "The username of the member. We use this to prevent automated user id entering.",
"type": "string"
},
"web.CRUDable": {},
"web.Rights": {}
}
}
},
"models.TeamNamespace": {
@ -8816,9 +8802,13 @@
},
"right": {
"description": "The right this team has. 0 = Read only, 1 = Read \u0026 Write, 2 = Admin. See the docs for more details.",
"type": "integer",
"default": 0,
"maximum": 2
"maximum": 2,
"allOf": [
{
"$ref": "#/definitions/models.Right"
}
]
},
"team_id": {
"description": "The team id.",
@ -8827,9 +8817,7 @@
"updated": {
"description": "A timestamp when this relation was last updated. You cannot change this value.",
"type": "string"
},
"web.CRUDable": {},
"web.Rights": {}
}
}
},
"models.TeamUser": {
@ -8865,8 +8853,7 @@
"type": "string",
"maxLength": 250,
"minLength": 1
},
"web.Auth": {}
}
}
},
"models.TeamWithRight": {
@ -8878,7 +8865,11 @@
},
"created_by": {
"description": "The user who created this team.",
"$ref": "#/definitions/user.User"
"allOf": [
{
"$ref": "#/definitions/user.User"
}
]
},
"description": {
"description": "The team's description.",
@ -8902,16 +8893,12 @@
"minLength": 1
},
"right": {
"description": "The right this team has. 0 = Read only, 1 = Read \u0026 Write, 2 = Admin. See the docs for more details.",
"type": "integer",
"default": 0
"$ref": "#/definitions/models.Right"
},
"updated": {
"description": "A timestamp when this relation was last updated. You cannot change this value.",
"type": "string"
},
"web.CRUDable": {},
"web.Rights": {}
}
}
},
"models.UserWithRight": {
@ -8935,9 +8922,7 @@
"type": "string"
},
"right": {
"description": "The right this user has. 0 = Read only, 1 = Read \u0026 Write, 2 = Admin. See the docs for more details.",
"type": "integer",
"default": 0
"$ref": "#/definitions/models.Right"
},
"updated": {
"description": "A timestamp when this task was last updated. You cannot change this value.",
@ -8948,8 +8933,7 @@
"type": "string",
"maxLength": 250,
"minLength": 1
},
"web.Auth": {}
}
}
},
"notifications.DatabaseNotification": {
@ -9178,8 +9162,7 @@
"type": "string",
"maxLength": 250,
"minLength": 1
},
"web.Auth": {}
}
}
},
"v1.LinkShareAuth": {
@ -9387,15 +9370,6 @@
"type": "string"
}
}
},
"wunderlist.Migration": {
"type": "object",
"properties": {
"code": {
"description": "Code is the code used to get a user api token",
"type": "string"
}
}
}
},
"securityDefinitions": {

View File

@ -58,30 +58,9 @@ definitions:
value.
type: string
created_by:
$ref: '#/definitions/user.User'
allOf:
- $ref: '#/definitions/user.User'
description: The user who initially created the bucket.
filter_by:
description: The field name of the field to filter by
items:
type: string
type: array
filter_comparator:
description: The comparator for field and value
items:
type: string
type: array
filter_concat:
description: The way all filter conditions are concatenated together, can
be either "and" or "or".,
type: string
filter_include_nulls:
description: If set to true, the result will also include null values
type: boolean
filter_value:
description: The value of the field name to filter by
items:
type: string
type: array
id:
description: The unique, numeric id of this bucket.
type: integer
@ -97,22 +76,10 @@ definitions:
list_id:
description: The list this bucket belongs to.
type: integer
order_by:
description: The query parameter to order the items by. This can be either
asc or desc, with asc being the default.
items:
type: string
type: array
position:
description: The position this bucket has when querying all buckets. See the
tasks.position property on how to use this.
type: number
sort_by:
description: The query parameter to sort by. This is for ex. done, priority,
etc.
items:
type: string
type: array
tasks:
description: All tasks which belong to this bucket.
items:
@ -126,8 +93,6 @@ definitions:
description: A timestamp when this bucket was last updated. You cannot change
this value.
type: string
web.CRUDable: {}
web.Rights: {}
type: object
models.BulkAssignees:
properties:
@ -136,8 +101,6 @@ definitions:
items:
$ref: '#/definitions/user.User'
type: array
web.CRUDable: {}
web.Rights: {}
type: object
models.BulkTask:
properties:
@ -163,7 +126,8 @@ definitions:
value.
type: string
created_by:
$ref: '#/definitions/user.User'
allOf:
- $ref: '#/definitions/user.User'
description: The user who initially created the task.
description:
description: The task description.
@ -228,7 +192,8 @@ definitions:
sort by this later.
type: integer
related_tasks:
$ref: '#/definitions/models.RelatedTaskMap'
allOf:
- $ref: '#/definitions/models.RelatedTaskMap'
description: All related tasks, grouped by their relation kind
reminder_dates:
description: An array of datetimes when the user wants to be reminded of the
@ -242,16 +207,18 @@ definitions:
increase all remindes and the due date by its amount.
type: integer
repeat_mode:
allOf:
- $ref: '#/definitions/models.TaskRepeatMode'
description: 'Can have three possible values which will trigger when the task
is marked as done: 0 = repeats after the amount specified in repeat_after,
1 = repeats all dates each months (ignoring repeat_after), 3 = repeats from
the current date rather than the last set date.'
type: integer
start_date:
description: When this task starts.
type: string
subscription:
$ref: '#/definitions/models.Subscription'
allOf:
- $ref: '#/definitions/models.Subscription'
description: |-
The subscription status for the user reading this task. You can only read this property, use the subscription endpoints to modify it.
Will only returned when retreiving one task.
@ -268,8 +235,6 @@ definitions:
description: A timestamp when this task was last updated. You cannot change
this value.
type: string
web.CRUDable: {}
web.Rights: {}
type: object
models.DatabaseNotifications:
properties:
@ -294,8 +259,6 @@ definitions:
description: When this notification is marked as read, this will be updated
with the current timestamp.
type: string
web.CRUDable: {}
web.Rights: {}
type: object
models.Label:
properties:
@ -304,7 +267,8 @@ definitions:
value.
type: string
created_by:
$ref: '#/definitions/user.User'
allOf:
- $ref: '#/definitions/user.User'
description: The user who created this label
description:
description: The label description.
@ -326,8 +290,6 @@ definitions:
description: A timestamp when this label was last updated. You cannot change
this value.
type: string
web.CRUDable: {}
web.Rights: {}
type: object
models.LabelTask:
properties:
@ -338,8 +300,6 @@ definitions:
label_id:
description: The label id you want to associate with a task.
type: integer
web.CRUDable: {}
web.Rights: {}
type: object
models.LabelTaskBulk:
properties:
@ -348,8 +308,6 @@ definitions:
items:
$ref: '#/definitions/models.Label'
type: array
web.CRUDable: {}
web.Rights: {}
type: object
models.LinkSharing:
properties:
@ -372,26 +330,27 @@ definitions:
it after the link share has been created.
type: string
right:
allOf:
- $ref: '#/definitions/models.Right'
default: 0
description: The right this list is shared with. 0 = Read only, 1 = Read &
Write, 2 = Admin. See the docs for more details.
maximum: 2
type: integer
shared_by:
$ref: '#/definitions/user.User'
allOf:
- $ref: '#/definitions/user.User'
description: The user who shared this list
sharing_type:
allOf:
- $ref: '#/definitions/models.SharingType'
default: 0
description: The kind of this link. 0 = undefined, 1 = without password, 2
= with password.
maximum: 2
type: integer
updated:
description: A timestamp when this share was last updated. You cannot change
this value.
type: string
web.CRUDable: {}
web.Rights: {}
type: object
models.List:
properties:
@ -433,14 +392,16 @@ definitions:
namespace_id:
type: integer
owner:
$ref: '#/definitions/user.User'
allOf:
- $ref: '#/definitions/user.User'
description: The user who created this list.
position:
description: The position this list has when querying all lists. See the tasks.position
property on how to use this.
type: number
subscription:
$ref: '#/definitions/models.Subscription'
allOf:
- $ref: '#/definitions/models.Subscription'
description: |-
The subscription status for the user reading this list. You can only read this property, use the subscription endpoints to modify it.
Will only returned when retreiving one list.
@ -453,19 +414,16 @@ definitions:
description: A timestamp when this list was last updated. You cannot change
this value.
type: string
web.CRUDable: {}
web.Rights: {}
type: object
models.ListDuplicate:
properties:
list:
$ref: '#/definitions/models.List'
allOf:
- $ref: '#/definitions/models.List'
description: The copied list
namespace_id:
description: The target namespace ID
type: integer
web.CRUDable: {}
web.Rights: {}
type: object
models.ListUser:
properties:
@ -477,11 +435,12 @@ definitions:
description: The unique, numeric id of this list <-> user relation.
type: integer
right:
allOf:
- $ref: '#/definitions/models.Right'
default: 0
description: The right this user has. 0 = Read only, 1 = Read & Write, 2 =
Admin. See the docs for more details.
maximum: 2
type: integer
updated:
description: A timestamp when this relation was last updated. You cannot change
this value.
@ -489,8 +448,6 @@ definitions:
user_id:
description: The username.
type: string
web.CRUDable: {}
web.Rights: {}
type: object
models.Message:
properties:
@ -518,10 +475,12 @@ definitions:
description: Whether or not a namespace is archived.
type: boolean
owner:
$ref: '#/definitions/user.User'
allOf:
- $ref: '#/definitions/user.User'
description: The user who owns this namespace
subscription:
$ref: '#/definitions/models.Subscription'
allOf:
- $ref: '#/definitions/models.Subscription'
description: |-
The subscription status for the user reading this namespace. You can only read this property, use the subscription endpoints to modify it.
Will only returned when retreiving one namespace.
@ -534,8 +493,6 @@ definitions:
description: A timestamp when this namespace was last updated. You cannot
change this value.
type: string
web.CRUDable: {}
web.Rights: {}
type: object
models.NamespaceUser:
properties:
@ -547,11 +504,12 @@ definitions:
description: The unique, numeric id of this namespace <-> user relation.
type: integer
right:
allOf:
- $ref: '#/definitions/models.Right'
default: 0
description: The right this user has. 0 = Read only, 1 = Read & Write, 2 =
Admin. See the docs for more details.
maximum: 2
type: integer
updated:
description: A timestamp when this relation was last updated. You cannot change
this value.
@ -559,8 +517,6 @@ definitions:
user_id:
description: The username.
type: string
web.CRUDable: {}
web.Rights: {}
type: object
models.NamespaceWithLists:
properties:
@ -586,10 +542,12 @@ definitions:
$ref: '#/definitions/models.List'
type: array
owner:
$ref: '#/definitions/user.User'
allOf:
- $ref: '#/definitions/user.User'
description: The user who owns this namespace
subscription:
$ref: '#/definitions/models.Subscription'
allOf:
- $ref: '#/definitions/models.Subscription'
description: |-
The subscription status for the user reading this namespace. You can only read this property, use the subscription endpoints to modify it.
Will only returned when retreiving one namespace.
@ -602,8 +560,6 @@ definitions:
description: A timestamp when this namespace was last updated. You cannot
change this value.
type: string
web.CRUDable: {}
web.Rights: {}
type: object
models.RelatedTaskMap:
additionalProperties:
@ -611,6 +567,44 @@ definitions:
$ref: '#/definitions/models.Task'
type: array
type: object
models.RelationKind:
enum:
- unknown
- subtask
- parenttask
- related
- duplicateof
- duplicates
- blocking
- blocked
- precedes
- follows
- copiedfrom
- copiedto
type: string
x-enum-varnames:
- RelationKindUnknown
- RelationKindSubtask
- RelationKindParenttask
- RelationKindRelated
- RelationKindDuplicateOf
- RelationKindDuplicates
- RelationKindBlocking
- RelationKindBlocked
- RelationKindPreceeds
- RelationKindFollows
- RelationKindCopiedFrom
- RelationKindCopiedTo
models.Right:
enum:
- 0
- 1
- 2
type: integer
x-enum-varnames:
- RightRead
- RightWrite
- RightAdmin
models.SavedFilter:
properties:
created:
@ -621,7 +615,8 @@ definitions:
description: The description of the filter
type: string
filters:
$ref: '#/definitions/models.TaskCollection'
allOf:
- $ref: '#/definitions/models.TaskCollection'
description: The actual filters this filter contains
id:
description: The unique numeric id of this saved filter
@ -631,7 +626,8 @@ definitions:
a separate namespace together with favorite lists.
type: boolean
owner:
$ref: '#/definitions/user.User'
allOf:
- $ref: '#/definitions/user.User'
description: The user who owns this filter
title:
description: The title of the filter.
@ -642,9 +638,17 @@ definitions:
description: A timestamp when this filter was last updated. You cannot change
this value.
type: string
web.CRUDable: {}
web.Rights: {}
type: object
models.SharingType:
enum:
- 0
- 1
- 2
type: integer
x-enum-varnames:
- SharingTypeUnknown
- SharingTypeWithoutPassword
- SharingTypeWithPassword
models.Subscription:
properties:
created:
@ -660,10 +664,9 @@ definitions:
description: The numeric ID of the subscription
type: integer
user:
$ref: '#/definitions/user.User'
allOf:
- $ref: '#/definitions/user.User'
description: The user who made this subscription
web.CRUDable: {}
web.Rights: {}
type: object
models.Task:
properties:
@ -689,7 +692,8 @@ definitions:
value.
type: string
created_by:
$ref: '#/definitions/user.User'
allOf:
- $ref: '#/definitions/user.User'
description: The user who initially created the task.
description:
description: The task description.
@ -754,7 +758,8 @@ definitions:
sort by this later.
type: integer
related_tasks:
$ref: '#/definitions/models.RelatedTaskMap'
allOf:
- $ref: '#/definitions/models.RelatedTaskMap'
description: All related tasks, grouped by their relation kind
reminder_dates:
description: An array of datetimes when the user wants to be reminded of the
@ -768,16 +773,18 @@ definitions:
increase all remindes and the due date by its amount.
type: integer
repeat_mode:
allOf:
- $ref: '#/definitions/models.TaskRepeatMode'
description: 'Can have three possible values which will trigger when the task
is marked as done: 0 = repeats after the amount specified in repeat_after,
1 = repeats all dates each months (ignoring repeat_after), 3 = repeats from
the current date rather than the last set date.'
type: integer
start_date:
description: When this task starts.
type: string
subscription:
$ref: '#/definitions/models.Subscription'
allOf:
- $ref: '#/definitions/models.Subscription'
description: |-
The subscription status for the user reading this task. You can only read this property, use the subscription endpoints to modify it.
Will only returned when retreiving one task.
@ -789,8 +796,6 @@ definitions:
description: A timestamp when this task was last updated. You cannot change
this value.
type: string
web.CRUDable: {}
web.Rights: {}
type: object
models.TaskAssginee:
properties:
@ -798,8 +803,6 @@ definitions:
type: string
user_id:
type: integer
web.CRUDable: {}
web.Rights: {}
type: object
models.TaskAttachment:
properties:
@ -813,8 +816,6 @@ definitions:
type: integer
task_id:
type: integer
web.CRUDable: {}
web.Rights: {}
type: object
models.TaskCollection:
properties:
@ -852,8 +853,6 @@ definitions:
items:
type: string
type: array
web.CRUDable: {}
web.Rights: {}
type: object
models.TaskComment:
properties:
@ -867,8 +866,6 @@ definitions:
type: integer
updated:
type: string
web.CRUDable: {}
web.Rights: {}
type: object
models.TaskRelation:
properties:
@ -877,20 +874,30 @@ definitions:
value.
type: string
created_by:
$ref: '#/definitions/user.User'
allOf:
- $ref: '#/definitions/user.User'
description: The user who created this relation
other_task_id:
description: The ID of the other task, the task which is being related.
type: integer
relation_kind:
allOf:
- $ref: '#/definitions/models.RelationKind'
description: The kind of the relation.
type: string
task_id:
description: The ID of the "base" task, the task which has a relation to another.
type: integer
web.CRUDable: {}
web.Rights: {}
type: object
models.TaskRepeatMode:
enum:
- 0
- 1
- 2
type: integer
x-enum-varnames:
- TaskRepeatModeDefault
- TaskRepeatModeMonth
- TaskRepeatModeFromCurrentDate
models.Team:
properties:
created:
@ -898,7 +905,8 @@ definitions:
this value.
type: string
created_by:
$ref: '#/definitions/user.User'
allOf:
- $ref: '#/definitions/user.User'
description: The user who created this team.
description:
description: The team's description.
@ -920,8 +928,6 @@ definitions:
description: A timestamp when this relation was last updated. You cannot change
this value.
type: string
web.CRUDable: {}
web.Rights: {}
type: object
models.TeamList:
properties:
@ -933,11 +939,12 @@ definitions:
description: The unique, numeric id of this list <-> team relation.
type: integer
right:
allOf:
- $ref: '#/definitions/models.Right'
default: 0
description: The right this team has. 0 = Read only, 1 = Read & Write, 2 =
Admin. See the docs for more details.
maximum: 2
type: integer
team_id:
description: The team id.
type: integer
@ -945,8 +952,6 @@ definitions:
description: A timestamp when this relation was last updated. You cannot change
this value.
type: string
web.CRUDable: {}
web.Rights: {}
type: object
models.TeamMember:
properties:
@ -965,8 +970,6 @@ definitions:
description: The username of the member. We use this to prevent automated
user id entering.
type: string
web.CRUDable: {}
web.Rights: {}
type: object
models.TeamNamespace:
properties:
@ -978,11 +981,12 @@ definitions:
description: The unique, numeric id of this namespace <-> team relation.
type: integer
right:
allOf:
- $ref: '#/definitions/models.Right'
default: 0
description: The right this team has. 0 = Read only, 1 = Read & Write, 2 =
Admin. See the docs for more details.
maximum: 2
type: integer
team_id:
description: The team id.
type: integer
@ -990,8 +994,6 @@ definitions:
description: A timestamp when this relation was last updated. You cannot change
this value.
type: string
web.CRUDable: {}
web.Rights: {}
type: object
models.TeamUser:
properties:
@ -1022,7 +1024,6 @@ definitions:
maxLength: 250
minLength: 1
type: string
web.Auth: {}
type: object
models.TeamWithRight:
properties:
@ -1031,7 +1032,8 @@ definitions:
this value.
type: string
created_by:
$ref: '#/definitions/user.User'
allOf:
- $ref: '#/definitions/user.User'
description: The user who created this team.
description:
description: The team's description.
@ -1050,16 +1052,11 @@ definitions:
minLength: 1
type: string
right:
default: 0
description: The right this team has. 0 = Read only, 1 = Read & Write, 2 =
Admin. See the docs for more details.
type: integer
$ref: '#/definitions/models.Right'
updated:
description: A timestamp when this relation was last updated. You cannot change
this value.
type: string
web.CRUDable: {}
web.Rights: {}
type: object
models.UserWithRight:
properties:
@ -1078,10 +1075,7 @@ definitions:
description: The full name of the user.
type: string
right:
default: 0
description: The right this user has. 0 = Read only, 1 = Read & Write, 2 =
Admin. See the docs for more details.
type: integer
$ref: '#/definitions/models.Right'
updated:
description: A timestamp when this task was last updated. You cannot change
this value.
@ -1091,7 +1085,6 @@ definitions:
maxLength: 250
minLength: 1
type: string
web.Auth: {}
type: object
notifications.DatabaseNotification:
properties:
@ -1258,7 +1251,6 @@ definitions:
maxLength: 250
minLength: 1
type: string
web.Auth: {}
type: object
v1.LinkShareAuth:
properties:
@ -1407,12 +1399,6 @@ definitions:
message:
type: string
type: object
wunderlist.Migration:
properties:
code:
description: Code is the code used to get a user api token
type: string
type: object
info:
contact:
email: hello@vikunja.io
@ -1509,7 +1495,7 @@ paths:
parameters:
- description: Unsplash Image ID
in: path
name: thumb
name: image
required: true
type: integer
produces:
@ -1539,7 +1525,7 @@ paths:
parameters:
- description: Unsplash Image ID
in: path
name: thumb
name: image
required: true
type: integer
produces:
@ -3490,77 +3476,6 @@ paths:
summary: Get migration status
tags:
- migration
/migration/wunderlist/auth:
get:
description: Returns the auth url where the user needs to get its auth code.
This code can then be used to migrate everything from wunderlist to Vikunja.
produces:
- application/json
responses:
"200":
description: The auth url.
schema:
$ref: '#/definitions/handler.AuthURL'
"500":
description: Internal server error
schema:
$ref: '#/definitions/models.Message'
security:
- JWTKeyAuth: []
summary: Get the auth url from wunderlist
tags:
- migration
/migration/wunderlist/migrate:
post:
consumes:
- application/json
description: Migrates all folders, lists, tasks, notes, reminders, subtasks
and files from wunderlist to vikunja.
parameters:
- description: The auth code previously obtained from the auth url. See the
docs for /migration/wunderlist/auth.
in: body
name: migrationCode
required: true
schema:
$ref: '#/definitions/wunderlist.Migration'
produces:
- application/json
responses:
"200":
description: A message telling you everything was migrated successfully.
schema:
$ref: '#/definitions/models.Message'
"500":
description: Internal server error
schema:
$ref: '#/definitions/models.Message'
security:
- JWTKeyAuth: []
summary: Migrate all lists, tasks etc. from wunderlist
tags:
- migration
/migration/wunderlist/status:
get:
description: Returns if the current user already did the migation or not. This
is useful to show a confirmation message in the frontend if the user is trying
to do the same migration again.
produces:
- application/json
responses:
"200":
description: The migration status
schema:
$ref: '#/definitions/migration.Status'
"500":
description: Internal server error
schema:
$ref: '#/definitions/models.Message'
security:
- JWTKeyAuth: []
summary: Get migration status
tags:
- migration
/namespace/{id}:
post:
consumes:
@ -5247,7 +5162,7 @@ paths:
type: string
- description: The id of the other task.
in: path
name: otherTaskID
name: otherTaskId
required: true
type: integer
produces:

View File

@ -419,7 +419,7 @@ func GetUserFromClaims(claims jwt.MapClaims) (user *User, err error) {
}
// UpdateUser updates a user
func UpdateUser(s *xorm.Session, user *User) (updatedUser *User, err error) {
func UpdateUser(s *xorm.Session, user *User, forceOverride bool) (updatedUser *User, err error) {
// Check if it exists
theUser, err := GetUserWithEmail(s, &User{ID: user.ID})
@ -442,7 +442,7 @@ func UpdateUser(s *xorm.Session, user *User) (updatedUser *User, err error) {
}
// Check if we have a name
if user.Name == "" {
if user.Name == "" && !forceOverride {
user.Name = theUser.Name
}

View File

@ -292,7 +292,7 @@ func TestUpdateUser(t *testing.T) {
ID: 1,
Password: "LoremIpsum",
Email: "testing@example.com",
})
}, false)
assert.NoError(t, err)
assert.Equal(t, "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", uuser.Password) // Password should not change
assert.Equal(t, "user1", uuser.Username) // Username should not change either
@ -305,7 +305,7 @@ func TestUpdateUser(t *testing.T) {
uuser, err := UpdateUser(s, &User{
ID: 1,
Username: "changedname",
})
}, false)
assert.NoError(t, err)
assert.Equal(t, "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", uuser.Password) // Password should not change
assert.Equal(t, "changedname", uuser.Username)
@ -317,7 +317,7 @@ func TestUpdateUser(t *testing.T) {
_, err := UpdateUser(s, &User{
ID: 99999,
})
}, false)
assert.Error(t, err)
assert.True(t, IsErrUserDoesNotExist(err))
})