Compare commits

..

114 Commits

Author SHA1 Message Date
renovate b693983789 chore(deps): update alpine docker tag to v3.15
continuous-integration/drone/pr Build is passing Details
2021-11-19 01:00:52 +00:00
renovate b0622732a1 fix(deps): update golang.org/x/sys commit hash to dee7805 (#1051)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#1051
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-17 19:13:59 +00:00
renovate 072b82412b fix(deps): update golang.org/x/crypto commit hash to ae814b3 (#1050)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#1050
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-17 19:13:46 +00:00
renovate ea539cc284 fix(deps): update module github.com/ulule/limiter/v3 to v3.9.0 (#1049)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#1049
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-17 17:57:27 +00:00
Joseph Kavanagh 36bf3d216a
feat: gravatar - Lowercase emails before MD5 hash (#10)
continuous-integration/drone/push Build is passing Details
2021-11-16 22:10:22 +01:00
renovate efd0970971 fix(deps): update golang.org/x/crypto commit hash to b4de73f (#1047)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#1047
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-16 20:54:16 +00:00
renovate 677ca03489 fix(deps): update golang.org/x/sys commit hash to 0a5406a (#1048)
continuous-integration/drone/push Build was killed Details
Reviewed-on: vikunja/api#1048
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-16 20:54:01 +00:00
kolaente 1fa74cba64
docs: add another tutorial link
continuous-integration/drone/push Build is passing Details
2021-11-16 21:17:53 +01:00
kolaente 0b7762590f
fix: lint
continuous-integration/drone/push Build is passing Details
2021-11-16 20:37:37 +01:00
kolaente c3e0e6405a
fix: importing trello attachments
continuous-integration/drone/push Build is failing Details
Since jan 2021, trello requires authentication to access attachments. This commit passes the required auth headers to make downloading card attachments work.

resolves https://github.com/go-vikunja/api/issues/6
2021-11-14 21:47:51 +01:00
kolaente 57e5d10eee
fix: sorting for saved filters
continuous-integration/drone/push Build is passing Details
resolves #786
2021-11-14 21:03:55 +01:00
kolaente 88a2cede19
feat: use wallpaper topic for default unsplash background list
continuous-integration/drone/push Build is passing Details
2021-11-14 20:47:15 +01:00
kolaente 093d0c65ca
feat: enable rate limit for unauthenticated routes
continuous-integration/drone/push Build is passing Details
2021-11-14 20:42:33 +01:00
renovate da2d5e41c7 fix(deps): update module github.com/yuin/goldmark to v1.4.4 (#1046)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#1046
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-14 11:14:35 +00:00
kolaente 9bf32aae99
docs: improve development docs
continuous-integration/drone/push Build is passing Details
2021-11-13 18:20:23 +01:00
kolaente 2aea1691cf
docs: add postgres to docker-compose examples 2021-11-13 18:08:57 +01:00
kolaente 4829c89940
docs: update backup instructions 2021-11-13 18:00:49 +01:00
kolaente cf05de19b3
fix: updating a list might remove its background
continuous-integration/drone/push Build is passing Details
resolves #1039
2021-11-13 17:52:14 +01:00
renovate 2ae1da473e fix(deps): update golang.org/x/sys commit hash to 0c823b9 (#1045)
continuous-integration/drone/push Build is failing Details
Reviewed-on: vikunja/api#1045
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-13 13:31:17 +00:00
renovate 62361d8ad9 fix(deps): update golang.org/x/sys commit hash to f221eed (#1044)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#1044
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-12 06:56:18 +00:00
renovate 98316a04b2 fix(deps): update golang.org/x/sys commit hash to 99a5385 (#1043)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#1043
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-10 17:16:01 +00:00
renovate ae192c1291 fix(deps): update golang.org/x/sys commit hash to 51b60fd (#1042)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#1042
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-09 21:02:29 +00:00
renovate e3210a11df fix(deps): update module github.com/lib/pq to v1.10.4 (#1040)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#1040
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-09 07:14:03 +00:00
renovate 5763aca428 fix(deps): update golang.org/x/crypto commit hash to ceb1ce7 (#1041)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#1041
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-09 07:13:37 +00:00
renovate 609497d08c fix(deps): update module github.com/yuin/goldmark to v1.4.3 (#1038)
continuous-integration/drone/push Build is failing Details
Reviewed-on: vikunja/api#1038
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-08 11:36:38 +00:00
renovate b92c370d6d fix(deps): update golang.org/x/sys commit hash to e0b2ad0 (#1037)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#1037
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-07 12:17:43 +00:00
renovate 0b707369d0 fix(deps): update golang.org/x/sys commit hash to ebca88c (#1035)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#1035
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-06 20:29:05 +00:00
renovate e64f06fabc fix(deps): update golang.org/x/sys commit hash to c75c477 (#1034)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#1034
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-05 21:31:57 +00:00
renovate bcfaa78c1a fix(deps): update module github.com/labstack/gommon to v0.3.1 (#1033)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#1033
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-05 11:03:42 +00:00
renovate 29eb0765f3 fix(deps): update golang.org/x/oauth2 commit hash to d3ed0bb (#1032)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#1032
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-04 21:28:37 +00:00
renovate 473692cf7a fix(deps): update golang.org/x/sys commit hash to 7861aae (#1031)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#1031
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-04 05:50:10 +00:00
renovate b9819de157 fix(deps): update golang.org/x/sys commit hash to ae416a5 (#1030)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#1030
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-03 19:13:48 +00:00
renovate b3e520427b fix(deps): update golang.org/x/sys commit hash to 4dd7244 (#1029)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#1029
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-02 20:31:19 +00:00
renovate cb74337738 fix(deps): update golang.org/x/sys commit hash to a2f17f7 (#1028)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#1028
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-02 12:52:59 +00:00
renovate a03d119a06 fix(deps): update golang.org/x/sys commit hash to 39c9dd3 (#1027)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#1027
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-01 21:33:48 +00:00
kolaente 2683ef23d5
feat: expose if a user is a local user through the /user endpoint
continuous-integration/drone/push Build is passing Details
2021-10-31 13:57:19 +01:00
kolaente 516c812043
feat: expose if a user is a local user through its jwt token
continuous-integration/drone/push Build is passing Details
2021-10-31 12:37:31 +01:00
kolaente 9eca971c93
feat: don't require a password for data export from users authenticated with third-party auth 2021-10-31 12:37:08 +01:00
kolaente cc612d505f
docs: make sure all links to vikunja pages are https
continuous-integration/drone/push Build is passing Details
2021-10-31 11:23:42 +01:00
renovate 53703dd0c4 fix(deps): update golang.org/x/sys commit hash to 611d5d6 (#1026)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#1026
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-31 09:16:18 +00:00
renovate 068ad56d2b fix(deps): update golang.org/x/sys commit hash to b3129d9 (#1025)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#1025
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-30 18:06:39 +00:00
renovate a20cbc99fd fix(deps): update golang.org/x/sys commit hash to 6e78728 (#1024)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#1024
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-29 17:33:07 +00:00
renovate eb47161c64 fix(deps): update golang.org/x/image commit hash to 6944b10 (#1023)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#1023
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-28 21:19:49 +00:00
renovate b194715690 fix(deps): update golang.org/x/oauth2 commit hash to ba495a6 (#1022)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#1022
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-28 20:26:11 +00:00
kolaente 8f55af07c9
feat: add more debug logging when deleting users
continuous-integration/drone/push Build is passing Details
related to #1021
2021-10-27 22:08:31 +02:00
renovate 9869fd694a fix(deps): update golang.org/x/sys commit hash to 69cdffd (#1020)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#1020
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-25 21:15:15 +00:00
renovate ff7270838e fix(deps): update golang.org/x/sys commit hash to 711f33c (#1019)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#1019
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-25 16:19:50 +00:00
renovate 4bdf147c89 fix(deps): update module github.com/swaggo/swag to v1.7.4 (#1018)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#1018
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-25 16:19:21 +00:00
renovate 1443f8aa2d fix(deps): update golang.org/x/sys commit hash to d6a326f (#1017)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#1017
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-23 11:57:43 +00:00
renovate af306e5ad3 fix(deps): update golang.org/x/sys commit hash to 8e51046 (#1016)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#1016
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-23 06:02:24 +00:00
renovate 9fbda1d503 fix(deps): update module github.com/yuin/goldmark to v1.4.2 (#1012)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#1012
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-21 17:11:46 +00:00
renovate 445703e155 fix(deps): update golang.org/x/sys commit hash to 9d61738 (#1011)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#1011
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-20 18:55:56 +00:00
renovate 4c05912633 fix(deps): update golang.org/x/sys commit hash to 0ec99a6 (#1010)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#1010
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-20 07:17:11 +00:00
renovate 8dfbb2c344 fix(deps): update golang.org/x/sys commit hash to 9d821ac (#1009)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#1009
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-19 19:56:31 +00:00
renovate 84fe135f69 fix(deps): update module github.com/mattn/go-sqlite3 to v1.14.9 (#1008)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#1008
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-19 15:52:30 +00:00
renovate 0a6e6dcac1 fix(deps): update module github.com/go-redis/redis/v8 to v8.11.4 (#1003)
continuous-integration/drone/push Build is passing Details
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#1003
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-17 12:56:28 +00:00
jayden-c dcb52c00f1 feat: improve account deletion email grammar (#1006)
continuous-integration/drone/push Build is passing Details
Co-authored-by: Jayden Chan <jaydencn7@gmail.com>
Reviewed-on: vikunja/api#1006
Reviewed-by: konrad <k@knt.li>
Co-authored-by: jayden-c <jaydencn7@gmail.com>
Co-committed-by: jayden-c <jaydencn7@gmail.com>
2021-10-16 21:53:33 +00:00
kolaente 50b65a517d
fix: correctly load and pass the user when deleting it
continuous-integration/drone/push Build is passing Details
Fixes #984
2021-10-16 17:00:48 +02:00
kolaente d7e47a28d4
fix: don't try to load the namespace of a list if it is a shared list
continuous-integration/drone/push Build is passing Details
2021-10-16 16:50:16 +02:00
kolaente c2b6119434
fix: make sure the full task is available in notifications
continuous-integration/drone/push Build is passing Details
2021-10-16 16:38:59 +02:00
renovate f46c1c5d13 chore(deps): update postgres docker tag to v14 (#1005)
continuous-integration/drone/push Build is passing Details
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#1005
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-16 14:31:04 +00:00
renovate 66589ca37d fix(deps): update module github.com/gabriel-vasile/mimetype to v1.4.0 (#1004)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#1004
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-15 21:50:50 +00:00
renovate 45a8c63015 fix(deps): update golang.org/x/sys commit hash to 69063c4 (#1001)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#1001
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-15 21:50:32 +00:00
renovate fce8b89b45 fix(deps): update golang.org/x/oauth2 commit hash to 6b3c2da (#1000)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#1000
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-15 20:19:29 +00:00
stephen-hill 82a3330412 Added the ability to configure the JWT expiry date using a new server.jwtttl config parameter. (#999)
continuous-integration/drone/push Build is passing Details
Co-authored-by: Stephen Hill <stephen@gatekiller.co.uk>
Reviewed-on: vikunja/api#999
Co-authored-by: stephen-hill <stephen@gatekiller.co.uk>
Co-committed-by: stephen-hill <stephen@gatekiller.co.uk>
2021-10-09 11:02:28 +00:00
ajgon fb9fa27488 healthcheck endpoint (#998)
continuous-integration/drone/push Build is passing Details
Co-authored-by: Igor Rzegocki <igor@rzegocki.pl>
Reviewed-on: vikunja/api#998
Reviewed-by: konrad <k@knt.li>
Co-authored-by: ajgon <vikunja-gitea@ajgon.ovh>
Co-committed-by: ajgon <vikunja-gitea@ajgon.ovh>
2021-10-03 18:37:02 +00:00
kolaente cd6cd840a2
chore(ci): sign drone config
continuous-integration/drone/push Build is passing Details
2021-10-02 16:36:07 +02:00
kolaente c2f33868c8
chore(ci): use latest version of s3 plugin
continuous-integration/drone/push Build was killed Details
2021-10-02 16:31:06 +02:00
kolaente 73a99ebd92
chore(mage): don't set api packages when they are not used
continuous-integration/drone/push Build is failing Details
2021-10-02 15:40:56 +02:00
kolaente 68998e90a4
docs: fix api url in docker examples without a proxy
continuous-integration/drone/push Build is passing Details
2021-09-29 20:38:09 +02:00
renovate c67456d420 fix(deps): update golang.org/x/term commit hash to 03fcf44 (#996)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#996
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-28 07:04:56 +00:00
renovate 2ab27c72f2 fix(deps): update golang.org/x/sys commit hash to 39ccf1d (#995)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#995
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-27 10:57:11 +00:00
renovate df5c5e3be2 fix(deps): update golang.org/x/sys commit hash to 1cf2251 (#994)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#994
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-27 08:05:48 +00:00
renovate 1f067930a2 fix(deps): update module github.com/labstack/echo/v4 to v4.6.1 (#993)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#993
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-26 16:25:10 +00:00
renovate 0ca1560bf1 fix(deps): update module github.com/swaggo/swag to v1.7.3 (#990)
continuous-integration/drone/push Build is passing Details
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#990
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-26 12:44:37 +00:00
kolaente 4de8ec56a6
fix: generate swagger docs
continuous-integration/drone/push Build is passing Details
2021-09-26 14:18:44 +02:00
kolaente ae8db176db
feat: expose if task comments are enabled or not in /info
continuous-integration/drone/push Build is failing Details
2021-09-26 13:37:57 +02:00
renovate 56efbdc297 fix(deps): update golang.org/x/sys commit hash to 92d5a99 (#992)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#992
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-25 06:34:41 +00:00
renovate 4764a8e4f0 fix(deps): update module github.com/golang-jwt/jwt/v4 to v4.1.0 (#991)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#991
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-25 06:34:22 +00:00
kolaente dbd6f36da6
docs: add another youtube tutorial
continuous-integration/drone/push Build was killed Details
2021-09-24 22:11:03 +02:00
kolaente 4255bc3a94
docs: add docker-compose example with no proxy
continuous-integration/drone/push Build is failing Details
2021-09-24 20:45:37 +02:00
kolaente bb086eb9f8
feat: add better error logs for mage commands
continuous-integration/drone/push Build is passing Details
2021-09-24 20:03:38 +02:00
renovate c4da0b424b fix(deps): update golang.org/x/sys commit hash to b8560ed (#989)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#989
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-23 18:36:23 +00:00
renovate 037457e865 fix(deps): update module github.com/labstack/echo/v4 to v4.6.0 (#988)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#988
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-21 20:08:42 +00:00
renovate e51f125931 fix(deps): update golang.org/x/term commit hash to 140adaa (#983)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#983
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-21 19:40:27 +00:00
renovate 57d447b165 fix(deps): update golang.org/x/crypto commit hash to 089bfa5 (#979)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#979
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-21 19:40:15 +00:00
renovate 4136255c67 fix(deps): update module github.com/spf13/viper to v1.9.0 (#987)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#987
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-21 16:18:39 +00:00
renovate 0f2013f915 fix(deps): update module github.com/coreos/go-oidc/v3 to v3.1.0 (#985)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#985
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-21 16:18:19 +00:00
renovate 0ca31aed28 fix(deps): update module github.com/yuin/goldmark to v1.4.1 (#976)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#976
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-21 16:18:02 +00:00
renovate 351fe1f624 fix(deps): update golang.org/x/sys commit hash to 437939a (#975)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#975
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-21 16:17:50 +00:00
renovate abfdae0012 fix(deps): update golang.org/x/sys commit hash to 528a39c (#974)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#974
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-09 22:22:38 +00:00
renovate 5f5936c972 fix(deps): update golang.org/x/sys commit hash to aa78b53 (#973)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#973
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-09 16:16:28 +00:00
renovate 111ac92619 fix(deps): update golang.org/x/sys commit hash to a851e7d (#972)
continuous-integration/drone/push Build is failing Details
Reviewed-on: vikunja/api#972
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-08 20:17:39 +00:00
kolaente bf00ea4939
0.18.1 release preparations
continuous-integration/drone/push Build is passing Details
2021-09-08 19:46:02 +02:00
kolaente 465f6d90ab
Fix tasks not exported
continuous-integration/drone/push Build is passing Details
2021-09-08 18:40:54 +02:00
renovate 07bda3eae3 fix(deps): update golang.org/x/sys commit hash to c212e73 (#971)
continuous-integration/drone/push Build is failing Details
Reviewed-on: vikunja/api#971
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-08 16:22:29 +00:00
kolaente 03d818fd9f
Fix tmp export file created in the wrong path
continuous-integration/drone/push Build is passing Details
2021-09-08 17:44:23 +02:00
kolaente f019ae42bb
Fix exporting tasks from archived lists
continuous-integration/drone/push Build is passing Details
2021-09-08 17:40:33 +02:00
kolaente 9000f2c3cd
Fix lint
continuous-integration/drone/push Build is passing Details
2021-09-07 19:09:44 +02:00
kolaente cc1bb3083f
Don't try to export items which do not have a parent
continuous-integration/drone/push Build is failing Details
2021-09-06 22:14:38 +02:00
kolaente be47459c14
Docs: Add another third-party tutorial link
continuous-integration/drone/push Build is passing Details
2021-09-06 21:15:04 +02:00
renovate 086f005e3d fix(deps): update golang.org/x/sys commit hash to 6f6e228 (#970)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#970
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-06 19:02:33 +00:00
kolaente 24bb7e98fa
0.18.0 release preparations
continuous-integration/drone/push Build is passing Details
2021-09-05 16:49:13 +02:00
kolaente 496c38de8e
Switch the :latest docker image tag to contain the latest release instead of the latest unstable
continuous-integration/drone/push Build is passing Details
2021-09-05 14:39:56 +02:00
konrad 90146aea5b User Data Export and import (#967)
continuous-integration/drone/push Build is passing Details
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#967
Co-authored-by: konrad <k@knt.li>
Co-committed-by: konrad <k@knt.li>
2021-09-04 19:26:31 +00:00
renovate fc51a3e76f fix(deps): update golang.org/x/sys commit hash to 97244b9 (#965)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#965
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-03 12:41:22 +00:00
renovate 4154247d95 fix(deps): update module github.com/lib/pq to v1.10.3 (#963)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#963
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-02 21:16:49 +00:00
renovate e3956527d4 fix(deps): update golang.org/x/sys commit hash to f475640 (#962)
continuous-integration/drone/push Build is failing Details
Reviewed-on: vikunja/api#962
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-09-02 06:09:23 +00:00
kolaente 15e5a9069b
Docs: Update translation guidelines
continuous-integration/drone/push Build is passing Details
2021-08-31 22:45:02 +02:00
kolaente c9d0f519ee
Docs: Update translation guidelines
continuous-integration/drone/push Build is failing Details
2021-08-31 22:41:56 +02:00
renovate 72f7c3333a fix(deps): update golang.org/x/sys commit hash to f4d4317 (#961)
continuous-integration/drone/push Build is failing Details
Reviewed-on: vikunja/api#961
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-08-31 05:17:52 +00:00
kolaente d7b74500c3
Add another youtube tutorial
continuous-integration/drone/push Build is failing Details
2021-08-30 20:55:21 +02:00
kolaente bd24743640
docs: Add other installation resources
continuous-integration/drone/push Build is failing Details
2021-08-23 17:52:01 +02:00
renovate e2dd9d85b5 fix(deps): update golang.org/x/sys commit hash to 63515b4 (#959)
continuous-integration/drone/push Build is failing Details
Reviewed-on: vikunja/api#959
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-08-23 08:21:32 +00:00
82 changed files with 3656 additions and 978 deletions

View File

@ -62,7 +62,7 @@ services:
- name: tmp-mysql-migration
path: /var/lib/mysql
- name: test-postgres-unit
image: postgres:13
image: postgres:14
environment:
POSTGRES_PASSWORD: vikunjatest
POSTGRES_DB: vikunjatest
@ -72,7 +72,7 @@ services:
commands:
- docker-entrypoint.sh -c fsync=off -c full_page_writes=off # turns of wal
- name: test-postgres-integration
image: postgres:13
image: postgres:14
environment:
POSTGRES_PASSWORD: vikunjatest
POSTGRES_DB: vikunjatest
@ -82,7 +82,7 @@ services:
commands:
- docker-entrypoint.sh -c fsync=off -c full_page_writes=off # turns of wal
- name: test-postgres-migration
image: postgres:13
image: postgres:14
environment:
POSTGRES_PASSWORD: vikunjatest
POSTGRES_DB: vikunjatest
@ -459,7 +459,7 @@ steps:
# Push the releases to our pseudo-s3-bucket
- name: release-latest
image: plugins/s3:1
image: plugins/s3
pull: true
settings:
bucket: vikunja-releases
@ -481,7 +481,7 @@ steps:
depends_on: [ sign-release ]
- name: release-version
image: plugins/s3:1
image: plugins/s3
pull: true
settings:
bucket: vikunja-releases
@ -514,7 +514,7 @@ steps:
# Push the os releases to our pseudo-s3-bucket
- name: release-os-latest
image: plugins/s3:1
image: plugins/s3
pull: true
settings:
bucket: vikunja-releases
@ -536,7 +536,7 @@ steps:
depends_on: [ build-os-packages ]
- name: release-os-version
image: plugins/s3:1
image: plugins/s3
pull: true
settings:
bucket: vikunja-releases
@ -576,7 +576,7 @@ steps:
# Push the releases to our pseudo-s3-bucket
- name: release-deb
image: plugins/s3:1
image: plugins/s3
pull: true
settings:
bucket: vikunja-releases
@ -662,7 +662,7 @@ steps:
image: docker:git
commands:
- git fetch --tags
- name: docker-arm-latest
- name: docker-arm-unstable
image: plugins/docker:linux-arm
pull: true
settings:
@ -671,7 +671,7 @@ steps:
password:
from_secret: docker_password
repo: vikunja/api
tags: latest-linux-arm
tags: unstable-linux-arm
depends_on: [ fetch-tags ]
when:
ref:
@ -693,7 +693,7 @@ steps:
ref:
- "refs/tags/**"
- name: docker-arm64-latest
- name: docker-arm64-unstable
image: plugins/docker:linux-arm64
pull: true
settings:
@ -702,7 +702,7 @@ steps:
password:
from_secret: docker_password
repo: vikunja/api
tags: latest-linux-arm64
tags: unstable-linux-arm64
depends_on: [ fetch-tags ]
when:
ref:
@ -746,7 +746,8 @@ steps:
image: docker:git
commands:
- git fetch --tags
- name: docker-latest
- name: docker-unstable
image: plugins/docker:linux-amd64
pull: true
settings:
@ -755,7 +756,7 @@ steps:
password:
from_secret: docker_password
repo: vikunja/api
tags: latest-linux-amd64
tags: unstable-linux-amd64
depends_on: [ fetch-tags ]
when:
ref:
@ -792,13 +793,13 @@ depends_on:
- docker-arm-release
steps:
- name: manifest-latest
- name: manifest-unstable
pull: always
image: plugins/manifest
settings:
tags: latest
tags: unstable
ignore_missing: true
spec: docker-manifest-latest.tmpl
spec: docker-manifest-unstable.tmpl
password:
from_secret: docker_password
username:
@ -807,7 +808,7 @@ steps:
ref:
- refs/heads/main
- name: manifest
- name: manifest-release
pull: always
image: plugins/manifest
settings:
@ -822,6 +823,23 @@ steps:
ref:
- "refs/tags/**"
- name: manifest-release-latest
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
when:
ref:
- "refs/tags/**"
---
kind: pipeline
type: docker
@ -856,6 +874,6 @@ steps:
- failure
---
kind: signature
hmac: 2cee8cac22aae8b4a25ffa28c47d9defc6ce227e0d262b57677f9adbf8badc3e
hmac: 4b36351eaff966fb0e28775059a8a4e091eca7a3ef4c52df2a050fd6c59d409e
...

View File

@ -2,11 +2,184 @@
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres
to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
All releases can be found on https://code.vikunja.io/api/releases.
## [0.18.1] - 2021-09-08
### Fixed
* Docs: Add another third-party tutorial link
* Don't try to export items which do not have a parent
* fix(deps): update golang.org/x/sys commit hash to 6f6e228 (#970)
* fix(deps): update golang.org/x/sys commit hash to c212e73 (#971)
* Fix exporting tasks from archived lists
* Fix lint
* Fix tasks not exported
* Fix tmp export file created in the wrong path
## [0.18.0] - 2021-09-05
### Added
* Add default list setting (#875)
* Add menu link to Vikunja Cloud in docs
* Add more logging and better error messages for openid authentication + clarify docs
* Add more logging for test data api endpoint
* Add searching for tasks by index
* Add setting for first day of the week
* Add support of Unix socket (#912)
* Add truncate parameter to test fixtures setup
* Notify the user after three failed login attempts
* Reorder tasks, lists and kanban buckets (#923)
* Send a notification on failed TOTP
* Task mentions (#926)
* Try to get more information about the user when authenticating with openid
* User account deletion (#937)
* User Data Export and import (#967)
### Changed
* Allow running migration 20210711173657 multiple times to fix issues when it didn't completely run through previously
* Better logging for errors while importing a bunch of tasks
* Change task title to TEXT instead of varchar(250) to allow for longer task titles
* Disable the user account after 10 failed password attempts
* Docs: Add a note about default password
* Docs: Add another youtube tutorial
* Docs: Add ios to the list of not working caldav clients
* Docs: Add k8s-at-home Helm Chart for Vikunja
* Docs: Add other installation resources
* Docs: Add translation docs
* Docs: Fix rewrite rules in apache example configs
* Docs: Translation now happening at crowdin
* Docs: Update translation guidelines
* Don't fail when removing the last bucket in migration from other services
* Don't notify the user who created the team
* Don't use the mariadb root user in docker-compose examples
* Ensure case insensitive search on postgres (#927)
* Increase test timeout
* Only filter out failing openid providers if multiple are configured and one of them failed
* Only send an email about failed totp after three failed attempts
* Rearrange setting frontend url in config
* Refactor user email confirmation + password reset handling (#919)
* Rename and sign drone config
* Replace jwt-go with github.com/golang-jwt/jwt
* Reset failed totp attempts when logging in successfully
* Save user tokens as text and not varchar
* Save user tokens as varchar(450) and not text to fix mysql indexing issues
* Set todoist migration redirect url to the frontend url by default
* Show config full paths and env variables with config options
* Switch the :latest docker image tag to contain the latest release instead of the latest unstable
* Tune test db server settings to speed up tests (#939)
### Fixed
* Fix authentication callback
* Fix duplicating empty lists
* Fix error handling when deleting an attachment file
* Fix error when searching for a namespace returned no results
* Fix error when searching for a namespace with subscribers
* Fix goimports
* Fix importing archived projects and done items from todoist
* Fix jwt middleware
* Fix lint
* Fix mapping task priorities from Vikunja to calDAV
* Fix moving the done bucket around
* Fix old references to master in docs
* Fix panic on invalid smtp config
* Fix parsing openid config when using a json config file
* Fix saving pointer values to memory keyvalue
* Fix saving reminders of repeating tasks
* Fix setting a saved filter as favorite
* Fix setting task favorite status of related tasks
* Fix setting up keyvalue storage in tests
* Fix swagger docs for create requests
* Fix task relations not getting properly cleaned up when deleting them
* Fix tests & lint
* Make sure a bucket exists or use the default bucket when importing tasks
* Make sure all associated entities of a task are deleted when the task is deleted
* Make sure list / task favorites are set per user, not per entity (#915)
* Make sure the configured frontend url always has a / at the end
* Refactor & fix storing struct-values in redis keyvalue
* Todoist migration: don't panic if no reminder was found for task
### Dependency updates
* fix(deps): update golang.org/x/sys commit hash to 63515b4 (#959)
* fix(deps): update golang.org/x/sys commit hash to 97244b9 (#965)
* fix(deps): update golang.org/x/sys commit hash to f475640 (#962)
* fix(deps): update golang.org/x/sys commit hash to f4d4317 (#961)
* fix(deps): update module github.com/lib/pq to v1.10.3 (#963)
* Update alpine Docker tag to v3.13 (#884)
* Update alpine Docker tag to v3.14 (#889)
* Update golang.org/x/crypto commit hash to 0a44fdf (#944)
* Update golang.org/x/crypto commit hash to 0ba0e8f (#943)
* Update golang.org/x/crypto commit hash to 32db794 (#949)
* Update golang.org/x/crypto commit hash to 5ff15b2 (#891)
* Update golang.org/x/crypto commit hash to a769d52 (#916)
* Update golang.org/x/image commit hash to 775e3b0 (#880)
* Update golang.org/x/image commit hash to a66eb64 (#900)
* Update golang.org/x/image commit hash to e6eecd4 (#893)
* Update golang.org/x/net commit hash to 37e1c6af
* Update golang.org/x/oauth2 commit hash to 14747e6 (#894)
* Update golang.org/x/oauth2 commit hash to 2bc19b1 (#955)
* Update golang.org/x/oauth2 commit hash to 6f1e639 (#931)
* Update golang.org/x/oauth2 commit hash to 7df4dd6 (#952)
* Update golang.org/x/oauth2 commit hash to a41e5a7 (#902)
* Update golang.org/x/oauth2 commit hash to a8dc77f (#896)
* Update golang.org/x/oauth2 commit hash to bce0382 (#895)
* Update golang.org/x/oauth2 commit hash to d040287 (#888)
* Update golang.org/x/oauth2 commit hash to f6687ab (#862)
* Update golang.org/x/oauth2 commit hash to faf39c7 (#935)
* Update golang.org/x/sys commit hash to 00dd8d7 (#953)
* Update golang.org/x/sys commit hash to 15123e1 (#946)
* Update golang.org/x/sys commit hash to 1e6c022 (#947)
* Update golang.org/x/sys commit hash to 30e4713 (#945)
* Update golang.org/x/sys commit hash to 41cdb87 (#956)
* Update golang.org/x/sys commit hash to 7d9622a (#948)
* Update golang.org/x/sys commit hash to bfb29a6 (#951)
* Update golang.org/x/sys commit hash to d867a43 (#934)
* Update golang.org/x/sys commit hash to e5e7981 (#933)
* Update golang.org/x/sys commit hash to f52c844 (#954)
* Update golang.org/x/term commit hash to 6886f2d (#887)
* Update module getsentry/sentry-go to v0.11.0 (#869)
* Update module github.com/coreos/go-oidc to v3 (#885)
* Update module github.com/gabriel-vasile/mimetype to v1.3.1 (#904)
* Update module github.com/golang-jwt/jwt to v3.2.2 (#928)
* Update module github.com/golang-jwt/jwt to v4 (#930)
* Update module github.com/go-redis/redis/v8 to v8.11.0 (#903)
* Update module github.com/go-redis/redis/v8 to v8.11.1 (#925)
* Update module github.com/go-redis/redis/v8 to v8.11.2 (#932)
* Update module github.com/go-redis/redis/v8 to v8.11.3 (#942)
* Update module github.com/iancoleman/strcase to v0.2.0 (#918)
* Update module github.com/labstack/echo/v4 to v4.4.0 (#917)
* Update module github.com/labstack/echo/v4 to v4.5.0 (#929)
* Update module github.com/mattn/go-sqlite3 to v1.14.8 (#921)
* Update module github.com/spf13/cobra to v1.2.0 (#905)
* Update module github.com/spf13/cobra to v1.2.1 (#906)
* Update module github.com/spf13/viper to v1.8.0 (#890)
* Update module github.com/spf13/viper to v1.8.1 (#899)
* Update module github.com/swaggo/swag to v1.7.1 (#936)
* Update module github.com/yuin/goldmark to v1.3.8 (#892)
* Update module github.com/yuin/goldmark to v1.3.9 (#901)
* Update module github.com/yuin/goldmark to v1.4.0 (#908)
* Update module go-redis/redis/v8 to v8.10.0 (#882)
* Update module go-redis/redis/v8 to v8.7.1 (#807)
* Update module go-testfixtures/testfixtures/v3 to v3.6.1 (#868)
* Update module lib/pq to v1.10.2 (#865)
* Update module prometheus/client_golang to v1.11.0 (#879)
* Update module yuin/goldmark to v1.3.6 (#863)
* Update module yuin/goldmark to v1.3.7 (#867)
* Update monachus/hugo Docker tag to v0.75.1 (#940)
## [0.17.1] - 2021-06-09
### Fixed
* Fix parsing openid config when using a json config file
## [0.17.0] - 2021-05-14
### Added
@ -446,7 +619,7 @@ All releases can be found on https://code.vikunja.io/api/releases.
## [0.14.0] - 2020-07-01
### Added
### Added
* Add ability to run the docker container with configurable user and group ids
* Add better errors if the sqlite db file is not writable
@ -660,7 +833,7 @@ All releases can be found on https://code.vikunja.io/api/releases.
## [0.12] - 2020-04-04
#### Added
#### Added
* Add support for archiving lists and namespaces (#152)
* Colors for lists and namespaces (#155)
@ -684,7 +857,7 @@ All releases can be found on https://code.vikunja.io/api/releases.
## [0.11] - 2020-03-01
### Added
### Added
* Add config options for cors handling (#124)
* Add config options for task attachments (#125)
@ -752,7 +925,7 @@ All releases can be found on https://code.vikunja.io/api/releases.
## [0.9] - 2019-11-24
### Added
### Added
* Task Attachments (#104)
* Task Relations (#103)
@ -765,7 +938,8 @@ All releases can be found on https://code.vikunja.io/api/releases.
### Fixed
* Fix default logging settings (#107)
* Fixed a bug where adding assignees or reminders via an update would re-create them and not respect already inserted ones, leaving a lot of garbage
* Fixed a bug where adding assignees or reminders via an update would re-create them and not respect already inserted
ones, leaving a lot of garbage
* Fixed a bug where deleting an attachment would cause a nil panic
* Fixed building docs theme
* Fixed error when setting max file size on 32-Bit systems
@ -778,7 +952,7 @@ All releases can be found on https://code.vikunja.io/api/releases.
* Fixed removing reminders
* Small link share fixes (#96)
### Changed
### Changed
* Improve pagination (#105)
* Moved `teams_{namespace|list}_*` to `{namespace|list}_teams_*` for better consistency (#101)
@ -899,7 +1073,8 @@ All releases can be found on https://code.vikunja.io/api/releases.
* Updated libraries
* Updated drone to version 1
* Releases are now signed with our pgp key (more info about this on [the download page](https://vikunja.io/en/download/)).
* Releases are now signed with our pgp key (more info about this
on [the download page](https://vikunja.io/en/download/)).
## [0.5] - 2018-12-02
@ -929,7 +1104,7 @@ All releases can be found on https://code.vikunja.io/api/releases.
## [0.3] - 2018-11-02
### Added
### Added
* Password reset
* Email verification when registering

View File

@ -23,7 +23,7 @@ RUN if [ -n "${VIKUNJA_VERSION}" ]; then git checkout "${VIKUNJA_VERSION}"; fi \
# The actual image
# Note: I wanted to use the scratch image here, but unfortunatly the go-sqlite bindings require cgo and
# because of this, the container would not start when I compiled the image without cgo.
FROM alpine:3.14
FROM alpine:3.15
LABEL maintainer="maintainers@vikunja.io"
WORKDIR /app/vikunja/

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.17.0-brightgreen.svg)](https://dl.vikunja.io)
[![Download](https://img.shields.io/badge/download-v0.18.1-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

@ -3,6 +3,9 @@ service:
# Default is a random token which will be generated at each startup of vikunja.
# (This means all already issued tokens will be invalid once you restart vikunja)
JWTSecret: "<jwt-secret>"
# The duration of the issed JWT tokens in seconds.
# The default is 259200 seconds (3 Days).
jwtttl: 259200
# The interface on which to run the webserver
interface: ":3456"
# Path to Unix socket. If set, it will be created and used instead of tcp

View File

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

View File

@ -31,10 +31,10 @@ menu:
url: https://vikunja.io/en/
weight: 10
- name: Features
url: https://vikunja.io/en/features
url: https://vikunja.io/features
weight: 20
- name: Download
url: https://vikunja.io/en/download
url: https://vikunja.io/download
weight: 30
- name: Docs
url: https://vikunja.io/docs

View File

@ -10,8 +10,8 @@ menu:
# Database
Vikunja uses [xorm](http://xorm.io/) as an abstraction layer to handle the database connection.
Please refer to [their](http://xorm.io/docs/) documentation on how to exactly use it.
Vikunja uses [xorm](https://xorm.io/) as an abstraction layer to handle the database connection.
Please refer to [their](https://xorm.io/docs/) documentation on how to exactly use it.
{{< table_of_contents >}}
@ -24,7 +24,7 @@ In other packages, use the `db.NewSession()` method to get a new database sessio
To add a new table to the database, create the struct and [add a migration for it]({{< ref "db-migrations.md" >}}).
To learn more about how to configure your struct to create "good" tables, refer to [the xorm documentaion](http://xorm.io/docs/).
To learn more about how to configure your struct to create "good" tables, refer to [the xorm documentaion](https://xorm.io/docs/).
In most cases you will also need to implement the `TableName() string` method on the new struct to make sure the table
name matches the rest of the tables - plural.

View File

@ -12,11 +12,45 @@ menu:
# Development
We use go modules to manage third-party libraries for Vikunja, so you'll need at least go `1.11` to use these.
{{< table_of_contents >}}
## General
To contribute to Vikunja, fork the project and work on the main branch.
Once you feel like your changes are ready, open a PR in the respective repo.
A maintainer will take a look and give you feedback. Once everyone is happy, the PR gets merged and released.
If you plan to do a bigger change, it is better to open an issue for discussion first.
## API
The code for the api is located at [code.vikunja.io/api](https://code.vikunja.io/api).
We use go modules to manage third-party libraries for Vikunja, so you'll need at least go `1.11` to use these.
A lot of developing tasks are automated using a Magefile, so make sure to [take a look at it]({{< ref "mage.md">}}).
Make sure to check the other doc articles for specific development tasks like [testing]({{< ref "test.md">}}),
Make sure to check the other doc articles for specific development tasks like [testing]({{< ref "test.md">}}),
[database migrations]({{< ref "db-migrations.md" >}}) and the [project structure]({{< ref "structure.md" >}}).
## Frontend requirements
The code for the frontend is located at [code.vikunja.io/frontend](https://code.vikunja.io/frontend).
You need to have yarn v1 and nodejs in version 16 installed.
## Git flow
The `main` branch is the latest and bleeding edge branch with all changes. Unstable releases are automatically
created from this branch.
A release gets tagged from the main branch with the version name as tag name.
Backports and point-releases should go to a `release/version` branch, based on the tag they are building on top of.
## Conventional commits
We're using [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) because they greatly simplify
generating release notes.
It is not required to use them when creating a PR, but appreciated.

View File

@ -14,7 +14,17 @@ It is possible to migrate data from other to-do services to Vikunja.
To make this easier, we have put together a few helpers which are documented on this page.
In general, each migrator implements a migrator interface which is then called from a client.
The interface makes it possible to use helper methods which handle http an focus only on the implementation of the migrator itself.
The interface makes it possible to use helper methods which handle http and focus only on the implementation of the migrator itself.
There are two ways of migrating data from another service:
1. Through the auth-based flow where the user gives you access to their data at the third-party service through an
oauth flow. You can then call the service's api on behalf of your user to get all the data.
The Todoist, Trello and Microsoft To-Do Migrators use this pattern.
2. A file migration where the user uploads a file obtained from some third-party service. In your migrator, you need
to parse the file and create the lists, tasks etc.
The Vikunja File Import uses this pattern.
To differentiate the two, there are two different interfaces you must implement.
{{< table_of_contents >}}
@ -23,13 +33,16 @@ The interface makes it possible to use helper methods which handle http an focus
All migrator implementations live in their own package in `pkg/modules/migration/<name-of-the-service>`.
When creating a new migrator, you should place all related code inside that module.
## Migrator interface
## Migrator Interface
The migrator interface is defined as follows:
```go
// Migrator is the basic migrator interface which is shared among all migrators
type Migrator interface {
// Name holds the name of the migration.
// This is used to show the name to users and to keep track of users who already migrated.
Name() string
// Migrate is the interface used to migrate a user's tasks from another platform to vikunja.
// The user object is the user who's tasks will be migrated.
Migrate(user *models.User) error
@ -37,9 +50,20 @@ type Migrator interface {
// The use case for this are Oauth flows, where the server token should remain hidden and not
// known to the frontend.
AuthURL() string
}
```
## File Migrator Interface
```go
// FileMigrator handles importing Vikunja data from a file. The implementation of it determines the format.
type FileMigrator interface {
// Name holds the name of the migration.
// This is used to show the name to users and to keep track of users who already migrated.
Name() string
// Migrate is the interface used to migrate a user's tasks, list and other things from a file to vikunja.
// The user object is the user who's tasks will be migrated.
Migrate(user *user.User, file io.ReaderAt, size int64) error
}
```
@ -54,15 +78,26 @@ authUrl, Status and Migrate methods.
```go
// This is an example for the Wunderlist migrator
if config.MigrationWunderlistEnable.GetBool() {
wunderlistMigrationHandler := &migrationHandler.MigrationWeb{
wunderlistMigrationHandler := &migrationHandler.MigrationWeb{
MigrationStruct: func() migration.Migrator {
return &wunderlist.Migration{}
},
}
wunderlistMigrationHandler.RegisterRoutes(m)
wunderlistMigrationHandler.RegisterRoutes(m)
}
```
And for the file migrator:
```go
vikunjaFileMigrationHandler := &migrationHandler.FileMigratorWeb{
MigrationStruct: func() migration.FileMigrator {
return &vikunja_file.FileMigrator{}
},
}
vikunjaFileMigrationHandler.RegisterRoutes(m)
```
You should also document the routes with [swagger annotations]({{< ref "swagger-docs.md" >}}).
## Insertion helper method
@ -70,7 +105,8 @@ You should also document the routes with [swagger annotations]({{< ref "swagger-
There is a method available in the `migration` package which takes a fully nested Vikunja structure and creates it with all relations.
This means you start by adding a namespace, then add lists inside of that namespace, then tasks in the lists and so on.
The root structure must be present as `[]*models.NamespaceWithLists`.
The root structure must be present as `[]*models.NamespaceWithListsAndTasks`. It allows to represent all of Vikunja's
hierachie as a single data structure.
Then call the method like so:
@ -85,14 +121,16 @@ err = migration.InsertFromStructure(fullVikunjaHierachie, user)
## Configuration
You should add at least an option to enable or disable the migration.
If your migrator is an oauth-based one, you should add at least an option to enable or disable it.
Chances are, you'll need some more options for things like client ID and secret
(if the other service uses oAuth as an authentication flow).
The easiest way to implement an on/off switch is to check whether your migration service is enabled or not when
registering the routes, and then simply don't registering the routes in the case it is disabled.
registering the routes, and then simply don't registering the routes in case it is disabled.
File based migrators can always be enabled.
### Making the migrator public in `/info`
You should make your migrator available in the `/info` endpoint so that frontends can display options to enable them or not.
To do this, add an entry to `pkg/routes/api/v1/info.go`.
To do this, add an entry to the `AvailableMigrators` field in `pkg/routes/api/v1/info.go`.

View File

@ -25,29 +25,52 @@ Genauer definiert:
* “falsch” anstatt “nicht korrekt/inkorrekt”
* “Wende dich an …” anstatt “kontaktiere …”
* In derselben Zeit übersetzen (sonst wird aus dem englischen “is“ das deutsche “war”)
* Richtige Anführungszeichen verwenden. Also „“ statt '' oder ' oder ` oder ´
* Richtige Anführungszeichen verwenden. Also `„“` statt `''` oder `'` oder ` oder ´
* `„` für beginnende Anführungszeichen, `“` für schließende Anführungszeichen
Es gelten Artikel und Worttrennungen aus dem [Duden](https://duden.de).
## Gendern
## Formulierungen
Wo möglich, sollte eine geschlechtsneutrale Anrede verwendet werden.
Falls diese sehr umständlich würden (siehe oben „Amtsdeutsch-Umschreibungen“), soll mit *Doppelpunkt* gegendert werden.
Beispiel: „Benutzer:in“
* `Account` statt `Konto`.
* `TOTP` immer als ein Wort und Groß.
* `CalDAV` immer so.
* `löschen` oder `entfernen` je nach Kontext. Wenn etwas *gelöscht* wird, existiert das gelöschte Objekt und danach
nicht mehr und hat evtl. andere Objekte mitgelöscht (z.B. eine Aufgabe). Wird etwas *entfernt*, bezieht sich das
meistens auf die Beziehung zu einem anderen Objekt. Das entfernte Objekt existiert danach immernoch, z.B. beim
Entfernen eine:r Nutzer:in aus einem Team.
* Analog zu `löschen` oder `entfernen` gilt ähnliches für `hinzufügen` oder `erstellen`. Eine Aufgabe wird *erstellt*,
aber ein:e Nutzer:in nur zu einem Team *hinzugefügt*.
* `Anmeldename` anstatt `Benutzer:innenname`
## Formulierungen in Modals und Buttons
Es sollten die gleichen Formulierungen auf Buttons und Modals verwendet werden.
Beispiel: Wenn der Button mit `löschen` beschriftet ist, sollte im Modal die Frage lauten `Willst du das wirklich löschen?` und nicht `Willst du das wirklich entfernen?`.
Gleiches gilt für Erfolgs/Fehlermeldungen nach der Aktion.
Beispiel: Wenn der Button mit `löschen` beschriftet ist, sollte im Modal die Frage
lauten `Willst du das wirklich löschen?` und nicht `Willst du das wirklich entfernen?`. Gleiches gilt für
Erfolgs/Fehlermeldungen nach der Aktion.
## Gendern
Wo möglich, sollte eine geschlechtsneutrale Anrede verwendet werden. Falls diese sehr umständlich würden (siehe oben
„Amtsdeutsch-Umschreibungen“), soll mit *Doppelpunkt* gegendert werden.
Beispiel: „Benutzer:in“
## Trennungen
* E-Mail-Adresse (siehe Duden)
## Wörter und Ausdrücke
| Englisches Original | Verwendung in deutscher Übersetzung |
| ------------------- | -------------------- |
| Bucket | Spalte |
| Namespace | Namespace |
| Link Share | Linkfreigabe |
| Username | Anmeldename |
## Weiterführende Links
* http://docs.translatehouse.org/projects/localization-guide/en/latest/guide/translation_guidelines_german.html
* https://docs.translatehouse.org/projects/localization-guide/en/latest/guide/translation_guidelines_german.html

View File

@ -48,7 +48,7 @@ Strings in other languages will be synced through weblate and should not be adde
## Requesting a new language
If you want to start translating Vikunja in a language not yet available in Vikunja, please request the language through the weblate interface.
If you have issues with this or need a discussion before doing so, pleace [contact us](https://vikunja.io/contact/) or [start a discussion in the forum](https://community.vikunja.io).
If you want to start translating Vikunja in a language not yet available in Vikunja, please request the language through the crowdin interface.
If you have issues with this or need a discussion before doing so, please [contact us](https://vikunja.io/contact/) or [start a discussion in the forum](https://community.vikunja.io).
Once at least 50% of all translation strings are translated and approved, they will be added and distributed with the Vikunja frontend for users to select and use Vikunja with them.

View File

@ -10,12 +10,17 @@ menu:
# What to backup
Vikunja does not store any data outside of the database.
So, all you need to backup are the contents of that database and maybe the config file.
There are two parts you need to back up: The database and attachment files.
{{< table_of_contents >}}
## MySQL
## Files
To back up attachments and other files, it is enough to copy them [from the attachments folder]({{< ref "config.md" >}}#basepath) to some other place.
## Database
### MySQL
To create a backup from mysql use the `mysqldump` command:
@ -31,7 +36,7 @@ To restore it, simply pipe it back into the `mysql` command:
mysql -u <user> -p -h <db-host> <database> < vkunja-backup.sql
{{< /highlight >}}
## PostgreSQL
### PostgreSQL
To create a backup from PostgreSQL use the `pg_dump` command:
@ -49,6 +54,6 @@ psql -U <user> -h <db-host> <database> < vikunja-backup.sql
For more information, please visit the [relevant PostgreSQL documentation](https://www.postgresql.org/docs/12/backup-dump.html).
## SQLite
### SQLite
To backup sqllite databases, it is enough to copy the database elsewhere.
To back up sqllite databases, it is enough to copy the [database file]({{< ref "config.md" >}}#path) to somwhere else.

View File

@ -79,6 +79,18 @@ Full path: `service.JWTSecret`
Environment path: `VIKUNJA_SERVICE_JWT_SECRET`
### jwtttl
The duration of the issed JWT tokens in seconds.
The default is 259200 seconds (3 Days).
Default: `259200`
Full path: `service.jwtttl`
Environment path: `VIKUNJA_SERVICE_JWTTTL`
### interface
The interface on which to run the webserver

View File

@ -25,6 +25,9 @@ All examples on this page already reflect this and do not require additional wor
## Redis
While Vikunja has support to use redis as a caching backend, you'll probably not need it unless you're using Vikunja
with more than a handful of users.
To use redis, you'll need to add this to the config examples below:
{{< highlight yaml >}}
@ -44,6 +47,78 @@ services:
image: redis
{{< /highlight >}}
## PostgreSQL
Vikunja supports postgres, mysql and sqlite as a database backend. The examples on this page use mysql with a mariadb container.
To use postgres as a database backend, change the `db` section of the examples to this:
{{< highlight yaml >}}
db:
image: postgres:13
environment:
POSTGRES_PASSWORD: secret
POSTGRES_USER: vikunja
volumes:
- ./db:/var/lib/postgresql/data
restart: unless-stopped
{{< /highlight >}}
You'll also need to change the `VIKUNJA_DATABASE_TYPE` to `postgres` on the api container declaration.
<div class="notification is-warning">
<b>NOTE:</b> The mariadb container can sometimes take a while to initialize, especially on the first run.
During this time, the api container will fail to start at all. It will automatically restart every few seconds.
</div>
## Example without any proxy
This example lets you host Vikunja without any reverse proxy in front of it. This is the absolute minimum configuration
you need to get something up and running. If you want to host Vikunja on one single port instead of two different ones
or need tls termination, check out one of the other examples.
Not 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.
{{< highlight yaml >}}
version: '3'
services:
db:
image: mariadb:10
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
environment:
MYSQL_ROOT_PASSWORD: supersecret
MYSQL_USER: vikunja
MYSQL_PASSWORD: secret
MYSQL_DATABASE: vikunja
volumes:
- ./db:/var/lib/mysql
restart: unless-stopped
api:
image: vikunja/api
environment:
VIKUNJA_DATABASE_HOST: db
VIKUNJA_DATABASE_PASSWORD: secret
VIKUNJA_DATABASE_TYPE: mysql
VIKUNJA_DATABASE_USER: vikunja
VIKUNJA_DATABASE_DATABASE: vikunja
ports:
- 3456:3456
volumes:
- ./files:/app/vikunja/files
depends_on:
- db
restart: unless-stopped
frontend:
image: vikunja/frontend
ports:
- 80:80
environment:
VIKUNJA_API_URL: http://<your-ip-here>:3456/api/v1
restart: unless-stopped
{{< /highlight >}}
## Example with traefik 2
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/).
@ -203,9 +278,9 @@ services:
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
environment:
MYSQL_ROOT_PASSWORD: supersecret
MYSQL_USER: vikunja
MYSQL_PASSWORD: secret
MYSQL_DATABASE: vikunja
MYSQL_USER: vikunja
MYSQL_PASSWORD: secret
MYSQL_DATABASE: vikunja
volumes:
- ./db:/var/lib/mysql
restart: unless-stopped
@ -260,10 +335,10 @@ services:
image: mariadb:10
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
environment:
MYSQL_ROOT_PASSWORD: supersecret
MYSQL_USER: vikunja
MYSQL_PASSWORD: secret
MYSQL_DATABASE: vikunja
MYSQL_ROOT_PASSWORD: supersecret
MYSQL_USER: vikunja
MYSQL_PASSWORD: secret
MYSQL_DATABASE: vikunja
volumes:
- ./db:/var/lib/mysql
restart: unless-stopped

View File

@ -44,3 +44,11 @@ This document provides an overview and instructions for the different methods.
A third-party Helm Chart is available from the k8s-at-home project [here](https://github.com/k8s-at-home/charts/tree/master/charts/stable/vikunja).
## Other installation resources
* [Docker Compose is MUCH Easier Than you Think - Let's Install Vikunja](https://www.youtube.com/watch?v=fGlz2PkXjuo) (Youtube)
* [Setup Vikunja using Docker Compose - Homelab Wiki](https://thehomelab.wiki/books/docker/page/setup-vikunja-using-docker-compose)
* [A Closer look at Vikunja - Email Notifications - Enable or Disable Registrations - Allow Attachments](https://www.youtube.com/watch?v=47wj9pRT6Gw) (Youtube)
* [Install Vikunja in Docker for self-hosted Task Tracking](https://smarthomepursuits.com/install-vikunja-in-docker-for-self-hosted-task-tracking/)
* [Self-Hosted To-Do List with Vikunja in Docker](https://www.youtube.com/watch?v=DqyqDWpEvKI) (Youtube)
* [Vikunja self-hosted (step by step)](https://nguyenminhhung.com/vikunja-self-hosted-step-by-step/)

View File

@ -11,7 +11,7 @@ menu:
# API Documentation
You can find the api docs under `http://vikunja.tld/api/v1/docs` of your instance.
A public instance is available on [try.vikunja.io](http://try.vikunja.io/api/v1/docs).
A public instance is available on [try.vikunja.io](https://try.vikunja.io/api/v1/docs).
These docs are autgenerated from annotations in the code with swagger.

37
go.mod
View File

@ -21,33 +21,31 @@ require (
gitea.com/xorm/xorm-redis-cache v0.2.0
github.com/ThreeDotsLabs/watermill v1.1.1
github.com/adlio/trello v1.9.0
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef
github.com/beevik/etree v1.1.0 // indirect
github.com/c2h5oh/datasize v0.0.0-20200825124411-48ed595a09d2
github.com/coreos/go-oidc/v3 v3.0.0
github.com/coreos/go-oidc/v3 v3.1.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.3.1
github.com/gabriel-vasile/mimetype v1.4.0
github.com/getsentry/sentry-go v0.11.0
github.com/go-errors/errors v1.1.1 // indirect
github.com/go-redis/redis/v8 v8.11.3
github.com/go-redis/redis/v8 v8.11.4
github.com/go-sql-driver/mysql v1.6.0
github.com/go-testfixtures/testfixtures/v3 v3.6.1
github.com/golang-jwt/jwt/v4 v4.0.0
github.com/golang-jwt/jwt/v4 v4.1.0
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
github.com/golang/snappy v0.0.4 // indirect
github.com/iancoleman/strcase v0.2.0
github.com/imdario/mergo v0.3.12
github.com/labstack/echo/v4 v4.5.0
github.com/labstack/gommon v0.3.0
github.com/labstack/echo/v4 v4.6.1
github.com/labstack/gommon v0.3.1
github.com/laurent22/ical-go v0.1.1-0.20181107184520-7e5d6ade8eef
github.com/lib/pq v1.10.2
github.com/lib/pq v1.10.4
github.com/magefile/mage v1.11.0
github.com/mattn/go-isatty v0.0.13 // indirect
github.com/mattn/go-sqlite3 v1.14.8
github.com/mattn/go-sqlite3 v1.14.9
github.com/olekukonko/tablewriter v0.0.5
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
github.com/pquerna/otp v1.3.0
@ -56,18 +54,17 @@ require (
github.com/samedi/caldav-go v3.0.0+incompatible
github.com/spf13/afero v1.6.0
github.com/spf13/cobra v1.2.1
github.com/spf13/viper v1.8.1
github.com/spf13/viper v1.9.0
github.com/stretchr/testify v1.7.0
github.com/swaggo/swag v1.7.1
github.com/ulule/limiter/v3 v3.8.0
github.com/yuin/goldmark v1.4.0
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d
golang.org/x/net v0.0.0-20210614182718-04defd469f4e // indirect
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f
github.com/swaggo/swag v1.7.4
github.com/ulule/limiter/v3 v3.9.0
github.com/yuin/goldmark v1.4.4
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871
golang.org/x/image v0.0.0-20211028202545-6944b10bf410
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/sys v0.0.0-20210820121016-41cdb8703e55
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b
golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/d4l3k/messagediff.v1 v1.2.1
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df

298
go.sum
View File

@ -19,6 +19,11 @@ cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmW
cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=
cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=
cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=
cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=
cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
@ -28,6 +33,7 @@ cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM7
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/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
cloud.google.com/go/firestore v1.6.0/go.mod h1:afJwI0vaXwAG54kI7A//lP/lSPDkQORQuMkv56TxEPU=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
@ -54,6 +60,7 @@ github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMd
github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
@ -66,12 +73,12 @@ github.com/ThreeDotsLabs/watermill v1.1.1 h1:+9NXqWQvplzxBru2CIInvVOZeKUnM+Nysg4
github.com/ThreeDotsLabs/watermill v1.1.1/go.mod h1:Qd1xNFxolCAHCzcMrm6RnjW0manbvN+DJVWc1MWRFlI=
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
@ -79,6 +86,7 @@ github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hC
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
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/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
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=
github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
@ -97,8 +105,11 @@ github.com/c2h5oh/datasize v0.0.0-20200825124411-48ed595a09d2/go.mod h1:S/7n9cop
github.com/cenkalti/backoff/v3 v3.0.0 h1:ske+9nBpD9qZsTBoF41nW5L+AIuFBKMeze18XQ3eG1c=
github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@ -106,13 +117,14 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-oidc/v3 v3.0.0 h1:/mAA0XMgYJw2Uqm7WKGCsKnjitE/+A0FFbOmiRJm7LQ=
github.com/coreos/go-oidc/v3 v3.0.0/go.mod h1:rEJ/idjfUyfkBit1eI1fvyr+64/g9dcKpAm8MJMesvo=
github.com/coreos/go-oidc/v3 v3.1.0 h1:6avEvcdvTa1qYsOZ6I5PRkSYHzpTNWgKYmaJfaYbrRw=
github.com/coreos/go-oidc/v3 v3.1.0/go.mod h1:rEJ/idjfUyfkBit1eI1fvyr+64/g9dcKpAm8MJMesvo=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
@ -158,16 +170,19 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw=
github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gabriel-vasile/mimetype v1.3.1 h1:qevA6c2MtE1RorlScnixeG0VA1H4xrXyhyX3oWBynNQ=
github.com/gabriel-vasile/mimetype v1.3.1/go.mod h1:fA8fi6KUiG7MgQQ+mEWotXoEOvmxRtOJlERCzSmRvr8=
github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
github.com/gabriel-vasile/mimetype v1.4.0 h1:Cn9dkdYsMIu56tGho+fqzh7XmvY2YyGU0FnbhiOsEro=
github.com/gabriel-vasile/mimetype v1.4.0/go.mod h1:fA8fi6KUiG7MgQQ+mEWotXoEOvmxRtOJlERCzSmRvr8=
github.com/garyburd/redigo v1.6.0 h1:0VruCpn7yAIIu7pWVClQC8wxCJEcG3nyzpMSHKi1PQc=
github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
@ -179,6 +194,7 @@ github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NB
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
github.com/go-chi/chi v4.0.2+incompatible h1:maB6vn6FqCxrpz4FqWdh4+lwpyZIQS7YEAUcHlgXVRs=
github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
@ -209,9 +225,10 @@ github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvSc
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
github.com/go-redis/redis/v8 v8.4.2/go.mod h1:A1tbYoHSa1fXwN+//ljcCYYJeLmVrwL9hbQN45Jdy0M=
github.com/go-redis/redis/v8 v8.11.3 h1:GCjoYp8c+yQTJfc0n69iwSiHjvuAdruxl7elnZCxgt8=
github.com/go-redis/redis/v8 v8.11.3/go.mod h1:xNJ9xDG09FsIPwh3bWdk+0oDWHbtF9rPN0F/oD9XeKc=
github.com/go-redis/redis/v8 v8.11.4 h1:kHoYkfZP6+pe04aFTnhDH6GDROa5yJdHJVNxV3F46Tg=
github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w=
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=
@ -225,16 +242,17 @@ github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6Wezm
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
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.0.0 h1:RAqyYixv1p7uEnocuy8P1nru5wprCh/MH2BIlW5z5/o=
github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang-jwt/jwt/v4 v4.1.0 h1:XUgk2Ex5veyVFVeLm0xhusUTQybEbexJXrvPNOKkSY0=
github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
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/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
@ -251,6 +269,7 @@ github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@ -271,6 +290,7 @@ github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
@ -294,6 +314,7 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
@ -305,28 +326,36 @@ github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLe
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/api v1.10.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI=
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
@ -339,8 +368,11 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
github.com/iancoleman/strcase v0.2.0 h1:05I4QRnGpI0m37iZQRuskXh+w77mr6Z41lwQzuHLwW0=
github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
@ -411,7 +443,6 @@ github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMW
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
@ -429,6 +460,7 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.10.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/kolaente/caldav-go v3.0.1-0.20190524174923-9e5cd1688227+incompatible h1:PkEEpmbrFXlMul8cOplR8nkcIM/NDbx+H6fq2+vaKAA=
github.com/kolaente/caldav-go v3.0.1-0.20190524174923-9e5cd1688227+incompatible/go.mod h1:y1UhTNI4g0hVymJrI6yJ5/ohy09hNBeU8iJEZjgdDOw=
@ -441,6 +473,7 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxv
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
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=
@ -448,10 +481,12 @@ 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/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g=
github.com/labstack/echo/v4 v4.1.16/go.mod h1:awO+5TzAjvL8XpibdsfXxPgHr+orhtXZJZIQCVjogKI=
github.com/labstack/echo/v4 v4.5.0 h1:JXk6H5PAw9I3GwizqUHhYyS4f45iyGebR/c1xNCeOCY=
github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y=
github.com/labstack/echo/v4 v4.6.1 h1:OMVsrnNFzYlGSdaiYGHbgWQnr+JM7NG+B9suCPie14M=
github.com/labstack/echo/v4 v4.6.1/go.mod h1:RnjgMWNDB9g/HucVWhQYNQP9PvbYf6adqftqryo7s9k=
github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0=
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
github.com/labstack/gommon v0.3.1 h1:OomWaJXm7xR6L1HmEtGyQf26TEn7V6X88mktX9kee9o=
github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
github.com/laurent22/ical-go v0.1.1-0.20181107184520-7e5d6ade8eef h1:RZnRnSID1skF35j/15KJ6hKZkdIC/teQClJK5wP5LU4=
github.com/laurent22/ical-go v0.1.1-0.20181107184520-7e5d6ade8eef/go.mod h1:4LATl0uhhtytR6p9n1AlktDyIz4u2iUnWEdI3L/hXiw=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
@ -460,8 +495,11 @@ github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.10.3 h1:v9QZf2Sn6AmjXtQeFpdoq/eaNtYP6IN+7lcrygsIAtg=
github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk=
github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lithammer/shortuuid/v3 v3.0.4 h1:uj4xhotfY92Y1Oa6n6HUiFn87CdoEHYUlTy0+IgbLrs=
github.com/lithammer/shortuuid/v3 v3.0.4/go.mod h1:RviRjexKqIzx/7r1peoAITm6m7gnif/h+0zmolKJjzw=
github.com/magefile/mage v1.11.0 h1:C/55Ywp9BpgVVclD3lRnSYCwXTYxmSppIgLeDYlNuls=
@ -476,18 +514,23 @@ github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJ
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs=
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA=
github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-oci8 v0.0.0-20191108001511-cbd8d5bc1da0/go.mod h1:/M9VLO+lUPmxvoOK2PfWRZ8mTtB4q1Hy9lEGijv9Nr8=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
@ -498,13 +541,17 @@ github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A
github.com/mattn/go-sqlite3 v1.14.7/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.8 h1:gDp86IdQsN/xWjIEmr9MF6o9mpksUgh0fu+9ByFxzIU=
github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.9 h1:10HX2Td0ocZpYEjhilsuo6WWtUqttj2Kb0KtD86/KYA=
github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8=
github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
@ -512,8 +559,9 @@ github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS4
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.4.2 h1:6h7AQ0yhTcIsmFmnAwQls75jp2Gzs4iB8W7pjMO+rqo=
github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@ -547,15 +595,16 @@ github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
github.com/onsi/gomega v1.15.0 h1:WjP/FQ/sk43MRmnEcT+MlDw2TFvkrXlprrPST/IudjU=
github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0=
github.com/onsi/gomega v1.16.0 h1:6gjqkI8iiRHMvdccRJM8rVKjCWk6ZIm6FTm3ddIe4/c=
github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ=
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM=
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
@ -567,6 +616,7 @@ github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZ
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/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
github.com/pquerna/otp v1.3.0 h1:oJV/SkzR33anKXwQU3Of42rL4wbrffP4uvUf1SvS5Xs=
github.com/pquerna/otp v1.3.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
@ -608,29 +658,31 @@ github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sagikazarmark/crypt v0.1.0/go.mod h1:B/mN0msZuINBtQ1zZLEQcegFJJf9vnYIR88KRMEuODE=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shopspring/decimal v0.0.0-20191009025716-f1972eb1d1f5 h1:Gojs/hac/DoYEM7WEICT45+hNWczIeuL5D21e5/HPAw=
github.com/shopspring/decimal v0.0.0-20191009025716-f1972eb1d1f5/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY=
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA=
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/cobra v1.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw=
github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk=
@ -641,8 +693,9 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn
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.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/spf13/viper v1.8.1 h1:Kq1fyeebqsBfbjZj4EL7gj2IO0mMaiyjYUWcUsl2O44=
github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
github.com/spf13/viper v1.9.0 h1:yR6EXjTp0y0cLN8OZg1CRZmOBdI88UcGkhgyJhu6nZk=
github.com/spf13/viper v1.9.0/go.mod h1:+i6ajR7OX2XaiBkrcZJFK21htRk7eDeLg7+O6bhUPP4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
@ -655,8 +708,10 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/swaggo/swag v1.7.1 h1:gY9ZakXlNWg/i/v5bQBic7VMZ4teq4m89lpiao74p/s=
github.com/swaggo/swag v1.7.1/go.mod h1:gAiHxNTb9cIpNmA/VEGUP+CyZMCP/EW7mdtc8Bny+p8=
github.com/swaggo/swag v1.7.3 h1:ucB7irEdRrhjmW+Z1Ss4GjO68oPKQFjSgOR8BCAvcbU=
github.com/swaggo/swag v1.7.3/go.mod h1:zD8h6h4SPv7t3l+4BKdRquqW1ASWjKZgT6Qv9z3kNqI=
github.com/swaggo/swag v1.7.4 h1:up+ixy8yOqJKiFcuhMgkuYuF4xnevuhnFAXXF8OSfNg=
github.com/swaggo/swag v1.7.4/go.mod h1:zD8h6h4SPv7t3l+4BKdRquqW1ASWjKZgT6Qv9z3kNqI=
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
@ -665,6 +720,8 @@ github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljT
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/ulule/limiter/v3 v3.8.0 h1:rq76QxDIq5s/rvXc/A6HRHuGmehi/JE18qK3FaRUxKg=
github.com/ulule/limiter/v3 v3.8.0/go.mod h1:TpV4HWgOM7M43mrkE7MU1S62/XtuoZ/C9PL+ExxeTK4=
github.com/ulule/limiter/v3 v3.9.0 h1:ebASTkd6QNNUGhuDrWqImMpsg9GtItgNgxF3nKao58Q=
github.com/ulule/limiter/v3 v3.9.0/go.mod h1:icWc7rrF3T07dj59AhU4+HqKh0uWeh1wKct31wmcoF0=
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
@ -672,12 +729,14 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w=
github.com/valyala/fasthttp v1.17.0/go.mod h1:jjraHZVbKOXftJfsOYoAjaeygpj5hr8ermTRJNroD7A=
github.com/valyala/fasthttp v1.31.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD3K/7o2Cus=
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/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
@ -691,8 +750,14 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
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.0 h1:OtISOGfH6sOWa1/qXqqAiOIAO6Z5J3AEAE18WAq6BiQ=
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.1 h1:/vn0k+RBvwlxEmP5E7SZMqNxPhfMVFEJiykr15/0XKM=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.2 h1:5qVKCqCRBaGz8EepBTi7pbIw8gGCFnB1Mi6kXU4dYv8=
github.com/yuin/goldmark v1.4.2/go.mod h1:rmuwmfZ0+bvzB24eSC//bk1R1Zp3hM0OXYv/G2LIilg=
github.com/yuin/goldmark v1.4.3 h1:eTEYYWtQLjK7+WK45Tk81OTkp/0UvAyqUj8flU0nTO4=
github.com/yuin/goldmark v1.4.3/go.mod h1:rmuwmfZ0+bvzB24eSC//bk1R1Zp3hM0OXYv/G2LIilg=
github.com/yuin/goldmark v1.4.4 h1:zNWRjYUW32G9KirMXYHQHVNFkXvMI7LpgNW2AgYAoIs=
github.com/yuin/goldmark v1.4.4/go.mod h1:rmuwmfZ0+bvzB24eSC//bk1R1Zp3hM0OXYv/G2LIilg=
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/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
@ -707,6 +772,7 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
go.opentelemetry.io/otel v0.14.0/go.mod h1:vH5xEuwy7Rts0GNtsCW3HYQoZDY+OmBJ6t1bFGGlxgw=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
@ -725,22 +791,23 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210812204632-0ba0e8f03122 h1:AOT7vJYHE32m61R8d1WlcqhOO1AocesDsKpcMq+UOaA=
golang.org/x/crypto v0.0.0-20210812204632-0ba0e8f03122/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e h1:VvfwVmMH40bpMeizC9/K7ipM5Qjucuu16RWfneFPyhQ=
golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa h1:idItI2DDfCokpg0N51B2VtiLdJ4vAuXC9fnCb2gACo4=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211115234514-b4de73f9ece8 h1:5QRxNnVsaJP6NAse0UdkRgL3zHMvCRRkrDVLNdNpdy4=
golang.org/x/crypto v0.0.0-20211115234514-b4de73f9ece8/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 h1:/pEO3GD/ABYAjuakUS6xSEmmlyVS4kxBNkeA9tLJiTI=
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
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=
@ -756,6 +823,8 @@ golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+o
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d h1:RNPAfi2nHY7C2srAV8A49jpsYr0ADedCk1wq6fTMTvs=
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
golang.org/x/image v0.0.0-20211028202545-6944b10bf410 h1:hTftEOvwiOq2+O8k2D5/Q7COC7k5Qcrgc2TFURJYnvQ=
golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
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=
@ -803,6 +872,7 @@ golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@ -830,9 +900,13 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210913180222-943fd674d43e h1:+b/22bPvDYt4NPDcy4xAGCmON713ONAWFeY3Z7I3tR8=
golang.org/x/net v0.0.0-20210913180222-943fd674d43e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
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=
@ -845,12 +919,16 @@ golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210810183815-faf39c7919d5 h1:Ati8dO7+U7mxpkPSxBZQEvzHVUYB/MqCklCN8ig5w/o=
golang.org/x/oauth2 v0.0.0-20210810183815-faf39c7919d5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210817223510-7df4dd6e12ab h1:llrcWN/wOwO+6gAyfBzxb5hZ+c3mriU/0+KNgYu6adA=
golang.org/x/oauth2 v0.0.0-20210817223510-7df4dd6e12ab/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f h1:Qmd2pbz05z7z6lm0DrgQVVPuBm92jqujBKMHMOlOQEw=
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1 h1:B333XXssMuKQeBwiNODx4TupZy7bf4sxFZnN2ZOcvUE=
golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20211028175245-ba495a64dcb5 h1:v79phzBz03tsVCUTbvTBmmC3CUXF5mKYt7DA4ZVldpM=
golang.org/x/oauth2 v0.0.0-20211028175245-ba495a64dcb5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg=
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
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=
@ -886,8 +964,12 @@ golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -895,6 +977,7 @@ golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -927,29 +1010,69 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e h1:WUoyKPm6nCo1BnNUvPGnFG3T5DUVem42yDJZZ4CNxMA=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210816032535-30e4713e60e3 h1:7hHxyYeKyS0AU/brXAMuc+9BxCO/a4vL1DoUVLDTVIo=
golang.org/x/sys v0.0.0-20210816032535-30e4713e60e3/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71 h1:ikCpsnYR+Ew0vu99XlDp55lGgDJdIMx3f4a18jfse/s=
golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912 h1:uCLL3g5wH2xjxVREVuAbP9JM5PPKjRbXKRa6IBjkzmU=
golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210817142637-7d9622a276b7 h1:lQ8Btl/sJr2+f4ql7ffKUKfnV0BsgsICvm0oEeINAQY=
golang.org/x/sys v0.0.0-20210817142637-7d9622a276b7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210817190340-bfb29a6856f2 h1:c8PlLMqBbOHoqtjteWm5/kbe6rNY2pbRfbIMVnepueo=
golang.org/x/sys v0.0.0-20210817190340-bfb29a6856f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210818153620-00dd8d7831e7 h1:/bmDWM82ZX7TawqxuI8kVjKI0TXHdSY6pHJArewwHtU=
golang.org/x/sys v0.0.0-20210818153620-00dd8d7831e7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c h1:Lyn7+CqXIiC+LOR9aHD6jDK+hPcmAuCfuXztd1v4w1Q=
golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210820121016-41cdb8703e55 h1:rw6UNGRMfarCepjI8qOepea/SXwIBVfTKjztZ5gBbq4=
golang.org/x/sys v0.0.0-20210820121016-41cdb8703e55/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211015200801-69063c4bb744 h1:KzbpndAYEM+4oHRp9JmB2ewj0NHHxO3Z0g7Gus2O1kk=
golang.org/x/sys v0.0.0-20211015200801-69063c4bb744/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 h1:id054HUawV2/6IGm2IV8KZQjqtwAOo2CYlOToYqa0d0=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211020064051-0ec99a608a1b h1:byBDhtWGQmWDrv1MlEv/BzGRMkw36h9QqsNnZQcDhRw=
golang.org/x/sys v0.0.0-20211020064051-0ec99a608a1b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211020174200-9d6173849985 h1:LOlKVhfDyahgmqa97awczplwkjzNaELFg3zRIJ13RYo=
golang.org/x/sys v0.0.0-20211020174200-9d6173849985/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211022215931-8e5104632af7 h1:e2q1CMOFXDvurT2sa2yhJAkuA2n8Rd9tMDd7Tcfvs6M=
golang.org/x/sys v0.0.0-20211022215931-8e5104632af7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211023085530-d6a326fbbf70 h1:SeSEfdIxyvwGJliREIJhRPPXvW6sDlLT+UQ3B0hD0NA=
golang.org/x/sys v0.0.0-20211023085530-d6a326fbbf70/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211025112917-711f33c9992c h1:i4MLwL3EbCgobekQtkVW94UBSPLMadfEGtKq+CAFsEU=
golang.org/x/sys v0.0.0-20211025112917-711f33c9992c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359 h1:2B5p2L5IfGiD7+b9BOoRMC6DgObAVZV+Fsp050NqXik=
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211029165221-6e7872819dc8 h1:M69LAlWZCshgp0QSzyDcSsSIejIEeuaCVpmwcKwyLMk=
golang.org/x/sys v0.0.0-20211029165221-6e7872819dc8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211030160813-b3129d9d1021 h1:giLT+HuUP/gXYrG2Plg9WTjj4qhfgaW424ZIFog3rlk=
golang.org/x/sys v0.0.0-20211030160813-b3129d9d1021/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211031064116-611d5d643895 h1:iaNpwpnrgL5jzWS0vCNnfa8HqzxveCFpFx3uC/X4Tps=
golang.org/x/sys v0.0.0-20211031064116-611d5d643895/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211101204403-39c9dd37992c h1:rnNohYBMnXA07uGnZ9CSWNhIu4Gob4FqWS43lLqZ2sU=
golang.org/x/sys v0.0.0-20211101204403-39c9dd37992c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211102061401-a2f17f7b995c h1:QOfDMdrf/UwlVR0UBq2Mpr58UzNtvgJRXA4BgPfFACs=
golang.org/x/sys v0.0.0-20211102061401-a2f17f7b995c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211102192858-4dd72447c267 h1:7zYaz3tjChtpayGDzu6H0hDAUM5zIGA2XW7kRNgQ0jc=
golang.org/x/sys v0.0.0-20211102192858-4dd72447c267/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211103184734-ae416a5f93c7 h1:wQUOddybiV2Rfc8FX691KCOx5yEoZlfwpBjtKV6huYo=
golang.org/x/sys v0.0.0-20211103184734-ae416a5f93c7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b h1:1VkfZQv42XQlA/jchYumAnv1UPo6RgF9rJFkTgZIxO4=
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c h1:+8miTPjMCTXwih7BQmvWwd0PjdBZq2MKp/qQaahSzEM=
golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211106132015-ebca88c72f68 h1:Ywe/f3fNleF8I6F6qv3MeFoSZ6CTf2zBMMa/7qVML8M=
golang.org/x/sys v0.0.0-20211106132015-ebca88c72f68/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211107104306-e0b2ad06fe42 h1:G2DDmludOQZoWbpCr7OKDxnl478ZBGMcOhrv+ooX/Q4=
golang.org/x/sys v0.0.0-20211107104306-e0b2ad06fe42/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211109184856-51b60fd695b3 h1:T6tyxxvHMj2L1R2kZg0uNMpS8ZhB9lRa9XRGTCSA65w=
golang.org/x/sys v0.0.0-20211109184856-51b60fd695b3/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211110154304-99a53858aa08 h1:WecRHqgE09JBkh/584XIE6PMz5KKE/vER4izNUi30AQ=
golang.org/x/sys v0.0.0-20211110154304-99a53858aa08/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211111213525-f221eed1c01e h1:zeJt6jBtVDK23XK9QXcmG0FvO0elikp0dYZQZOeL1y0=
golang.org/x/sys v0.0.0-20211111213525-f221eed1c01e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211113001501-0c823b97ae02 h1:7NCfEGl0sfUojmX78nK9pBJuUlSZWEJA/TwASvfiPLo=
golang.org/x/sys v0.0.0-20211113001501-0c823b97ae02/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c h1:DHcbWVXeY+0Y8HHKR+rbLwnoh2F4tNCY7rTiHJ30RmA=
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1 h1:kwrAHlwJ0DUBZwQ238v+Uod/3eZ8B2K5rYsUHBQvzmI=
golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b h1:9zKuko04nR4gjZ4+DNjHqRlAJqbJETHwiNKDqTfOjfE=
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
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=
@ -957,8 +1080,9 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
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 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
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=
@ -985,6 +1109,7 @@ golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgw
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191109212701-97ad0ed33101/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@ -1024,8 +1149,12 @@ golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.2 h1:kRBLX7v7Af8W7Gdbbc908OJcdgtK8bOz9Uaj8/F1ACA=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -1056,6 +1185,12 @@ google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjR
google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8=
google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=
google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=
google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=
google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=
google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@ -1106,7 +1241,18 @@ google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6D
google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=
google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
@ -1127,7 +1273,13 @@ google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA5
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@ -1139,8 +1291,9 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
@ -1158,8 +1311,9 @@ gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AW
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.63.2 h1:tGK/CyBg7SMzb60vP1M03vNZ3VDu3wGQJwn7Sxi9r3c=
gopkg.in/ini.v1 v1.63.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w=
gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=

View File

@ -74,9 +74,19 @@ var (
}
)
func runCmdWithOutput(name string, arg ...string) (output []byte, err error) {
cmd := exec.Command(name, arg...)
output, err = cmd.Output()
if err != nil {
ee := err.(*exec.ExitError)
return nil, fmt.Errorf("error running command: %s, %s", string(ee.Stderr), err)
}
return output, nil
}
func setVersion() {
versionCmd := exec.Command("git", "describe", "--tags", "--always", "--abbrev=10")
version, err := versionCmd.Output()
version, err := runCmdWithOutput("git", "describe", "--tags", "--always", "--abbrev=10")
if err != nil {
fmt.Printf("Error getting version: %s\n", err)
os.Exit(1)
@ -117,8 +127,7 @@ func setExecutable() {
}
func setApiPackages() {
cmd := exec.Command("go", "list", "all")
pkgs, err := cmd.Output()
pkgs, err := runCmdWithOutput("go", "list", "all")
if err != nil {
fmt.Printf("Error getting packages: %s\n", err)
os.Exit(1)
@ -145,8 +154,7 @@ func setRootPath() {
func setGoFiles() {
// GOFILES := $(shell find . -name "*.go" -type f ! -path "*/bindata.go")
cmd := exec.Command("find", ".", "-name", "*.go", "-type", "f", "!", "-path", "*/bindata.go")
files, err := cmd.Output()
files, err := runCmdWithOutput("find", ".", "-name", "*.go", "-type", "f", "!", "-path", "*/bindata.go")
if err != nil {
fmt.Printf("Error getting go files: %s\n", err)
os.Exit(1)
@ -170,7 +178,6 @@ func initVars() {
setVersion()
setBinLocation()
setPkgVersion()
setApiPackages()
setGoFiles()
Ldflags = `-X "` + PACKAGE + `/pkg/version.Version=` + VersionNumber + `" -X "main.Tags=` + Tags + `"`
}
@ -340,6 +347,7 @@ type Test mg.Namespace
// Runs all tests except integration tests
func (Test) Unit() {
mg.Deps(initVars)
setApiPackages()
// We run everything sequentially and not in parallel to prevent issues with real test databases
args := append([]string{"test", Goflags[0], "-p", "1", "-timeout", "20m"}, ApiPackages...)
runAndStreamOutput("go", args...)

View File

@ -26,7 +26,7 @@ import (
"github.com/laurent22/ical-go"
)
func GetCaldavTodosForTasks(list *models.List, listTasks []*models.Task) string {
func GetCaldavTodosForTasks(list *models.ListWithTasksAndBuckets, listTasks []*models.TaskWithComments) string {
// Make caldav todos from Vikunja todos
var caldavtodos []*Todo

View File

@ -103,7 +103,7 @@ func getUserFromArg(s *xorm.Session, arg string) *user.User {
log.Fatalf("Invalid user id: %s", err)
}
u, err := user.GetUserByID(s, id)
u, err := user.GetUserWithEmail(s, &user.User{ID: id})
if err != nil {
log.Fatalf("Could not get user: %s", err)
}

View File

@ -37,6 +37,7 @@ type Key string
const (
// #nosec
ServiceJWTSecret Key = `service.JWTSecret`
ServiceJWTTTL Key = `service.jwtttl`
ServiceInterface Key = `service.interface`
ServiceUnixSocket Key = `service.unixsocket`
ServiceUnixSocketMode Key = `service.unixsocketmode`
@ -226,6 +227,7 @@ func InitDefaultConfig() {
// Service
ServiceJWTSecret.setDefault(random)
ServiceJWTTTL.setDefault(259200)
ServiceInterface.setDefault(":3456")
ServiceUnixSocket.setDefault("")
ServiceFrontendurl.setDefault("")

View File

@ -22,6 +22,8 @@ import (
"strconv"
"time"
"xorm.io/xorm"
"code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/log"
@ -77,7 +79,18 @@ func Create(f io.Reader, realname string, realsize uint64, a web.Auth) (file *Fi
// CreateWithMime creates a new file from an FileHeader and sets its mime type
func CreateWithMime(f io.Reader, realname string, realsize uint64, a web.Auth, mime string) (file *File, err error) {
s := db.NewSession()
defer s.Close()
file, err = CreateWithMimeAndSession(s, f, realname, realsize, a, mime)
if err != nil {
_ = s.Rollback()
return
}
return
}
func CreateWithMimeAndSession(s *xorm.Session, f io.Reader, realname string, realsize uint64, a web.Auth, mime string) (file *File, err error) {
// Get and parse the configured file size
var maxSize datasize.ByteSize
err = maxSize.UnmarshalText([]byte(config.FilesMaxSize.GetString()))
@ -96,21 +109,13 @@ func CreateWithMime(f io.Reader, realname string, realsize uint64, a web.Auth, m
Mime: mime,
}
s := db.NewSession()
defer s.Close()
_, err = s.Insert(file)
if err != nil {
_ = s.Rollback()
return
}
// Save the file to storage with its new ID as path
err = file.Save(f)
if err != nil {
_ = s.Rollback()
return
}
return
}

View File

@ -97,6 +97,7 @@ func FullInit() {
user.RegisterTokenCleanupCron()
user.RegisterDeletionNotificationCron()
models.RegisterUserDeletionCron()
models.RegisterOldExportCleanupCron()
// Start processing events
go func() {

View File

@ -0,0 +1,33 @@
// 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 integrations
import (
"net/http"
"testing"
"code.vikunja.io/api/pkg/routes"
"github.com/stretchr/testify/assert"
)
func TestHealthcheck(t *testing.T) {
t.Run("healthcheck", func(t *testing.T) {
rec, err := newTestRequest(t, http.MethodGet, routes.HealthcheckHandler, ``, nil, nil)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), "OK")
})
}

View File

@ -0,0 +1,43 @@
// 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 migration
import (
"src.techknowlogick.com/xormigrate"
"xorm.io/xorm"
)
type users20210829194722 struct {
ExportFileID int64 `xorm:"bigint null" json:"-"`
}
func (users20210829194722) TableName() string {
return "users"
}
func init() {
migrations = append(migrations, &xormigrate.Migration{
ID: "20210829194722",
Description: "Add data export file id to users",
Migrate: func(tx *xorm.Engine) error {
return tx.Sync2(users20210829194722{})
},
Rollback: func(tx *xorm.Engine) error {
return nil
},
})
}

View File

@ -21,6 +21,16 @@ import (
"code.vikunja.io/web"
)
// DataExportRequestEvent represents a DataExportRequestEvent event
type DataExportRequestEvent struct {
User *user.User
}
// Name defines the name for DataExportRequestEvent
func (t *DataExportRequestEvent) Name() string {
return "user.export.request"
}
/////////////////
// Task Events //
/////////////////
@ -257,3 +267,13 @@ type TeamDeletedEvent struct {
func (t *TeamDeletedEvent) Name() string {
return "team.deleted"
}
// UserDataExportRequestedEvent represents a UserDataExportRequestedEvent event
type UserDataExportRequestedEvent struct {
User *user.User
}
// Name defines the name for UserDataExportRequestedEvent
func (t *UserDataExportRequestedEvent) Name() string {
return "user.export.requested"
}

366
pkg/models/export.go Normal file
View File

@ -0,0 +1,366 @@
// 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 models
import (
"archive/zip"
"encoding/json"
"fmt"
"io"
"os"
"strconv"
"time"
"code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/cron"
"code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/files"
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/notifications"
"code.vikunja.io/api/pkg/user"
"code.vikunja.io/api/pkg/utils"
"code.vikunja.io/api/pkg/version"
"xorm.io/xorm"
)
func ExportUserData(s *xorm.Session, u *user.User) (err error) {
exportDir := config.FilesBasePath.GetString() + "/user-export-tmp/"
err = os.MkdirAll(exportDir, 0700)
if err != nil {
return err
}
tmpFilename := exportDir + strconv.FormatInt(u.ID, 10) + "_" + time.Now().Format("2006-01-02_15-03-05") + ".zip"
// Open zip
dumpFile, err := os.Create(tmpFilename)
if err != nil {
return fmt.Errorf("error opening dump file: %s", err)
}
defer dumpFile.Close()
dumpWriter := zip.NewWriter(dumpFile)
defer dumpWriter.Close()
// Get the data
err = exportListsAndTasks(s, u, dumpWriter)
if err != nil {
return err
}
// Task attachment files
err = exportTaskAttachments(s, u, dumpWriter)
if err != nil {
return err
}
// Saved filters
err = exportSavedFilters(s, u, dumpWriter)
if err != nil {
return err
}
// Background files
err = exportListBackgrounds(s, u, dumpWriter)
if err != nil {
return err
}
// Vikunja Version
err = utils.WriteBytesToZip("VERSION", []byte(version.Version), dumpWriter)
if err != nil {
return err
}
// If we reuse the same file again, saving it as a file in Vikunja will save it as a file with 0 bytes in size.
// Closing and reopening does work.
dumpWriter.Close()
dumpFile.Close()
exported, err := os.Open(tmpFilename)
if err != nil {
return err
}
stat, err := exported.Stat()
if err != nil {
return err
}
exportFile, err := files.CreateWithMimeAndSession(s, exported, tmpFilename, uint64(stat.Size()), u, "application/zip")
if err != nil {
return err
}
// Save the file id with the user
u.ExportFileID = exportFile.ID
_, err = s.Cols("export_file_id").Update(u)
if err != nil {
return
}
// Remove the old file
err = os.Remove(exported.Name())
if err != nil {
return err
}
// Send a notification
return notifications.Notify(u, &DataExportReadyNotification{
User: u,
})
}
func exportListsAndTasks(s *xorm.Session, u *user.User, wr *zip.Writer) (err error) {
namspaces, _, _, err := (&Namespace{IsArchived: true}).ReadAll(s, u, "", -1, 0)
if err != nil {
return err
}
namespaceIDs := []int64{}
namespaces := []*NamespaceWithListsAndTasks{}
listMap := make(map[int64]*ListWithTasksAndBuckets)
listIDs := []int64{}
for _, n := range namspaces.([]*NamespaceWithLists) {
if n.ID < 1 {
// Don't include filters
continue
}
nn := &NamespaceWithListsAndTasks{
Namespace: n.Namespace,
Lists: []*ListWithTasksAndBuckets{},
}
for _, l := range n.Lists {
ll := &ListWithTasksAndBuckets{
List: *l,
BackgroundFileID: l.BackgroundFileID,
Tasks: []*TaskWithComments{},
}
nn.Lists = append(nn.Lists, ll)
listMap[l.ID] = ll
listIDs = append(listIDs, l.ID)
}
namespaceIDs = append(namespaceIDs, n.ID)
namespaces = append(namespaces, nn)
}
if len(namespaceIDs) == 0 {
return nil
}
// Get all lists
lists, err := getListsForNamespaces(s, namespaceIDs, true)
if err != nil {
return err
}
tasks, _, _, err := getTasksForLists(s, lists, u, &taskOptions{
page: 0,
perPage: -1,
})
if err != nil {
return err
}
taskMap := make(map[int64]*TaskWithComments, len(tasks))
for _, t := range tasks {
taskMap[t.ID] = &TaskWithComments{
Task: *t,
}
if _, exists := listMap[t.ListID]; !exists {
log.Debugf("[User Data Export] List %d does not exist for task %d, omitting", t.ListID, t.ID)
continue
}
listMap[t.ListID].Tasks = append(listMap[t.ListID].Tasks, taskMap[t.ID])
}
comments := []*TaskComment{}
err = s.
Join("LEFT", "tasks", "tasks.id = task_comments.task_id").
In("tasks.list_id", listIDs).
Find(&comments)
if err != nil {
return
}
for _, c := range comments {
if _, exists := taskMap[c.TaskID]; !exists {
log.Debugf("[User Data Export] Task %d does not exist for comment %d, omitting", c.TaskID, c.ID)
continue
}
taskMap[c.TaskID].Comments = append(taskMap[c.TaskID].Comments, c)
}
buckets := []*Bucket{}
err = s.In("list_id", listIDs).Find(&buckets)
if err != nil {
return
}
for _, b := range buckets {
if _, exists := listMap[b.ListID]; !exists {
log.Debugf("[User Data Export] List %d does not exist for bucket %d, omitting", b.ListID, b.ID)
continue
}
listMap[b.ListID].Buckets = append(listMap[b.ListID].Buckets, b)
}
data, err := json.Marshal(namespaces)
if err != nil {
return err
}
return utils.WriteBytesToZip("data.json", data, wr)
}
func exportTaskAttachments(s *xorm.Session, u *user.User, wr *zip.Writer) (err error) {
lists, _, _, err := getRawListsForUser(
s,
&listOptions{
user: u,
page: -1,
},
)
if err != nil {
return err
}
tasks, _, _, err := getRawTasksForLists(s, lists, u, &taskOptions{page: -1})
if err != nil {
return err
}
taskIDs := []int64{}
for _, t := range tasks {
taskIDs = append(taskIDs, t.ID)
}
tas, err := getTaskAttachmentsByTaskIDs(s, taskIDs)
if err != nil {
return err
}
fs := make(map[int64]io.ReadCloser)
for _, ta := range tas {
if err := ta.File.LoadFileByID(); err != nil {
return err
}
fs[ta.FileID] = ta.File.File
}
return utils.WriteFilesToZip(fs, wr)
}
func exportSavedFilters(s *xorm.Session, u *user.User, wr *zip.Writer) (err error) {
filters, err := getSavedFiltersForUser(s, u)
if err != nil {
return err
}
data, err := json.Marshal(filters)
if err != nil {
return err
}
return utils.WriteBytesToZip("filters.json", data, wr)
}
func exportListBackgrounds(s *xorm.Session, u *user.User, wr *zip.Writer) (err error) {
lists, _, _, err := getRawListsForUser(
s,
&listOptions{
user: u,
page: -1,
},
)
if err != nil {
return err
}
fs := make(map[int64]io.ReadCloser)
for _, l := range lists {
if l.BackgroundFileID == 0 {
continue
}
bgFile := &files.File{
ID: l.BackgroundFileID,
}
err = bgFile.LoadFileByID()
if err != nil {
return
}
fs[l.BackgroundFileID] = bgFile.File
}
return utils.WriteFilesToZip(fs, wr)
}
func RegisterOldExportCleanupCron() {
const logPrefix = "[User Export Cleanup Cron] "
err := cron.Schedule("0 * * * *", func() {
s := db.NewSession()
defer s.Close()
users := []*user.User{}
err := s.Where("export_file_id IS NOT NULL AND export_file_id != ?", 0).Find(&users)
if err != nil {
log.Errorf(logPrefix+"Could not get users with export files: %s", err)
return
}
fileIDs := []int64{}
for _, u := range users {
fileIDs = append(fileIDs, u.ExportFileID)
}
fs := []*files.File{}
err = s.Where("created < ?", time.Now().Add(-time.Hour*24*7)).In("id", fileIDs).Find(&fs)
if err != nil {
log.Errorf(logPrefix+"Could not get users with export files: %s", err)
return
}
if len(fs) == 0 {
return
}
log.Debugf(logPrefix+"Removing %d old user data exports...", len(fs))
for _, f := range fs {
err = f.Delete()
if err != nil {
log.Errorf(logPrefix+"Could not remove user export file %d: %s", f.ID, err)
return
}
}
_, err = s.In("export_file_id", fileIDs).Cols("export_file_id").Update(&user.User{})
if err != nil {
log.Errorf(logPrefix+"Could not update user export file state: %s", err)
return
}
log.Debugf(logPrefix+"Removed %d old user data exports...", len(fs))
})
if err != nil {
log.Fatalf("Could not old export cleanup cron: %s", err)
}
}

View File

@ -51,12 +51,6 @@ type List struct {
// The user who created this list.
Owner *user.User `xorm:"-" json:"owner" valid:"-"`
// An array of tasks which belong to the list.
// Deprecated: you should use the dedicated task list endpoint because it has support for pagination and filtering
Tasks []*Task `xorm:"-" json:"-"`
// Only used for migration.
Buckets []*Bucket `xorm:"-" json:"-"`
// Whether or not a list is archived.
IsArchived bool `xorm:"not null default false" json:"is_archived" query:"is_archived"`
@ -85,6 +79,15 @@ type List struct {
web.Rights `xorm:"-" json:"-"`
}
type ListWithTasksAndBuckets struct {
List
// An array of tasks which belong to the list.
Tasks []*TaskWithComments `xorm:"-" json:"tasks"`
// Only used for migration.
Buckets []*Bucket `xorm:"-" json:"buckets"`
BackgroundFileID int64 `xorm:"null" json:"background_file_id"`
}
// TableName returns a better name for the lists table
func (l *List) TableName() string {
return "lists"
@ -537,12 +540,10 @@ func (l *List) CheckIsArchived(s *xorm.Session) (err error) {
return nil
}
// CreateOrUpdateList updates a list or creates it if it doesn't exist
func CreateOrUpdateList(s *xorm.Session, list *List, auth web.Auth) (err error) {
func checkListBeforeUpdateOrDelete(s *xorm.Session, list *List) error {
// Check if the namespace exists
if list.NamespaceID != 0 && list.NamespaceID != FavoritesPseudoNamespace.ID {
_, err = GetNamespaceByID(s, list.NamespaceID)
if list.NamespaceID > 0 {
_, err := GetNamespaceByID(s, list.NamespaceID)
if err != nil {
return err
}
@ -562,65 +563,115 @@ func CreateOrUpdateList(s *xorm.Session, list *List, auth web.Auth) (err error)
}
}
if list.ID == 0 {
_, err = s.Insert(list)
if err != nil {
return
}
return nil
}
list.Position = calculateDefaultPosition(list.ID, list.Position)
_, err = s.Where("id = ?", list.ID).Update(list)
if err != nil {
return
}
if list.IsFavorite {
if err := addToFavorites(s, list.ID, auth, FavoriteKindList); err != nil {
return err
}
}
} else {
// We need to specify the cols we want to update here to be able to un-archive lists
colsToUpdate := []string{
"title",
"is_archived",
"identifier",
"hex_color",
"background_file_id",
"position",
}
if list.Description != "" {
colsToUpdate = append(colsToUpdate, "description")
}
func CreateList(s *xorm.Session, list *List, auth web.Auth) (err error) {
err = list.CheckIsArchived(s)
if err != nil {
return err
}
wasFavorite, err := isFavorite(s, list.ID, auth, FavoriteKindList)
if err != nil {
return err
}
if list.IsFavorite && !wasFavorite {
if err := addToFavorites(s, list.ID, auth, FavoriteKindList); err != nil {
return err
}
}
doer, err := user.GetFromAuth(auth)
if err != nil {
return err
}
if !list.IsFavorite && wasFavorite {
if err := removeFromFavorite(s, list.ID, auth, FavoriteKindList); err != nil {
return err
}
}
list.OwnerID = doer.ID
list.Owner = doer
list.ID = 0 // Otherwise only the first time a new list would be created
_, err = s.
ID(list.ID).
Cols(colsToUpdate...).
Update(list)
if err != nil {
err = checkListBeforeUpdateOrDelete(s, list)
if err != nil {
return
}
_, err = s.Insert(list)
if err != nil {
return
}
list.Position = calculateDefaultPosition(list.ID, list.Position)
_, err = s.Where("id = ?", list.ID).Update(list)
if err != nil {
return
}
if list.IsFavorite {
if err := addToFavorites(s, list.ID, auth, FavoriteKindList); err != nil {
return err
}
}
// Create a new first bucket for this list
b := &Bucket{
ListID: list.ID,
Title: "Backlog",
}
err = b.Create(s, auth)
if err != nil {
return
}
return events.Dispatch(&ListCreatedEvent{
List: list,
Doer: doer,
})
}
func UpdateList(s *xorm.Session, list *List, auth web.Auth, updateListBackground bool) (err error) {
err = checkListBeforeUpdateOrDelete(s, list)
if err != nil {
return
}
// We need to specify the cols we want to update here to be able to un-archive lists
colsToUpdate := []string{
"title",
"is_archived",
"identifier",
"hex_color",
"position",
}
if list.Description != "" {
colsToUpdate = append(colsToUpdate, "description")
}
if updateListBackground {
colsToUpdate = append(colsToUpdate, "background_file_id")
}
wasFavorite, err := isFavorite(s, list.ID, auth, FavoriteKindList)
if err != nil {
return err
}
if list.IsFavorite && !wasFavorite {
if err := addToFavorites(s, list.ID, auth, FavoriteKindList); err != nil {
return err
}
}
if !list.IsFavorite && wasFavorite {
if err := removeFromFavorite(s, list.ID, auth, FavoriteKindList); err != nil {
return err
}
}
_, err = s.
ID(list.ID).
Cols(colsToUpdate...).
Update(list)
if err != nil {
return err
}
err = events.Dispatch(&ListUpdatedEvent{
List: list,
Doer: auth,
})
if err != nil {
return err
}
l, err := GetListSimpleByID(s, list.ID)
if err != nil {
return err
@ -665,15 +716,7 @@ func (l *List) Update(s *xorm.Session, a web.Auth) (err error) {
return nil
}
err = CreateOrUpdateList(s, l, a)
if err != nil {
return err
}
return events.Dispatch(&ListUpdatedEvent{
List: l,
Doer: a,
})
return UpdateList(s, l, a, false)
}
func updateListLastUpdated(s *xorm.Session, list *List) error {
@ -706,39 +749,12 @@ func updateListByTaskID(s *xorm.Session, taskID int64) (err error) {
// @Failure 500 {object} models.Message "Internal error"
// @Router /namespaces/{namespaceID}/lists [put]
func (l *List) Create(s *xorm.Session, a web.Auth) (err error) {
err = l.CheckIsArchived(s)
if err != nil {
return err
}
doer, err := user.GetFromAuth(a)
if err != nil {
return err
}
l.OwnerID = doer.ID
l.Owner = doer
l.ID = 0 // Otherwise only the first time a new list would be created
err = CreateOrUpdateList(s, l, a)
err = CreateList(s, l, a)
if err != nil {
return
}
// Create a new first bucket for this list
b := &Bucket{
ListID: l.ID,
Title: "Backlog",
}
err = b.Create(s, a)
if err != nil {
return
}
return events.Dispatch(&ListCreatedEvent{
List: l,
Doer: doer,
})
return l.ReadOne(s, a)
}
// Delete implements the delete method of CRUDable

View File

@ -75,7 +75,7 @@ func (ld *ListDuplicate) Create(s *xorm.Session, doer web.Auth) (err error) {
ld.List.Identifier = "" // Reset the identifier to trigger regenerating a new one
// Set the owner to the current user
ld.List.OwnerID = doer.GetID()
if err := CreateOrUpdateList(s, ld.List, doer); err != nil {
if err := CreateList(s, ld.List, doer); err != nil {
// If there is no available unique list identifier, just reset it.
if IsErrListIdentifierIsNotUnique(err) {
ld.List.Identifier = ""

View File

@ -50,6 +50,7 @@ func RegisterListeners() {
events.RegisterListener((&TaskCommentUpdatedEvent{}).Name(), &HandleTaskCommentEditMentions{})
events.RegisterListener((&TaskCreatedEvent{}).Name(), &HandleTaskCreateMentions{})
events.RegisterListener((&TaskUpdatedEvent{}).Name(), &HandleTaskUpdatedMentions{})
events.RegisterListener((&UserDataExportRequestedEvent{}).Name(), &HandleUserDataExport{})
}
//////
@ -246,6 +247,11 @@ func (s *SendTaskAssignedNotification) Handle(msg *message.Message) (err error)
log.Debugf("Sending task assigned notifications to %d subscribers for task %d", len(subscribers), event.Task.ID)
task, err := GetTaskByIDSimple(sess, event.Task.ID)
if err != nil {
return err
}
for _, subscriber := range subscribers {
if subscriber.UserID == event.Doer.ID {
continue
@ -253,7 +259,7 @@ func (s *SendTaskAssignedNotification) Handle(msg *message.Message) (err error)
n := &TaskAssignedNotification{
Doer: event.Doer,
Task: event.Task,
Task: &task,
Assignee: event.Assignee,
}
err = notifications.Notify(subscriber.User, n)
@ -562,3 +568,41 @@ func (s *SendTeamMemberAddedNotification) Handle(msg *message.Message) (err erro
Team: event.Team,
})
}
// HandleUserDataExport represents a listener
type HandleUserDataExport struct {
}
// Name defines the name for the HandleUserDataExport listener
func (s *HandleUserDataExport) Name() string {
return "handle.user.data.export"
}
// Handle is executed when the event HandleUserDataExport listens on is fired
func (s *HandleUserDataExport) Handle(msg *message.Message) (err error) {
event := &UserDataExportRequestedEvent{}
err = json.Unmarshal(msg.Payload, event)
if err != nil {
return err
}
log.Debugf("Starting to export user data for user %d...", event.User.ID)
sess := db.NewSession()
defer sess.Close()
err = sess.Begin()
if err != nil {
return
}
err = ExportUserData(sess, event.User)
if err != nil {
_ = sess.Rollback()
return
}
log.Debugf("Done exporting user data for user %d...", event.User.ID)
err = sess.Commit()
return err
}

View File

@ -187,6 +187,11 @@ type NamespaceWithLists struct {
Lists []*List `xorm:"-" json:"lists"`
}
type NamespaceWithListsAndTasks struct {
Namespace
Lists []*ListWithTasksAndBuckets `xorm:"-" json:"lists"`
}
func makeNamespaceSlice(namespaces map[int64]*NamespaceWithLists, userMap map[int64]*user.User, subscriptions map[int64]*Subscription) []*NamespaceWithLists {
all := make([]*NamespaceWithLists, 0, len(namespaces))
for _, n := range namespaces {

View File

@ -112,7 +112,7 @@ type TaskAssignedNotification struct {
func (n *TaskAssignedNotification) ToMail() *notifications.Mail {
return notifications.NewMail().
Subject(n.Task.Title+"("+n.Task.GetFullIdentifier()+")"+" has been assigned to "+n.Assignee.GetName()).
Line(n.Doer.GetName()+" has assigned this task to "+n.Assignee.GetName()).
Line(n.Doer.GetName()+" has assigned this task to "+n.Assignee.GetName()+".").
Action("View Task", n.Task.GetFrontendURL())
}
@ -302,3 +302,29 @@ func (n *UserMentionedInTaskNotification) ToDB() interface{} {
func (n *UserMentionedInTaskNotification) Name() string {
return "task.mentioned"
}
// DataExportReadyNotification represents a DataExportReadyNotification notification
type DataExportReadyNotification struct {
User *user.User `json:"user"`
}
// ToMail returns the mail notification for DataExportReadyNotification
func (n *DataExportReadyNotification) ToMail() *notifications.Mail {
return notifications.NewMail().
Subject("Your Vikunja Data Export is ready").
Greeting("Hi "+n.User.GetName()+",").
Line("Your Vikunja Data Export is ready for you to download. Click the button below to download it:").
Action("Download", config.ServiceFrontendurl.GetString()+"user/export/download").
Line("The download will be available for the next 7 days.").
Line("Have a nice day!")
}
// ToDB returns the DataExportReadyNotification notification in a format which can be saved in the db
func (n *DataExportReadyNotification) ToDB() interface{} {
return nil
}
// Name returns the name of the notification
func (n *DataExportReadyNotification) Name() string {
return "data.export.ready"
}

View File

@ -149,6 +149,11 @@ func (tf *TaskCollection) ReadAll(s *xorm.Session, a web.Auth, search string, pa
return nil, 0, 0, err
}
sf.Filters.SortByArr = tf.SortByArr
sf.Filters.SortBy = tf.SortBy
sf.Filters.OrderByArr = tf.OrderByArr
sf.Filters.OrderBy = tf.OrderBy
return sf.getTaskCollection().ReadAll(s, a, search, page, perPage)
}

View File

@ -1081,6 +1081,24 @@ func TestTaskCollection_ReadAll(t *testing.T) {
task2,
},
},
{
name: "saved filter with sort order",
fields: fields{
ListID: -2,
SortBy: []string{"title", "id"},
OrderBy: []string{"desc", "asc"},
},
args: args{
a: &user.User{ID: 1},
},
want: []*Task{
task9,
task8,
task7,
task6,
task5,
},
},
}
for _, tt := range tests {

View File

@ -129,6 +129,11 @@ type Task struct {
web.Rights `xorm:"-" json:"-"`
}
type TaskWithComments struct {
Task
Comments []*TaskComment `xorm:"-" json:"comments"`
}
// TableName returns the table name for listtasks
func (Task) TableName() string {
return "tasks"

View File

@ -54,9 +54,13 @@ func deleteUsers() {
return
}
log.Debugf("Found %d users scheduled for deletion", len(users))
now := time.Now()
for _, u := range users {
if u.DeletionScheduledAt.Before(time.Now()) {
log.Debugf("User %d is not yet scheduled for deletion.", u.ID)
if u.DeletionScheduledAt.Before(now) {
log.Debugf("User %d is not yet scheduled for deletion. Scheduled at %s, now is %s", u.ID, u.DeletionScheduledAt, now)
continue
}
@ -73,6 +77,8 @@ func deleteUsers() {
return
}
log.Debugf("Deleted user %d", u.ID)
err = s.Commit()
if err != nil {
log.Errorf("Could not commit transaction: %s", err)
@ -167,7 +173,9 @@ func DeleteUser(s *xorm.Session, u *user.User) (err error) {
return err
}
return notifications.Notify(u, &user.AccountDeletedNotification{})
return notifications.Notify(u, &user.AccountDeletedNotification{
User: u,
})
}
func ensureNamespaceAdminUser(s *xorm.Session, n *Namespace) (hadUsers bool, err error) {

View File

@ -24,6 +24,7 @@ import (
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/user"
"code.vikunja.io/web"
"github.com/golang-jwt/jwt/v4"
"github.com/labstack/echo/v4"
)
@ -51,18 +52,22 @@ func NewUserAuthTokenResponse(u *user.User, c echo.Context) error {
}
// NewUserJWTAuthtoken generates and signes a new jwt token for a user. This is a global function to be able to call it from integration tests.
func NewUserJWTAuthtoken(user *user.User) (token string, err error) {
func NewUserJWTAuthtoken(u *user.User) (token string, err error) {
t := jwt.New(jwt.SigningMethodHS256)
var ttl = time.Duration(config.ServiceJWTTTL.GetInt64())
var exp = time.Now().Add(time.Second * ttl).Unix()
// Set claims
claims := t.Claims.(jwt.MapClaims)
claims["type"] = AuthTypeUser
claims["id"] = user.ID
claims["username"] = user.Username
claims["email"] = user.Email
claims["exp"] = time.Now().Add(time.Hour * 72).Unix()
claims["name"] = user.Name
claims["emailRemindersEnabled"] = user.EmailRemindersEnabled
claims["id"] = u.ID
claims["username"] = u.Username
claims["email"] = u.Email
claims["exp"] = exp
claims["name"] = u.Name
claims["emailRemindersEnabled"] = u.EmailRemindersEnabled
claims["isLocalUser"] = u.Issuer == user.IssuerLocal
// Generate encoded token and send it as response.
return t.SignedString([]byte(config.ServiceJWTSecret.GetString()))
@ -72,6 +77,9 @@ func NewUserJWTAuthtoken(user *user.User) (token string, err error) {
func NewLinkShareJWTAuthtoken(share *models.LinkSharing) (token string, err error) {
t := jwt.New(jwt.SigningMethodHS256)
var ttl = time.Duration(config.ServiceJWTTTL.GetInt64())
var exp = time.Now().Add(time.Second * ttl).Unix()
// Set claims
claims := t.Claims.(jwt.MapClaims)
claims["type"] = AuthTypeLinkShare
@ -80,7 +88,8 @@ func NewLinkShareJWTAuthtoken(share *models.LinkSharing) (token string, err erro
claims["list_id"] = share.ListID
claims["right"] = share.Right
claims["sharedByID"] = share.SharedByID
claims["exp"] = time.Now().Add(time.Hour * 72).Unix()
claims["exp"] = exp
claims["isLocalUser"] = true // Link shares are always local
// Generate encoded token and send it as response.
return t.SignedString([]byte(config.ServiceJWTSecret.GetString()))

View File

@ -21,6 +21,7 @@ import (
"io/ioutil"
"net/http"
"strconv"
"strings"
"time"
"code.vikunja.io/api/pkg/config"
@ -64,7 +65,7 @@ func (g *Provider) GetAvatar(user *user.User, size int64) ([]byte, string, error
}
if !exists || needsRefetch {
log.Debugf("Gravatar for user %d with size %d not cached, requesting from gravatar...", user.ID, size)
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "https://www.gravatar.com/avatar/"+utils.Md5String(user.Email)+"?s="+sizeString+"&d=mp", nil)
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "https://www.gravatar.com/avatar/"+utils.Md5String(strings.ToLower(user.Email))+"?s="+sizeString+"&d=mp", nil)
if err != nil {
return nil, "", err
}

View File

@ -300,7 +300,7 @@ func RemoveListBackground(c echo.Context) error {
list.BackgroundFileID = 0
list.BackgroundInformation = nil
err = list.Update(s, auth)
err = models.UpdateList(s, list, auth, true)
if err != nil {
return err
}

View File

@ -163,14 +163,14 @@ func (p *Provider) Search(s *xorm.Session, search string, page int64) (result []
}
if existsForPage {
log.Debugf("Serving initial unsplash collection for page %d from cache, last updated at %v", page, emptySearchResult.lastCached)
log.Debugf("Serving initial wallpaper topic from unsplash for page %d from cache, last updated at %v", page, emptySearchResult.lastCached)
return emptySearchResult.images[page], nil
}
log.Debugf("Retrieving initial unsplash collection for page %d from unsplash api", page)
log.Debugf("Retrieving initial wallpaper topic from unsplash for page %d from unsplash api", page)
collectionResult := []*Photo{}
err = doGet("collections/317099/photos?page="+strconv.FormatInt(page, 10)+"&per_page=25&order_by=latest", &collectionResult)
err = doGet("topics/wallpapers/photos?page="+strconv.FormatInt(page, 10)+"&per_page=25&order_by=latest", &collectionResult)
if err != nil {
return
}

View File

@ -21,19 +21,15 @@ import (
"fmt"
"io"
"os"
"strconv"
"code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/files"
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/utils"
"code.vikunja.io/api/pkg/version"
"github.com/spf13/viper"
)
// Change to deflate to gain better compression
// see http://golang.org/pkg/archive/zip/#pkg-constants
const compressionUsed = zip.Deflate
// Dump creates a zip file with all vikunja files at filename
func Dump(filename string) error {
dumpFile, err := os.Create(filename)
@ -55,7 +51,7 @@ func Dump(filename string) error {
// Version
log.Info("Start dumping version file...")
err = writeBytesToZip("VERSION", []byte(version.Version), dumpWriter)
err = utils.WriteBytesToZip("VERSION", []byte(version.Version), dumpWriter)
if err != nil {
return fmt.Errorf("error saving version: %s", err)
}
@ -68,7 +64,7 @@ func Dump(filename string) error {
return fmt.Errorf("error saving database data: %s", err)
}
for t, d := range data {
err = writeBytesToZip("database/"+t+".json", d, dumpWriter)
err = utils.WriteBytesToZip("database/"+t+".json", d, dumpWriter)
if err != nil {
return fmt.Errorf("error writing database table %s: %s", t, err)
}
@ -81,21 +77,12 @@ func Dump(filename string) error {
if err != nil {
return fmt.Errorf("error saving file: %s", err)
}
for fid, file := range allFiles {
header := &zip.FileHeader{
Name: "files/" + strconv.FormatInt(fid, 10),
Method: compressionUsed,
}
w, err := dumpWriter.CreateHeader(header)
if err != nil {
return err
}
_, err = io.Copy(w, file)
if err != nil {
return fmt.Errorf("error writing file %d: %s", fid, err)
}
_ = file.Close()
err = utils.WriteFilesToZip(allFiles, dumpWriter)
if err != nil {
return err
}
log.Infof("Dumped files")
log.Info("Done creating dump")
@ -123,7 +110,7 @@ func writeFileToZip(filename string, writer *zip.Writer) error {
}
header.Name = info.Name()
header.Method = compressionUsed
header.Method = utils.CompressionUsed
w, err := writer.CreateHeader(header)
if err != nil {
@ -132,16 +119,3 @@ func writeFileToZip(filename string, writer *zip.Writer) error {
_, err = io.Copy(w, fileToZip)
return err
}
func writeBytesToZip(filename string, data []byte, writer *zip.Writer) (err error) {
header := &zip.FileHeader{
Name: filename,
Method: compressionUsed,
}
w, err := writer.CreateHeader(header)
if err != nil {
return err
}
_, err = w.Write(data)
return
}

View File

@ -33,6 +33,7 @@ import (
"code.vikunja.io/api/pkg/initialize"
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/migration"
"src.techknowlogick.com/xormigrate"
)
@ -194,6 +195,7 @@ func Restore(filename string) error {
///////
// Done
log.Infof("Done restoring dump.")
log.Infof("Restart Vikunja to make sure the new configuration file is applied.")
return nil
}

View File

@ -31,7 +31,7 @@ import (
// InsertFromStructure takes a fully nested Vikunja data structure and a user and then creates everything for this user
// (Namespaces, tasks, etc. Even attachments and relations.)
func InsertFromStructure(str []*models.NamespaceWithLists, user *user.User) (err error) {
func InsertFromStructure(str []*models.NamespaceWithListsAndTasks, user *user.User) (err error) {
s := db.NewSession()
defer s.Close()
@ -45,7 +45,7 @@ func InsertFromStructure(str []*models.NamespaceWithLists, user *user.User) (err
return s.Commit()
}
func insertFromStructure(s *xorm.Session, str []*models.NamespaceWithLists, user *user.User) (err error) {
func insertFromStructure(s *xorm.Session, str []*models.NamespaceWithListsAndTasks, user *user.User) (err error) {
log.Debugf("[creating structure] Creating %d namespaces", len(str))
@ -129,7 +129,7 @@ func insertFromStructure(s *xorm.Session, str []*models.NamespaceWithLists, user
// Create all tasks
for _, t := range tasks {
setBucketOrDefault(t)
setBucketOrDefault(&t.Task)
t.ListID = l.ID
err = t.Create(s, user)
@ -221,6 +221,15 @@ func insertFromStructure(s *xorm.Session, str []*models.NamespaceWithLists, user
}
log.Debugf("[creating structure] Associated task %d with label %d", t.ID, lb.ID)
}
for _, comment := range t.Comments {
comment.TaskID = t.ID
err = comment.Create(s, user)
if err != nil {
return
}
log.Debugf("[creating structure] Created new comment %d", comment.ID)
}
}
// All tasks brought their own bucket with them, therefore the newly created default bucket is just extra space

View File

@ -32,79 +32,95 @@ func TestInsertFromStructure(t *testing.T) {
}
t.Run("normal", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
testStructure := []*models.NamespaceWithLists{
testStructure := []*models.NamespaceWithListsAndTasks{
{
Namespace: models.Namespace{
Title: "Test1",
Description: "Lorem Ipsum",
},
Lists: []*models.List{
Lists: []*models.ListWithTasksAndBuckets{
{
Title: "Testlist1",
Description: "Something",
List: models.List{
Title: "Testlist1",
Description: "Something",
},
Buckets: []*models.Bucket{
{
ID: 1234,
Title: "Test Bucket",
},
},
Tasks: []*models.Task{
Tasks: []*models.TaskWithComments{
{
Title: "Task1",
Description: "Lorem",
Task: models.Task{
Title: "Task1",
Description: "Lorem",
},
},
{
Title: "Task with related tasks",
RelatedTasks: map[models.RelationKind][]*models.Task{
models.RelationKindSubtask: {
Task: models.Task{
Title: "Task with related tasks",
RelatedTasks: map[models.RelationKind][]*models.Task{
models.RelationKindSubtask: {
{
Title: "Related to task with related task",
Description: "As subtask",
},
},
},
},
},
{
Task: models.Task{
Title: "Task with attachments",
Attachments: []*models.TaskAttachment{
{
Title: "Related to task with related task",
Description: "As subtask",
File: &files.File{
Name: "testfile",
Size: 4,
FileContent: []byte{1, 2, 3, 4},
},
},
},
},
},
{
Title: "Task with attachments",
Attachments: []*models.TaskAttachment{
{
File: &files.File{
Name: "testfile",
Size: 4,
FileContent: []byte{1, 2, 3, 4},
Task: models.Task{
Title: "Task with labels",
Labels: []*models.Label{
{
Title: "Label1",
HexColor: "ff00ff",
},
{
Title: "Label2",
HexColor: "ff00ff",
},
},
},
},
{
Title: "Task with labels",
Labels: []*models.Label{
{
Title: "Label1",
HexColor: "ff00ff",
},
{
Title: "Label2",
HexColor: "ff00ff",
Task: models.Task{
Title: "Task with same label",
Labels: []*models.Label{
{
Title: "Label1",
HexColor: "ff00ff",
},
},
},
},
{
Title: "Task with same label",
Labels: []*models.Label{
{
Title: "Label1",
HexColor: "ff00ff",
},
Task: models.Task{
Title: "Task in a bucket",
BucketID: 1234,
},
},
{
Title: "Task in a bucket",
BucketID: 1234,
},
{
Title: "Task in a nonexisting bucket",
BucketID: 1111,
Task: models.Task{
Title: "Task in a nonexisting bucket",
BucketID: 1111,
},
},
},
},

View File

@ -0,0 +1,40 @@
// 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 handler
import (
"net/http"
"code.vikunja.io/api/pkg/modules/migration"
user2 "code.vikunja.io/api/pkg/user"
"code.vikunja.io/web/handler"
"github.com/labstack/echo/v4"
)
func status(ms migration.MigratorName, c echo.Context) error {
user, err := user2.GetCurrentUser(c)
if err != nil {
return handler.HandleHTTPError(err, c)
}
status, err := migration.GetMigrationStatus(ms, user)
if err != nil {
return handler.HandleHTTPError(err, c)
}
return c.JSON(http.StatusOK, status)
}

View File

@ -84,15 +84,5 @@ func (mw *MigrationWeb) Migrate(c echo.Context) error {
func (mw *MigrationWeb) Status(c echo.Context) error {
ms := mw.MigrationStruct()
user, err := user2.GetCurrentUser(c)
if err != nil {
return handler.HandleHTTPError(err, c)
}
status, err := migration.GetMigrationStatus(ms, user)
if err != nil {
return handler.HandleHTTPError(err, c)
}
return c.JSON(http.StatusOK, status)
return status(ms, c)
}

View File

@ -0,0 +1,79 @@
// 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 handler
import (
"net/http"
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/modules/migration"
user2 "code.vikunja.io/api/pkg/user"
"code.vikunja.io/web/handler"
"github.com/labstack/echo/v4"
)
type FileMigratorWeb struct {
MigrationStruct func() migration.FileMigrator
}
// RegisterRoutes registers all routes for migration
func (fw *FileMigratorWeb) RegisterRoutes(g *echo.Group) {
ms := fw.MigrationStruct()
g.GET("/"+ms.Name()+"/status", fw.Status)
g.PUT("/"+ms.Name()+"/migrate", fw.Migrate)
}
// Migrate calls the migration method
func (fw *FileMigratorWeb) Migrate(c echo.Context) error {
ms := fw.MigrationStruct()
// Get the user from context
user, err := user2.GetCurrentUser(c)
if err != nil {
return handler.HandleHTTPError(err, c)
}
file, err := c.FormFile("import")
if err != nil {
return err
}
src, err := file.Open()
if err != nil {
return err
}
defer src.Close()
// Do the migration
err = ms.Migrate(user, src, file.Size)
if err != nil {
return handler.HandleHTTPError(err, c)
}
err = migration.SetMigrationStatus(ms, user)
if err != nil {
return handler.HandleHTTPError(err, c)
}
return c.JSON(http.StatusOK, models.Message{Message: "Everything was migrated successfully."})
}
// Status returns whether or not a user has already done this migration
func (fw *FileMigratorWeb) Status(c echo.Context) error {
ms := fw.MigrationStruct()
return status(ms, c)
}

View File

@ -26,10 +26,22 @@ import (
// DownloadFile downloads a file and returns its contents
func DownloadFile(url string) (buf *bytes.Buffer, err error) {
return DownloadFileWithHeaders(url, nil)
}
// DownloadFileWithHeaders downloads a file and allows you to pass in headers
func DownloadFileWithHeaders(url string, headers http.Header) (buf *bytes.Buffer, err error) {
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, url, nil)
if err != nil {
return nil, err
}
for key, h := range headers {
for _, hh := range h {
req.Header.Add(key, hh)
}
}
hc := http.Client{}
resp, err := hc.Do(req)
if err != nil {
@ -38,6 +50,7 @@ func DownloadFile(url string) (buf *bytes.Buffer, err error) {
defer resp.Body.Close()
buf = &bytes.Buffer{}
_, err = buf.ReadFrom(resp.Body)
return
}

View File

@ -243,15 +243,15 @@ func getMicrosoftTodoData(token string) (microsoftTodoData []*list, err error) {
return
}
func convertMicrosoftTodoData(todoData []*list) (vikunjsStructure []*models.NamespaceWithLists, err error) {
func convertMicrosoftTodoData(todoData []*list) (vikunjsStructure []*models.NamespaceWithListsAndTasks, err error) {
// One namespace with all lists
vikunjsStructure = []*models.NamespaceWithLists{
vikunjsStructure = []*models.NamespaceWithListsAndTasks{
{
Namespace: models.Namespace{
Title: "Migrated from Microsoft Todo",
},
Lists: []*models.List{},
Lists: []*models.ListWithTasksAndBuckets{},
},
}
@ -262,8 +262,10 @@ func convertMicrosoftTodoData(todoData []*list) (vikunjsStructure []*models.Name
log.Debugf("[Microsoft Todo Migration] Converting list %s", l.ID)
// Lists only with title
list := &models.List{
Title: l.DisplayName,
list := &models.ListWithTasksAndBuckets{
List: models.List{
Title: l.DisplayName,
},
}
log.Debugf("[Microsoft Todo Migration] Converting %d tasks", len(l.Tasks))
@ -340,7 +342,7 @@ func convertMicrosoftTodoData(todoData []*list) (vikunjsStructure []*models.Name
}
}
list.Tasks = append(list.Tasks, task)
list.Tasks = append(list.Tasks, &models.TaskWithComments{Task: *task})
log.Debugf("[Microsoft Todo Migration] Done converted %d tasks", len(l.Tasks))
}

View File

@ -102,57 +102,79 @@ func TestConverting(t *testing.T) {
},
}
expectedHierachie := []*models.NamespaceWithLists{
expectedHierachie := []*models.NamespaceWithListsAndTasks{
{
Namespace: models.Namespace{
Title: "Migrated from Microsoft Todo",
},
Lists: []*models.List{
Lists: []*models.ListWithTasksAndBuckets{
{
Title: "List 1",
Tasks: []*models.Task{
List: models.List{
Title: "List 1",
},
Tasks: []*models.TaskWithComments{
{
Title: "Task 1",
Description: "This is a description",
},
{
Title: "Task 2",
Done: true,
DoneAt: testtimeTime,
},
{
Title: "Task 3",
Priority: 1,
},
{
Title: "Task 4",
Priority: 3,
},
{
Title: "Task 5",
Reminders: []time.Time{
testtimeTime,
Task: models.Task{
Title: "Task 1",
Description: "This is a description",
},
},
{
Title: "Task 6",
DueDate: testtimeTime,
Task: models.Task{
Title: "Task 2",
Done: true,
DoneAt: testtimeTime,
},
},
{
Title: "Task 7",
DueDate: testtimeTime,
RepeatAfter: 60 * 60 * 24 * 7, // The amount of seconds in a week
Task: models.Task{
Title: "Task 3",
Priority: 1,
},
},
{
Task: models.Task{
Title: "Task 4",
Priority: 3,
},
},
{
Task: models.Task{
Title: "Task 5",
Reminders: []time.Time{
testtimeTime,
},
},
},
{
Task: models.Task{
Title: "Task 6",
DueDate: testtimeTime,
},
},
{
Task: models.Task{
Title: "Task 7",
DueDate: testtimeTime,
RepeatAfter: 60 * 60 * 24 * 7, // The amount of seconds in a week
},
},
},
},
{
Title: "List 2",
Tasks: []*models.Task{
List: models.List{
Title: "List 2",
},
Tasks: []*models.TaskWithComments{
{
Title: "Task 1",
Task: models.Task{
Title: "Task 1",
},
},
{
Title: "Task 2",
Task: models.Task{
Title: "Task 2",
},
},
},
},

View File

@ -37,7 +37,7 @@ func (s *Status) TableName() string {
}
// SetMigrationStatus sets the migration status for a user
func SetMigrationStatus(m Migrator, u *user.User) (err error) {
func SetMigrationStatus(m MigratorName, u *user.User) (err error) {
s := db.NewSession()
defer s.Close()
@ -50,7 +50,7 @@ func SetMigrationStatus(m Migrator, u *user.User) (err error) {
}
// GetMigrationStatus returns the migration status for a migration and a user
func GetMigrationStatus(m Migrator, u *user.User) (status *Status, err error) {
func GetMigrationStatus(m MigratorName, u *user.User) (status *Status, err error) {
s := db.NewSession()
defer s.Close()

View File

@ -17,11 +17,20 @@
package migration
import (
"io"
"code.vikunja.io/api/pkg/user"
)
type MigratorName interface {
// Name holds the name of the migration.
// This is used to show the name to users and to keep track of users who already migrated.
Name() string
}
// Migrator is the basic migrator interface which is shared among all migrators
type Migrator interface {
MigratorName
// Migrate is the interface used to migrate a user's tasks from another platform to vikunja.
// The user object is the user who's tasks will be migrated.
Migrate(user *user.User) error
@ -29,7 +38,12 @@ type Migrator interface {
// The use case for this are Oauth flows, where the server token should remain hidden and not
// known to the frontend.
AuthURL() string
// Title holds the name of the migration.
// This is used to show the name to users and to keep track of users who already migrated.
Name() string
}
// FileMigrator handles importing Vikunja data from a file. The implementation of it determines the format.
type FileMigrator interface {
MigratorName
// Migrate is the interface used to migrate a user's tasks, list and other things from a file to vikunja.
// The user object is the user who's tasks will be migrated.
Migrate(user *user.User, file io.ReaderAt, size int64) error
}

View File

@ -252,28 +252,30 @@ func parseDate(dateString string) (date time.Time, err error) {
return date, err
}
func convertTodoistToVikunja(sync *sync, doneItems map[int64]*doneItem) (fullVikunjaHierachie []*models.NamespaceWithLists, err error) {
func convertTodoistToVikunja(sync *sync, doneItems map[int64]*doneItem) (fullVikunjaHierachie []*models.NamespaceWithListsAndTasks, err error) {
newNamespace := &models.NamespaceWithLists{
newNamespace := &models.NamespaceWithListsAndTasks{
Namespace: models.Namespace{
Title: "Migrated from todoist",
},
}
// A map for all vikunja lists with the project id they're coming from as key
lists := make(map[int64]*models.List, len(sync.Projects))
lists := make(map[int64]*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.Task, len(sync.Items))
tasks := make(map[int64]*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))
for _, p := range sync.Projects {
list := &models.List{
Title: p.Name,
HexColor: todoistColors[p.Color],
IsArchived: p.IsArchived == 1,
list := &models.ListWithTasksAndBuckets{
List: models.List{
Title: p.Name,
HexColor: todoistColors[p.Color],
IsArchived: p.IsArchived == 1,
},
}
lists[p.ID] = list
@ -305,11 +307,13 @@ func convertTodoistToVikunja(sync *sync, doneItems map[int64]*doneItem) (fullVik
}
for _, i := range sync.Items {
task := &models.Task{
Title: i.Content,
Created: i.DateAdded.In(config.GetTimeZone()),
Done: i.Checked == 1,
BucketID: i.SectionID,
task := &models.TaskWithComments{
Task: models.Task{
Title: i.Content,
Created: i.DateAdded.In(config.GetTimeZone()),
Done: i.Checked == 1,
BucketID: i.SectionID,
},
}
// Only try to parse the task done at date if the task is actually done
@ -365,7 +369,7 @@ func convertTodoistToVikunja(sync *sync, doneItems map[int64]*doneItem) (fullVik
tasks[i.ParentID].RelatedTasks = make(models.RelatedTaskMap)
}
tasks[i.ParentID].RelatedTasks[models.RelationKindSubtask] = append(tasks[i.ParentID].RelatedTasks[models.RelationKindSubtask], tasks[i.ID])
tasks[i.ParentID].RelatedTasks[models.RelationKindSubtask] = append(tasks[i.ParentID].RelatedTasks[models.RelationKindSubtask], &tasks[i.ID].Task)
// Remove the task from the top level structure, otherwise it is added twice
outer:
@ -449,7 +453,7 @@ func convertTodoistToVikunja(sync *sync, doneItems map[int64]*doneItem) (fullVik
tasks[r.ItemID].Reminders = append(tasks[r.ItemID].Reminders, date.In(config.GetTimeZone()))
}
return []*models.NamespaceWithLists{
return []*models.NamespaceWithListsAndTasks{
newNamespace,
}, err
}

View File

@ -375,210 +375,258 @@ func TestConvertTodoistToVikunja(t *testing.T) {
},
}
expectedHierachie := []*models.NamespaceWithLists{
expectedHierachie := []*models.NamespaceWithListsAndTasks{
{
Namespace: models.Namespace{
Title: "Migrated from todoist",
},
Lists: []*models.List{
Lists: []*models.ListWithTasksAndBuckets{
{
Title: "Project1",
Description: "Lorem Ipsum dolor sit amet\nLorem Ipsum dolor sit amet 2\nLorem Ipsum dolor sit amet 3",
HexColor: todoistColors[30],
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],
},
Buckets: []*models.Bucket{
{
ID: 1234,
Title: "Some Bucket",
},
},
Tasks: []*models.Task{
Tasks: []*models.TaskWithComments{
{
Title: "Task400000000",
Description: "Lorem Ipsum dolor sit amet",
Done: false,
Created: time1,
Reminders: []time.Time{
time.Date(2020, time.June, 15, 0, 0, 0, 0, time.UTC).In(config.GetTimeZone()),
time.Date(2020, time.June, 16, 7, 0, 0, 0, time.UTC).In(config.GetTimeZone()),
Task: models.Task{
Title: "Task400000000",
Description: "Lorem Ipsum dolor sit amet",
Done: false,
Created: time1,
Reminders: []time.Time{
time.Date(2020, time.June, 15, 0, 0, 0, 0, time.UTC).In(config.GetTimeZone()),
time.Date(2020, time.June, 16, 7, 0, 0, 0, time.UTC).In(config.GetTimeZone()),
},
},
},
{
Title: "Task400000001",
Description: "Lorem Ipsum dolor sit amet",
Done: false,
Created: time1,
},
{
Title: "Task400000002",
Done: false,
Created: time1,
Reminders: []time.Time{
time.Date(2020, time.July, 15, 7, 0, 0, 0, time.UTC).In(config.GetTimeZone()),
Task: models.Task{
Title: "Task400000001",
Description: "Lorem Ipsum dolor sit amet",
Done: false,
Created: time1,
},
},
{
Title: "Task400000003",
Description: "Lorem Ipsum dolor sit amet",
Done: true,
DueDate: dueTime,
Created: time1,
DoneAt: time3,
Labels: vikunjaLabels,
Reminders: []time.Time{
time.Date(2020, time.June, 15, 7, 0, 0, 0, time.UTC).In(config.GetTimeZone()),
Task: models.Task{
Title: "Task400000002",
Done: false,
Created: time1,
Reminders: []time.Time{
time.Date(2020, time.July, 15, 7, 0, 0, 0, time.UTC).In(config.GetTimeZone()),
},
},
},
{
Title: "Task400000004",
Done: false,
Created: time1,
Labels: vikunjaLabels,
},
{
Title: "Task400000005",
Done: true,
DueDate: dueTime,
Created: time1,
DoneAt: time3,
Reminders: []time.Time{
time.Date(2020, time.June, 15, 7, 0, 0, 0, time.UTC).In(config.GetTimeZone()),
Task: models.Task{
Title: "Task400000003",
Description: "Lorem Ipsum dolor sit amet",
Done: true,
DueDate: dueTime,
Created: time1,
DoneAt: time3,
Labels: vikunjaLabels,
Reminders: []time.Time{
time.Date(2020, time.June, 15, 7, 0, 0, 0, time.UTC).In(config.GetTimeZone()),
},
},
},
{
Title: "Task400000006",
Done: true,
DueDate: dueTime,
Created: time1,
DoneAt: time3,
RelatedTasks: map[models.RelationKind][]*models.Task{
models.RelationKindSubtask: {
Task: models.Task{
Title: "Task400000004",
Done: false,
Created: time1,
Labels: vikunjaLabels,
},
},
{
Task: models.Task{
Title: "Task400000005",
Done: true,
DueDate: dueTime,
Created: time1,
DoneAt: time3,
Reminders: []time.Time{
time.Date(2020, time.June, 15, 7, 0, 0, 0, time.UTC).In(config.GetTimeZone()),
},
},
},
{
Task: models.Task{
Title: "Task400000006",
Done: true,
DueDate: dueTime,
Created: time1,
DoneAt: time3,
RelatedTasks: map[models.RelationKind][]*models.Task{
models.RelationKindSubtask: {
{
Title: "Task with parent",
Done: false,
Priority: 2,
Created: time1,
DoneAt: nilTime,
},
},
},
},
},
{
Task: models.Task{
Title: "Task400000106",
Done: true,
DueDate: dueTimeWithTime,
Created: time1,
DoneAt: time3,
Labels: vikunjaLabels,
},
},
{
Task: models.Task{
Title: "Task400000107",
Done: true,
Created: time1,
DoneAt: time3,
},
},
{
Task: models.Task{
Title: "Task400000108",
Done: true,
Created: time1,
DoneAt: time3,
},
},
{
Task: models.Task{
Title: "Task400000109",
Done: true,
Created: time1,
DoneAt: time3,
BucketID: 1234,
},
},
},
},
{
List: models.List{
Title: "Project2",
Description: "Lorem Ipsum dolor sit amet 4\nLorem Ipsum dolor sit amet 5",
HexColor: todoistColors[37],
},
Tasks: []*models.TaskWithComments{
{
Task: models.Task{
Title: "Task400000007",
Done: false,
DueDate: dueTime,
Created: time1,
},
},
{
Task: models.Task{
Title: "Task400000008",
Done: false,
DueDate: dueTime,
Created: time1,
},
},
{
Task: models.Task{
Title: "Task400000009",
Done: false,
Created: time1,
Reminders: []time.Time{
time.Date(2020, time.June, 15, 7, 0, 0, 0, time.UTC).In(config.GetTimeZone()),
},
},
},
{
Task: models.Task{
Title: "Task400000010",
Description: "Lorem Ipsum dolor sit amet",
Done: true,
Created: time1,
DoneAt: time3,
},
},
{
Task: models.Task{
Title: "Task400000101",
Description: "Lorem Ipsum dolor sit amet",
Done: false,
Created: time1,
Attachments: []*models.TaskAttachment{
{
Title: "Task with parent",
Done: false,
Priority: 2,
Created: time1,
DoneAt: nilTime,
File: &files.File{
Name: "file.md",
Mime: "text/plain",
Size: 12345,
Created: time1,
FileContent: exampleFile,
},
Created: time1,
},
},
},
},
{
Title: "Task400000106",
Done: true,
DueDate: dueTimeWithTime,
Created: time1,
DoneAt: time3,
Labels: vikunjaLabels,
Task: models.Task{
Title: "Task400000102",
Done: false,
DueDate: dueTime,
Created: time1,
Labels: vikunjaLabels,
},
},
{
Title: "Task400000107",
Done: true,
Created: time1,
DoneAt: time3,
Task: models.Task{
Title: "Task400000103",
Done: false,
Created: time1,
Labels: vikunjaLabels,
},
},
{
Title: "Task400000108",
Done: true,
Created: time1,
DoneAt: time3,
Task: models.Task{
Title: "Task400000104",
Done: false,
Created: time1,
Labels: vikunjaLabels,
},
},
{
Title: "Task400000109",
Done: true,
Created: time1,
DoneAt: time3,
BucketID: 1234,
Task: models.Task{
Title: "Task400000105",
Done: false,
DueDate: dueTime,
Created: time1,
Labels: vikunjaLabels,
},
},
},
},
{
Title: "Project2",
Description: "Lorem Ipsum dolor sit amet 4\nLorem Ipsum dolor sit amet 5",
HexColor: todoistColors[37],
Tasks: []*models.Task{
{
Title: "Task400000007",
Done: false,
DueDate: dueTime,
Created: time1,
},
{
Title: "Task400000008",
Done: false,
DueDate: dueTime,
Created: time1,
},
{
Title: "Task400000009",
Done: false,
Created: time1,
Reminders: []time.Time{
time.Date(2020, time.June, 15, 7, 0, 0, 0, time.UTC).In(config.GetTimeZone()),
},
},
{
Title: "Task400000010",
Description: "Lorem Ipsum dolor sit amet",
Done: true,
Created: time1,
DoneAt: time3,
},
{
Title: "Task400000101",
Description: "Lorem Ipsum dolor sit amet",
Done: false,
Created: time1,
Attachments: []*models.TaskAttachment{
{
File: &files.File{
Name: "file.md",
Mime: "text/plain",
Size: 12345,
Created: time1,
FileContent: exampleFile,
},
Created: time1,
},
},
},
{
Title: "Task400000102",
Done: false,
DueDate: dueTime,
Created: time1,
Labels: vikunjaLabels,
},
{
Title: "Task400000103",
Done: false,
Created: time1,
Labels: vikunjaLabels,
},
{
Title: "Task400000104",
Done: false,
Created: time1,
Labels: vikunjaLabels,
},
{
Title: "Task400000105",
Done: false,
DueDate: dueTime,
Created: time1,
Labels: vikunjaLabels,
},
List: models.List{
Title: "Project3 - Archived",
HexColor: todoistColors[37],
IsArchived: true,
},
},
{
Title: "Project3 - Archived",
HexColor: todoistColors[37],
IsArchived: true,
Tasks: []*models.Task{
Tasks: []*models.TaskWithComments{
{
Title: "Task400000111",
Done: true,
Created: time1,
DoneAt: time3,
Task: models.Task{
Title: "Task400000111",
Done: true,
Created: time1,
DoneAt: time3,
},
},
},
},

View File

@ -144,16 +144,16 @@ func getTrelloData(token string) (trelloData []*trello.Board, err error) {
// Converts all previously obtained data from trello into the vikunja format.
// `trelloData` should contain all boards with their lists and cards respectively.
func convertTrelloDataToVikunja(trelloData []*trello.Board) (fullVikunjaHierachie []*models.NamespaceWithLists, err error) {
func convertTrelloDataToVikunja(trelloData []*trello.Board, token string) (fullVikunjaHierachie []*models.NamespaceWithListsAndTasks, err error) {
log.Debugf("[Trello Migration] ")
fullVikunjaHierachie = []*models.NamespaceWithLists{
fullVikunjaHierachie = []*models.NamespaceWithListsAndTasks{
{
Namespace: models.Namespace{
Title: "Imported from Trello",
},
Lists: []*models.List{},
Lists: []*models.ListWithTasksAndBuckets{},
},
}
@ -162,10 +162,12 @@ func convertTrelloDataToVikunja(trelloData []*trello.Board) (fullVikunjaHierachi
log.Debugf("[Trello Migration] Converting %d boards to vikunja lists", len(trelloData))
for _, board := range trelloData {
list := &models.List{
Title: board.Name,
Description: board.Desc,
IsArchived: board.Closed,
list := &models.ListWithTasksAndBuckets{
List: models.List{
Title: board.Name,
Description: board.Desc,
IsArchived: board.Closed,
},
}
// Background
@ -252,7 +254,9 @@ func convertTrelloDataToVikunja(trelloData []*trello.Board) (fullVikunjaHierachi
log.Debugf("[Trello Migration] Downloading card attachment %s", attachment.ID)
buf, err := migration.DownloadFile(attachment.URL)
buf, err := migration.DownloadFileWithHeaders(attachment.URL, map[string][]string{
"Authorization": {`OAuth oauth_consumer_key="` + config.MigrationTrelloKey.GetString() + `", oauth_token="` + token + `"`},
})
if err != nil {
return nil, err
}
@ -269,7 +273,7 @@ func convertTrelloDataToVikunja(trelloData []*trello.Board) (fullVikunjaHierachi
log.Debugf("[Trello Migration] Downloaded card attachment %s", attachment.ID)
}
list.Tasks = append(list.Tasks, task)
list.Tasks = append(list.Tasks, &models.TaskWithComments{Task: *task})
}
list.Buckets = append(list.Buckets, bucket)
@ -307,7 +311,7 @@ func (m *Migration) Migrate(u *user.User) (err error) {
log.Debugf("[Trello Migration] Got all trello data for user %d", u.ID)
log.Debugf("[Trello Migration] Start converting trello data for user %d", u.ID)
fullVikunjaHierachie, err := convertTrelloDataToVikunja(trelloData)
fullVikunjaHierachie, err := convertTrelloDataToVikunja(trelloData, m.Token)
if err != nil {
return
}

View File

@ -187,16 +187,18 @@ func TestConvertTrelloToVikunja(t *testing.T) {
}
trelloData[0].Prefs.BackgroundImage = "https://vikunja.io/testimage.jpg" // Using an image which we are hosting, so it'll still be up
expectedHierachie := []*models.NamespaceWithLists{
expectedHierachie := []*models.NamespaceWithListsAndTasks{
{
Namespace: models.Namespace{
Title: "Imported from Trello",
},
Lists: []*models.List{
Lists: []*models.ListWithTasksAndBuckets{
{
Title: "TestBoard",
Description: "This is a description",
BackgroundInformation: bytes.NewBuffer(exampleFile),
List: models.List{
Title: "TestBoard",
Description: "This is a description",
BackgroundInformation: bytes.NewBuffer(exampleFile),
},
Buckets: []*models.Bucket{
{
ID: 1,
@ -207,37 +209,40 @@ func TestConvertTrelloToVikunja(t *testing.T) {
Title: "Test List 2",
},
},
Tasks: []*models.Task{
Tasks: []*models.TaskWithComments{
{
Title: "Test Card 1",
Description: "Card Description",
BucketID: 1,
KanbanPosition: 123,
DueDate: time1,
Labels: []*models.Label{
{
Title: "Label 1",
HexColor: trelloColorMap["green"],
Task: models.Task{
Title: "Test Card 1",
Description: "Card Description",
BucketID: 1,
KanbanPosition: 123,
DueDate: time1,
Labels: []*models.Label{
{
Title: "Label 1",
HexColor: trelloColorMap["green"],
},
{
Title: "Label 2",
HexColor: trelloColorMap["orange"],
},
},
{
Title: "Label 2",
HexColor: trelloColorMap["orange"],
},
},
Attachments: []*models.TaskAttachment{
{
File: &files.File{
Name: "Testimage.jpg",
Mime: "image/jpg",
Size: uint64(len(exampleFile)),
FileContent: exampleFile,
Attachments: []*models.TaskAttachment{
{
File: &files.File{
Name: "Testimage.jpg",
Mime: "image/jpg",
Size: uint64(len(exampleFile)),
FileContent: exampleFile,
},
},
},
},
},
{
Title: "Test Card 2",
Description: `
Task: models.Task{
Title: "Test Card 2",
Description: `
## Checklist 1
@ -248,84 +253,105 @@ func TestConvertTrelloToVikunja(t *testing.T) {
* [ ] Pending Task
* [ ] Another Pending Task`,
BucketID: 1,
KanbanPosition: 124,
BucketID: 1,
KanbanPosition: 124,
},
},
{
Title: "Test Card 3",
BucketID: 1,
KanbanPosition: 126,
Task: models.Task{
Title: "Test Card 3",
BucketID: 1,
KanbanPosition: 126,
},
},
{
Title: "Test Card 4",
BucketID: 1,
KanbanPosition: 127,
Labels: []*models.Label{
{
Title: "Label 2",
HexColor: trelloColorMap["orange"],
Task: models.Task{
Title: "Test Card 4",
BucketID: 1,
KanbanPosition: 127,
Labels: []*models.Label{
{
Title: "Label 2",
HexColor: trelloColorMap["orange"],
},
},
},
},
{
Title: "Test Card 5",
BucketID: 2,
KanbanPosition: 111,
Labels: []*models.Label{
{
Title: "Label 3",
HexColor: trelloColorMap["blue"],
Task: models.Task{
Title: "Test Card 5",
BucketID: 2,
KanbanPosition: 111,
Labels: []*models.Label{
{
Title: "Label 3",
HexColor: trelloColorMap["blue"],
},
},
},
},
{
Title: "Test Card 6",
BucketID: 2,
KanbanPosition: 222,
DueDate: time1,
Task: models.Task{
Title: "Test Card 6",
BucketID: 2,
KanbanPosition: 222,
DueDate: time1,
},
},
{
Title: "Test Card 7",
BucketID: 2,
KanbanPosition: 333,
Task: models.Task{
Title: "Test Card 7",
BucketID: 2,
KanbanPosition: 333,
},
},
{
Title: "Test Card 8",
BucketID: 2,
KanbanPosition: 444,
Task: models.Task{
Title: "Test Card 8",
BucketID: 2,
KanbanPosition: 444,
},
},
},
},
{
Title: "TestBoard 2",
List: models.List{
Title: "TestBoard 2",
},
Buckets: []*models.Bucket{
{
ID: 3,
Title: "Test List 4",
},
},
Tasks: []*models.Task{
Tasks: []*models.TaskWithComments{
{
Title: "Test Card 634",
BucketID: 3,
KanbanPosition: 123,
Task: models.Task{
Title: "Test Card 634",
BucketID: 3,
KanbanPosition: 123,
},
},
},
},
{
Title: "TestBoard Archived",
IsArchived: true,
List: models.List{
Title: "TestBoard Archived",
IsArchived: true,
},
Buckets: []*models.Bucket{
{
ID: 4,
Title: "Test List 5",
},
},
Tasks: []*models.Task{
Tasks: []*models.TaskWithComments{
{
Title: "Test Card 63423",
BucketID: 4,
KanbanPosition: 123,
Task: models.Task{
Title: "Test Card 63423",
BucketID: 4,
KanbanPosition: 123,
},
},
},
},
@ -333,7 +359,7 @@ func TestConvertTrelloToVikunja(t *testing.T) {
},
}
hierachie, err := convertTrelloDataToVikunja(trelloData)
hierachie, err := convertTrelloDataToVikunja(trelloData, "")
assert.NoError(t, err)
assert.NotNil(t, hierachie)
if diff, equal := messagediff.PrettyDiff(hierachie, expectedHierachie); !equal {

Binary file not shown.

View File

@ -0,0 +1,44 @@
// 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 vikunjafile
import (
"os"
"testing"
"code.vikunja.io/api/pkg/events"
"code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/files"
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/user"
)
// TestMain is the main test function used to bootstrap the test env
func TestMain(m *testing.M) {
// Set default config
config.InitDefaultConfig()
// We need to set the root path even if we're not using the config, otherwise fixtures are not loaded correctly
config.ServiceRootpath.Set(os.Getenv("VIKUNJA_SERVICE_ROOTPATH"))
// Some tests use the file engine, so we'll need to initialize that
files.InitTests()
user.InitTests()
models.SetupTests()
events.Fake()
os.Exit(m.Run())
}

View File

@ -0,0 +1,204 @@
// 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 vikunjafile
import (
"archive/zip"
"bytes"
"encoding/json"
"fmt"
"io"
"strconv"
"strings"
"code.vikunja.io/api/pkg/db"
"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"
)
const logPrefix = "[Vikunja File Import] "
type FileMigrator struct {
}
// Name is used to get the name of the vikunja-file migration - we're using the docs here to annotate the status route.
// @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/vikunja-file/status [get]
func (v *FileMigrator) Name() string {
return "vikunja-file"
}
// Migrate takes a vikunja file export, parses it and imports everything in it into Vikunja.
// @Summary Import all lists, tasks etc. from a Vikunja data export
// @Description Imports all projects, tasks, notes, reminders, subtasks and files from a Vikunjda data export into Vikunja.
// @tags migration
// @Accept json
// @Produce json
// @Security JWTKeyAuth
// @Param import formData string true "The Vikunja export zip file."
// @Success 200 {object} models.Message "A message telling you everything was migrated successfully."
// @Failure 500 {object} models.Message "Internal server error"
// @Router /migration/vikunja-file/migrate [post]
func (v *FileMigrator) Migrate(user *user.User, file io.ReaderAt, size int64) error {
r, err := zip.NewReader(file, size)
if err != nil {
return fmt.Errorf("could not open import file: %s", err)
}
log.Debugf(logPrefix+"Importing a zip file containing %d files", len(r.File))
var dataFile *zip.File
var filterFile *zip.File
storedFiles := make(map[int64]*zip.File)
for _, f := range r.File {
if strings.HasPrefix(f.Name, "files/") {
fname := strings.ReplaceAll(f.Name, "files/", "")
id, err := strconv.ParseInt(fname, 10, 64)
if err != nil {
return fmt.Errorf("could not convert file id: %s", err)
}
storedFiles[id] = f
log.Debugf(logPrefix + "Found a blob file")
continue
}
if f.Name == "data.json" {
dataFile = f
log.Debugf(logPrefix + "Found a data file")
continue
}
if f.Name == "filters.json" {
filterFile = f
log.Debugf(logPrefix + "Found a filter file")
}
}
if dataFile == nil {
return fmt.Errorf("no data file provided")
}
log.Debugf(logPrefix + "")
//////
// Import the bulk of Vikunja data
df, err := dataFile.Open()
if err != nil {
return fmt.Errorf("could not open data file: %s", err)
}
defer df.Close()
var bufData bytes.Buffer
if _, err := bufData.ReadFrom(df); err != nil {
return fmt.Errorf("could not read data file: %s", err)
}
namespaces := []*models.NamespaceWithListsAndTasks{}
if err := json.Unmarshal(bufData.Bytes(), &namespaces); err != nil {
return fmt.Errorf("could not read data: %s", err)
}
for _, n := range namespaces {
for _, l := range n.Lists {
if b, exists := storedFiles[l.BackgroundFileID]; exists {
bf, err := b.Open()
if err != nil {
return fmt.Errorf("could not open list background file %d for reading: %s", l.BackgroundFileID, err)
}
var buf bytes.Buffer
if _, err := buf.ReadFrom(bf); err != nil {
return fmt.Errorf("could not read list background file %d: %s", l.BackgroundFileID, err)
}
l.BackgroundInformation = &buf
}
for _, t := range l.Tasks {
for _, label := range t.Labels {
label.ID = 0
}
for _, comment := range t.Comments {
comment.ID = 0
}
for _, attachment := range t.Attachments {
af, err := storedFiles[attachment.File.ID].Open()
if err != nil {
return fmt.Errorf("could not open attachment %d for reading: %s", attachment.ID, err)
}
var buf bytes.Buffer
if _, err := buf.ReadFrom(af); err != nil {
return fmt.Errorf("could not read attachment %d: %s", attachment.ID, err)
}
attachment.ID = 0
attachment.File.ID = 0
attachment.File.FileContent = buf.Bytes()
}
}
}
}
err = migration.InsertFromStructure(namespaces, user)
if err != nil {
return fmt.Errorf("could not insert data: %s", err)
}
if filterFile == nil {
log.Debugf(logPrefix + "No filter file found")
return nil
}
///////
// Import filters
ff, err := filterFile.Open()
if err != nil {
return fmt.Errorf("could not open filters file: %s", err)
}
defer ff.Close()
var bufFilter bytes.Buffer
if _, err := bufFilter.ReadFrom(ff); err != nil {
return fmt.Errorf("could not read filters file: %s", err)
}
filters := []*models.SavedFilter{}
if err := json.Unmarshal(bufFilter.Bytes(), &filters); err != nil {
return fmt.Errorf("could not read filter data: %s", err)
}
log.Debugf(logPrefix+"Importing %d saved filters", len(filters))
s := db.NewSession()
defer s.Close()
for _, f := range filters {
f.ID = 0
err = f.Create(s, user)
if err != nil {
_ = s.Rollback()
return err
}
}
return s.Commit()
}

View File

@ -0,0 +1,79 @@
// 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 vikunjafile
import (
"os"
"testing"
"code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/user"
"github.com/stretchr/testify/assert"
)
func TestVikunjaFileMigrator_Migrate(t *testing.T) {
db.LoadAndAssertFixtures(t)
m := &FileMigrator{}
u := &user.User{ID: 1}
f, err := os.Open(config.ServiceRootpath.GetString() + "/pkg/modules/migration/vikunja-file/export.zip")
if err != nil {
t.Fatalf("Could not open file: %s", err)
}
defer f.Close()
s, err := f.Stat()
if err != nil {
t.Fatalf("Could not stat file: %s", err)
}
err = m.Migrate(u, f, s.Size())
assert.NoError(t, err)
db.AssertExists(t, "namespaces", map[string]interface{}{
"title": "test",
"owner_id": u.ID,
}, false)
db.AssertExists(t, "lists", map[string]interface{}{
"title": "Test list",
"owner_id": u.ID,
}, false)
db.AssertExists(t, "lists", map[string]interface{}{
"title": "A list with a background",
"owner_id": u.ID,
}, false)
db.AssertExists(t, "tasks", map[string]interface{}{
"title": "Some other task",
"created_by_id": u.ID,
}, false)
db.AssertExists(t, "task_comments", map[string]interface{}{
"comment": "This is a comment",
"author_id": u.ID,
}, false)
db.AssertExists(t, "files", map[string]interface{}{
"name": "cristiano-mozzillo-v3d5uBB26yA-unsplash.jpg",
"created_by_id": u.ID,
}, false)
db.AssertExists(t, "labels", map[string]interface{}{
"title": "test",
"created_by_id": u.ID,
}, false)
db.AssertExists(t, "buckets", map[string]interface{}{
"title": "Test Bucket",
"created_by_id": u.ID,
}, false)
}

View File

@ -142,11 +142,13 @@ type wunderlistContents struct {
subtasks []*subtask
}
func convertListForFolder(listID int, list *list, content *wunderlistContents) (*models.List, error) {
func convertListForFolder(listID int, list *list, content *wunderlistContents) (*models.ListWithTasksAndBuckets, error) {
l := &models.List{
Title: list.Title,
Created: list.CreatedAt,
l := &models.ListWithTasksAndBuckets{
List: models.List{
Title: list.Title,
Created: list.CreatedAt,
},
}
// Find all tasks belonging to this list and put them in
@ -233,13 +235,13 @@ func convertListForFolder(listID int, list *list, content *wunderlistContents) (
}
}
l.Tasks = append(l.Tasks, newTask)
l.Tasks = append(l.Tasks, &models.TaskWithComments{Task: *newTask})
}
}
return l, nil
}
func convertWunderlistToVikunja(content *wunderlistContents) (fullVikunjaHierachie []*models.NamespaceWithLists, err error) {
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))
@ -249,7 +251,7 @@ func convertWunderlistToVikunja(content *wunderlistContents) (fullVikunjaHierach
// First, we look through all folders and create namespaces for them.
for _, folder := range content.folders {
namespace := &models.NamespaceWithLists{
namespace := &models.NamespaceWithListsAndTasks{
Namespace: models.Namespace{
Title: folder.Title,
Created: folder.CreatedAt,
@ -276,7 +278,7 @@ func convertWunderlistToVikunja(content *wunderlistContents) (fullVikunjaHierach
// 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.NamespaceWithLists{
newNamespace := &models.NamespaceWithListsAndTasks{
Namespace: models.Namespace{
Title: "Migrated from wunderlist",
},

View File

@ -194,49 +194,55 @@ func TestWunderlistParsing(t *testing.T) {
},
}
expectedHierachie := []*models.NamespaceWithLists{
expectedHierachie := []*models.NamespaceWithListsAndTasks{
{
Namespace: models.Namespace{
Title: "Lorem Ipsum",
Created: time1,
Updated: time2,
},
Lists: []*models.List{
Lists: []*models.ListWithTasksAndBuckets{
{
Created: time1,
Title: "Lorem1",
Tasks: []*models.Task{
List: models.List{
Created: time1,
Title: "Lorem1",
},
Tasks: []*models.TaskWithComments{
{
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,
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,
},
Created: time2,
},
Reminders: []time.Time{time4},
},
Reminders: []time.Time{time4},
},
{
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",
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",
},
},
},
},
@ -244,38 +250,44 @@ func TestWunderlistParsing(t *testing.T) {
},
},
{
Created: time1,
Title: "Lorem2",
Tasks: []*models.Task{
List: models.List{
Created: time1,
Title: "Lorem2",
},
Tasks: []*models.TaskWithComments{
{
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,
},
},
},
{
Title: "Ipsum4",
DueDate: time.Unix(1378339200, 0).In(config.GetTimeZone()),
Created: time1,
Reminders: []time.Time{time3},
RelatedTasks: map[models.RelationKind][]*models.Task{
models.RelationKindSubtask: {
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{
{
Title: "LoremSub3",
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",
},
},
},
},
@ -283,52 +295,68 @@ func TestWunderlistParsing(t *testing.T) {
},
},
{
Created: time1,
Title: "Lorem3",
Tasks: []*models.Task{
List: models.List{
Created: time1,
Title: "Lorem3",
},
Tasks: []*models.TaskWithComments{
{
Title: "Ipsum5",
DueDate: time.Unix(1378339200, 0).In(config.GetTimeZone()),
Created: time1,
Task: models.Task{
Title: "Ipsum5",
DueDate: time.Unix(1378339200, 0).In(config.GetTimeZone()),
Created: time1,
},
},
{
Title: "Ipsum6",
DueDate: time.Unix(1378339200, 0).In(config.GetTimeZone()),
Created: time1,
Done: true,
DoneAt: time1,
Task: models.Task{
Title: "Ipsum6",
DueDate: time.Unix(1378339200, 0).In(config.GetTimeZone()),
Created: time1,
Done: true,
DoneAt: time1,
},
},
{
Title: "Ipsum7",
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,
},
},
{
Title: "Ipsum8",
DueDate: time.Unix(1378339200, 0).In(config.GetTimeZone()),
Created: time1,
Task: models.Task{
Title: "Ipsum8",
DueDate: time.Unix(1378339200, 0).In(config.GetTimeZone()),
Created: time1,
},
},
},
},
{
Created: time1,
Title: "Lorem4",
Tasks: []*models.Task{
List: models.List{
Created: time1,
Title: "Lorem4",
},
Tasks: []*models.TaskWithComments{
{
Title: "Ipsum9",
DueDate: time.Unix(1378339200, 0).In(config.GetTimeZone()),
Created: time1,
Done: true,
DoneAt: time1,
Task: models.Task{
Title: "Ipsum9",
DueDate: time.Unix(1378339200, 0).In(config.GetTimeZone()),
Created: time1,
Done: true,
DoneAt: time1,
},
},
{
Title: "Ipsum10",
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,
},
},
},
},
@ -338,10 +366,12 @@ func TestWunderlistParsing(t *testing.T) {
Namespace: models.Namespace{
Title: "Migrated from wunderlist",
},
Lists: []*models.List{
Lists: []*models.ListWithTasksAndBuckets{
{
Created: time4,
Title: "List without a namespace",
List: models.List{
Created: time4,
Title: "List without a namespace",
},
},
},
},

View File

@ -19,6 +19,8 @@ package v1
import (
"net/http"
vikunja_file "code.vikunja.io/api/pkg/modules/migration/vikunja-file"
microsofttodo "code.vikunja.io/api/pkg/modules/migration/microsoft-todo"
"code.vikunja.io/api/pkg/modules/migration/trello"
@ -49,6 +51,7 @@ type vikunjaInfos struct {
AuthInfo authInfo `json:"auth"`
EmailRemindersEnabled bool `json:"email_reminders_enabled"`
UserDeletionEnabled bool `json:"user_deletion_enabled"`
TaskCommentsEnabled bool `json:"task_comments_enabled"`
}
type authInfo struct {
@ -91,6 +94,10 @@ func Info(c echo.Context) error {
CaldavEnabled: config.ServiceEnableCaldav.GetBool(),
EmailRemindersEnabled: config.ServiceEnableEmailReminders.GetBool(),
UserDeletionEnabled: config.ServiceEnableUserDeletion.GetBool(),
TaskCommentsEnabled: config.ServiceEnableTaskComments.GetBool(),
AvailableMigrators: []string{
(&vikunja_file.FileMigrator{}).Name(),
},
Legal: legalInfo{
ImprintURL: config.LegalImprintURL.GetString(),
PrivacyPolicyURL: config.LegalPrivacyURL.GetString(),

View File

@ -27,7 +27,7 @@ import (
"github.com/labstack/echo/v4"
)
type UserDeletionRequest struct {
type UserPasswordConfirmation struct {
Password string `json:"password" valid:"required"`
}
@ -41,13 +41,13 @@ type UserDeletionRequestConfirm struct {
// @tags user
// @Accept json
// @Produce json
// @Param credentials body v1.UserDeletionRequest true "The user password."
// @Param credentials body v1.UserPasswordConfirmation true "The user password."
// @Success 200 {object} models.Message
// @Failure 412 {object} web.HTTPError "Bad password provided."
// @Failure 500 {object} models.Message "Internal error"
// @Router /user/deletion/request [post]
func UserRequestDeletion(c echo.Context) error {
var deletionRequest UserDeletionRequest
var deletionRequest UserPasswordConfirmation
if err := c.Bind(&deletionRequest); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "No password provided.")
}
@ -149,13 +149,13 @@ func UserConfirmDeletion(c echo.Context) error {
// @tags user
// @Accept json
// @Produce json
// @Param credentials body v1.UserDeletionRequest true "The user password to confirm."
// @Param credentials body v1.UserPasswordConfirmation true "The user password to confirm."
// @Success 200 {object} models.Message
// @Failure 412 {object} web.HTTPError "Bad password provided."
// @Failure 500 {object} models.Message "Internal error"
// @Router /user/deletion/cancel [post]
func UserCancelDeletion(c echo.Context) error {
var deletionRequest UserDeletionRequest
var deletionRequest UserPasswordConfirmation
if err := c.Bind(&deletionRequest); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "No password provided.")
}

View File

@ -0,0 +1,141 @@
// 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 v1
import (
"net/http"
"code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/events"
"code.vikunja.io/api/pkg/files"
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/user"
"code.vikunja.io/web/handler"
"github.com/labstack/echo/v4"
"xorm.io/xorm"
)
func checkExportRequest(c echo.Context) (s *xorm.Session, u *user.User, err error) {
s = db.NewSession()
defer s.Close()
err = s.Begin()
if err != nil {
return nil, nil, handler.HandleHTTPError(err, c)
}
u, err = user.GetCurrentUserFromDB(s, c)
if err != nil {
_ = s.Rollback()
return nil, nil, handler.HandleHTTPError(err, c)
}
// Users authenticated with a third-party are unable to provide their password.
if u.Issuer != user.IssuerLocal {
return
}
var pass UserPasswordConfirmation
if err := c.Bind(&pass); err != nil {
return nil, nil, echo.NewHTTPError(http.StatusBadRequest, "No password provided.")
}
err = c.Validate(pass)
if err != nil {
return nil, nil, echo.NewHTTPError(http.StatusBadRequest, err)
}
err = user.CheckUserPassword(u, pass.Password)
if err != nil {
_ = s.Rollback()
return nil, nil, handler.HandleHTTPError(err, c)
}
return
}
// RequestUserDataExport is the handler to request a user data export
// @Summary Request a user data export.
// @tags user
// @Accept json
// @Produce json
// @Security JWTKeyAuth
// @Param password body v1.UserPasswordConfirmation true "User password to confirm the data export request."
// @Success 200 {object} models.Message
// @Failure 400 {object} web.HTTPError "Something's invalid."
// @Failure 500 {object} models.Message "Internal server error."
// @Router /user/export/request [post]
func RequestUserDataExport(c echo.Context) error {
s, u, err := checkExportRequest(c)
if err != nil {
return err
}
err = events.Dispatch(&models.UserDataExportRequestedEvent{
User: u,
})
if err != nil {
_ = s.Rollback()
return handler.HandleHTTPError(err, c)
}
err = s.Commit()
if err != nil {
_ = s.Rollback()
return handler.HandleHTTPError(err, c)
}
return c.JSON(http.StatusOK, models.Message{Message: "Successfully requested data export. We will send you an email when it's ready."})
}
// DownloadUserDataExport is the handler to download a created user data export
// @Summary Download a user data export.
// @tags user
// @Accept json
// @Produce json
// @Security JWTKeyAuth
// @Param password body v1.UserPasswordConfirmation true "User password to confirm the download."
// @Success 200 {object} models.Message
// @Failure 400 {object} web.HTTPError "Something's invalid."
// @Failure 500 {object} models.Message "Internal server error."
// @Router /user/export/download [post]
func DownloadUserDataExport(c echo.Context) error {
s, u, err := checkExportRequest(c)
if err != nil {
return err
}
err = s.Commit()
if err != nil {
_ = s.Rollback()
return handler.HandleHTTPError(err, c)
}
// Download
exportFile := &files.File{ID: u.ExportFileID}
err = exportFile.LoadFileMetaByID()
if err != nil {
return handler.HandleHTTPError(err, c)
}
err = exportFile.LoadFileByID()
if err != nil {
return handler.HandleHTTPError(err, c)
}
http.ServeContent(c.Response(), c.Request(), exportFile.Name, exportFile.Created, exportFile.File)
return nil
}

View File

@ -35,6 +35,7 @@ type userWithSettings struct {
user.User
Settings *UserSettings `json:"settings"`
DeletionScheduledAt time.Time `json:"deletion_scheduled_at"`
IsLocalUser bool `json:"is_local_user"`
}
// UserShow gets all informations about the current user
@ -74,6 +75,7 @@ func UserShow(c echo.Context) error {
WeekStart: u.WeekStart,
},
DeletionScheduledAt: u.DeletionScheduledAt,
IsLocalUser: u.Issuer == user.IssuerLocal,
}
return c.JSON(http.StatusOK, us)

View File

@ -56,7 +56,7 @@ func ListHandler(c echo.Context) error {
}
storage := &VikunjaCaldavListStorage{
list: &models.List{ID: listID},
list: &models.ListWithTasksAndBuckets{List: models.List{ID: listID}},
user: u,
}
@ -102,7 +102,7 @@ func TaskHandler(c echo.Context) error {
taskUID := strings.TrimSuffix(c.Param("task"), ".ics")
storage := &VikunjaCaldavListStorage{
list: &models.List{ID: listID},
list: &models.ListWithTasksAndBuckets{List: models.List{ID: listID}},
task: &models.Task{UID: taskUID},
user: u,
}

View File

@ -39,7 +39,7 @@ const ListBasePath = DavBasePath + `lists`
// VikunjaCaldavListStorage represents a list storage
type VikunjaCaldavListStorage struct {
// Used when handling a list
list *models.List
list *models.ListWithTasksAndBuckets
// Used when handling a single task, like updating
task *models.Task
// The current user
@ -109,7 +109,9 @@ func (vcls *VikunjaCaldavListStorage) GetResources(rpath string, withChildren bo
var resources []data.Resource
for _, l := range lists {
rr := VikunjaListResourceAdapter{
list: l,
list: &models.ListWithTasksAndBuckets{
List: *l,
},
isCollection: true,
}
r := data.NewResource(ListBasePath+"/"+strconv.FormatInt(l.ID, 10), &rr)
@ -172,10 +174,10 @@ func (vcls *VikunjaCaldavListStorage) GetResourcesByFilters(rpath string, filter
for _, t := range vcls.list.Tasks {
rr := VikunjaListResourceAdapter{
list: vcls.list,
task: t,
task: &t.Task,
isCollection: false,
}
r := data.NewResource(getTaskURL(t), &rr)
r := data.NewResource(getTaskURL(&t.Task), &rr)
r.Name = t.Title
resources = append(resources, r)
}
@ -368,8 +370,8 @@ func (vcls *VikunjaCaldavListStorage) DeleteResource(rpath string) error {
// VikunjaListResourceAdapter holds the actual resource
type VikunjaListResourceAdapter struct {
list *models.List
listTasks []*models.Task
list *models.ListWithTasksAndBuckets
listTasks []*models.TaskWithComments
task *models.Task
isPrincipal bool
@ -415,7 +417,7 @@ func (vlra *VikunjaListResourceAdapter) GetContent() string {
}
if vlra.task != nil {
list := models.List{Tasks: []*models.Task{vlra.task}}
list := models.ListWithTasksAndBuckets{Tasks: []*models.TaskWithComments{{Task: *vlra.task}}}
return caldav.GetCaldavTodosForTasks(&list, list.Tasks)
}
@ -479,8 +481,10 @@ func (vcls *VikunjaCaldavListStorage) getListRessource(isCollection bool) (rr Vi
panic("Tasks returned from TaskCollection.ReadAll are not []*models.Task!")
}
listTasks = tasks
vcls.list.Tasks = tasks
for _, t := range tasks {
listTasks = append(listTasks, &models.TaskWithComments{Task: *t})
}
vcls.list.Tasks = listTasks
}
if err := s.Commit(); err != nil {

28
pkg/routes/healthcheck.go Normal file
View File

@ -0,0 +1,28 @@
// 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 routes
import (
"net/http"
"github.com/labstack/echo/v4"
)
// HealthcheckHandler handles healthckeck 'OK' response
func HealthcheckHandler(c echo.Context) error {
return c.String(http.StatusOK, "OK")
}

View File

@ -74,29 +74,33 @@ func RateLimit(rateLimiter *limiter.Limiter, rateLimitKind string) echo.Middlewa
}
}
func createRateLimiter(rate limiter.Rate) *limiter.Limiter {
var store limiter.Store
var err error
switch config.RateLimitStore.GetString() {
case "memory":
store = memory.NewStore()
case "redis":
if !config.RedisEnabled.GetBool() {
log.Fatal("Redis is configured for rate limiting, but not enabled!")
}
store, err = redis.NewStore(red.GetRedis())
if err != nil {
log.Fatalf("Error while creating rate limit redis store: %s", err)
}
default:
log.Fatalf("Unknown Rate limit store \"%s\"", config.RateLimitStore.GetString())
}
return limiter.New(store, rate)
}
func setupRateLimit(a *echo.Group, rateLimitKind string) {
if config.RateLimitEnabled.GetBool() {
rate := limiter.Rate{
Period: config.RateLimitPeriod.GetDuration() * time.Second,
Limit: config.RateLimitLimit.GetInt64(),
}
var store limiter.Store
var err error
switch config.RateLimitStore.GetString() {
case "memory":
store = memory.NewStore()
case "redis":
if !config.RedisEnabled.GetBool() {
log.Fatal("Redis is configured for rate limiting, but not enabled!")
}
store, err = redis.NewStore(red.GetRedis())
if err != nil {
log.Fatalf("Error while creating rate limit redis store: %s", err)
}
default:
log.Fatalf("Unknown Rate limit store \"%s\"", config.RateLimitStore.GetString())
}
rateLimiter := limiter.New(store, rate)
rateLimiter := createRateLimiter(rate)
log.Debugf("Rate limit configured with %s and %v requests per %v", config.RateLimitStore.GetString(), rate.Limit, rate.Period)
a.Use(RateLimit(rateLimiter, rateLimitKind))
}

View File

@ -52,6 +52,10 @@ import (
"strings"
"time"
"github.com/ulule/limiter/v3"
vikunja_file "code.vikunja.io/api/pkg/modules/migration/vikunja-file"
"code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/log"
@ -196,6 +200,9 @@ func RegisterRoutes(e *echo.Echo) {
registerCalDavRoutes(c)
}
// healthcheck
e.GET("/health", HealthcheckHandler)
// CORS_SHIT
if config.CorsEnable.GetBool() {
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
@ -230,17 +237,26 @@ func registerAPIRoutes(a *echo.Group) {
// Prometheus endpoint
setupMetrics(n)
// Separate route for unauthenticated routes to enable rate limits for it
ur := a.Group("")
rate := limiter.Rate{
Period: 60 * time.Second,
Limit: 10,
}
rateLimiter := createRateLimiter(rate)
ur.Use(RateLimit(rateLimiter, "ip"))
if config.AuthLocalEnabled.GetBool() {
// User stuff
n.POST("/login", apiv1.Login)
n.POST("/register", apiv1.RegisterUser)
n.POST("/user/password/token", apiv1.UserRequestResetPasswordToken)
n.POST("/user/password/reset", apiv1.UserResetPassword)
n.POST("/user/confirm", apiv1.UserConfirmEmail)
ur.POST("/login", apiv1.Login)
ur.POST("/register", apiv1.RegisterUser)
ur.POST("/user/password/token", apiv1.UserRequestResetPasswordToken)
ur.POST("/user/password/reset", apiv1.UserResetPassword)
ur.POST("/user/confirm", apiv1.UserConfirmEmail)
}
if config.AuthOpenIDEnabled.GetBool() {
n.POST("/auth/openid/:provider/callback", openid.HandleCallback)
ur.POST("/auth/openid/:provider/callback", openid.HandleCallback)
}
// Testing
@ -256,7 +272,7 @@ func registerAPIRoutes(a *echo.Group) {
// Link share auth
if config.ServiceEnableLinkSharing.GetBool() {
n.POST("/shares/:share/auth", apiv1.AuthenticateLinkShare)
ur.POST("/shares/:share/auth", apiv1.AuthenticateLinkShare)
}
// ===== Routes with Authetication =====
@ -303,6 +319,8 @@ func registerAPIRoutes(a *echo.Group) {
u.POST("/settings/avatar", apiv1.ChangeUserAvatarProvider)
u.PUT("/settings/avatar/upload", apiv1.UploadAvatar)
u.POST("/settings/general", apiv1.UpdateGeneralUserSettings)
u.POST("/export/request", apiv1.RequestUserDataExport)
u.POST("/export/download", apiv1.DownloadUserDataExport)
if config.ServiceEnableTotp.GetBool() {
u.GET("/settings/totp", apiv1.UserTOTP)
@ -563,7 +581,35 @@ func registerAPIRoutes(a *echo.Group) {
// Migrations
m := a.Group("/migration")
registerMigrations(m)
// List Backgrounds
if config.BackgroundsEnabled.GetBool() {
a.GET("/lists/:list/background", backgroundHandler.GetListBackground)
a.DELETE("/lists/:list/background", backgroundHandler.RemoveListBackground)
if config.BackgroundsUploadEnabled.GetBool() {
uploadBackgroundProvider := &backgroundHandler.BackgroundProvider{
Provider: func() background.Provider {
return &upload.Provider{}
},
}
a.PUT("/lists/:list/backgrounds/upload", uploadBackgroundProvider.UploadBackground)
}
if config.BackgroundsUnsplashEnabled.GetBool() {
unsplashBackgroundProvider := &backgroundHandler.BackgroundProvider{
Provider: func() background.Provider {
return &unsplash.Provider{}
},
}
a.GET("/backgrounds/unsplash/search", unsplashBackgroundProvider.SearchBackgrounds)
a.POST("/lists/:list/backgrounds/unsplash", unsplashBackgroundProvider.SetBackground)
a.GET("/backgrounds/unsplash/images/:image/thumb", unsplash.ProxyUnsplashThumb)
a.GET("/backgrounds/unsplash/images/:image", unsplash.ProxyUnsplashImage)
}
}
}
func registerMigrations(m *echo.Group) {
// Wunderlist
if config.MigrationWunderlistEnable.GetBool() {
wunderlistMigrationHandler := &migrationHandler.MigrationWeb{
@ -604,30 +650,12 @@ func registerAPIRoutes(a *echo.Group) {
microsoftTodoMigrationHandler.RegisterRoutes(m)
}
// List Backgrounds
if config.BackgroundsEnabled.GetBool() {
a.GET("/lists/:list/background", backgroundHandler.GetListBackground)
a.DELETE("/lists/:list/background", backgroundHandler.RemoveListBackground)
if config.BackgroundsUploadEnabled.GetBool() {
uploadBackgroundProvider := &backgroundHandler.BackgroundProvider{
Provider: func() background.Provider {
return &upload.Provider{}
},
}
a.PUT("/lists/:list/backgrounds/upload", uploadBackgroundProvider.UploadBackground)
}
if config.BackgroundsUnsplashEnabled.GetBool() {
unsplashBackgroundProvider := &backgroundHandler.BackgroundProvider{
Provider: func() background.Provider {
return &unsplash.Provider{}
},
}
a.GET("/backgrounds/unsplash/search", unsplashBackgroundProvider.SearchBackgrounds)
a.POST("/lists/:list/backgrounds/unsplash", unsplashBackgroundProvider.SetBackground)
a.GET("/backgrounds/unsplash/images/:image/thumb", unsplash.ProxyUnsplashThumb)
a.GET("/backgrounds/unsplash/images/:image", unsplash.ProxyUnsplashImage)
}
vikunjaFileMigrationHandler := &migrationHandler.FileMigratorWeb{
MigrationStruct: func() migration.FileMigrator {
return &vikunja_file.FileMigrator{}
},
}
vikunjaFileMigrationHandler.RegisterRoutes(m)
}
func registerCalDavRoutes(c *echo.Group) {

View File

@ -1,14 +1,13 @@
// GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
// Package swagger GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
// This file was generated by swaggo/swag
package swagger
import (
"bytes"
"encoding/json"
"strings"
"text/template"
"github.com/alecthomas/template"
"github.com/swaggo/swag"
)
@ -16,7 +15,7 @@ var doc = `{
"schemes": {{ marshal .Schemes }},
"swagger": "2.0",
"info": {
"description": "{{.Description}}",
"description": "{{escape .Description}}",
"title": "{{.Title}}",
"contact": {
"name": "General Vikunja contact",
@ -2921,6 +2920,80 @@ var doc = `{
}
}
},
"/migration/vikunja-file/migrate": {
"post": {
"security": [
{
"JWTKeyAuth": []
}
],
"description": "Imports all projects, tasks, notes, reminders, subtasks and files from a Vikunjda data export into Vikunja.",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"migration"
],
"summary": "Import all lists, tasks etc. from a Vikunja data export",
"parameters": [
{
"type": "string",
"description": "The Vikunja export zip file.",
"name": "import",
"in": "formData",
"required": true
}
],
"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/vikunja-file/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"
}
}
}
}
},
"/migration/wunderlist/auth": {
"get": {
"security": [
@ -6335,7 +6408,7 @@ var doc = `{
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/v1.UserDeletionRequest"
"$ref": "#/definitions/v1.UserPasswordConfirmation"
}
}
],
@ -6427,7 +6500,7 @@ var doc = `{
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/v1.UserDeletionRequest"
"$ref": "#/definitions/v1.UserPasswordConfirmation"
}
}
],
@ -6453,6 +6526,106 @@ var doc = `{
}
}
},
"/user/export/download": {
"post": {
"security": [
{
"JWTKeyAuth": []
}
],
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"user"
],
"summary": "Download a user data export.",
"parameters": [
{
"description": "User password to confirm the download.",
"name": "password",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/v1.UserPasswordConfirmation"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/models.Message"
}
},
"400": {
"description": "Something's invalid.",
"schema": {
"$ref": "#/definitions/web.HTTPError"
}
},
"500": {
"description": "Internal server error.",
"schema": {
"$ref": "#/definitions/models.Message"
}
}
}
}
},
"/user/export/request": {
"post": {
"security": [
{
"JWTKeyAuth": []
}
],
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"user"
],
"summary": "Request a user data export.",
"parameters": [
{
"description": "User password to confirm the data export request.",
"name": "password",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/v1.UserPasswordConfirmation"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/models.Message"
}
},
"400": {
"description": "Something's invalid.",
"schema": {
"$ref": "#/definitions/web.HTTPError"
}
},
"500": {
"description": "Internal server error.",
"schema": {
"$ref": "#/definitions/models.Message"
}
}
}
}
},
"/user/password": {
"post": {
"security": [
@ -7231,8 +7404,7 @@ var doc = `{
"type": "string"
},
"info": {
"description": "This can be used to supply extra information from an image provider to clients",
"type": "object"
"description": "This can be used to supply extra information from an image provider to clients"
},
"thumb": {
"type": "string"
@ -7382,7 +7554,9 @@ var doc = `{
"updated": {
"description": "A timestamp when this bucket was last updated. You cannot change this value.",
"type": "string"
}
},
"web.CRUDable": {},
"web.Rights": {}
}
},
"models.BulkAssignees": {
@ -7394,7 +7568,9 @@ var doc = `{
"items": {
"$ref": "#/definitions/user.User"
}
}
},
"web.CRUDable": {},
"web.Rights": {}
}
},
"models.BulkTask": {
@ -7536,7 +7712,9 @@ var doc = `{
"updated": {
"description": "A timestamp when this task was last updated. You cannot change this value.",
"type": "string"
}
},
"web.CRUDable": {},
"web.Rights": {}
}
},
"models.DatabaseNotifications": {
@ -7555,8 +7733,7 @@ var doc = `{
"type": "string"
},
"notification": {
"description": "The actual content of the notification.",
"type": "object"
"description": "The actual content of the notification."
},
"read": {
"description": "Whether or not to mark this notification as read or unread.\nTrue is read, false is unread.",
@ -7565,7 +7742,9 @@ var doc = `{
"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": {
@ -7601,7 +7780,9 @@ var doc = `{
"updated": {
"description": "A timestamp when this label was last updated. You cannot change this value.",
"type": "string"
}
},
"web.CRUDable": {},
"web.Rights": {}
}
},
"models.LabelTask": {
@ -7614,7 +7795,9 @@ var doc = `{
"label_id": {
"description": "The label id you want to associate with a task.",
"type": "integer"
}
},
"web.CRUDable": {},
"web.Rights": {}
}
},
"models.LabelTaskBulk": {
@ -7626,7 +7809,9 @@ var doc = `{
"items": {
"$ref": "#/definitions/models.Label"
}
}
},
"web.CRUDable": {},
"web.Rights": {}
}
},
"models.LinkSharing": {
@ -7671,15 +7856,16 @@ var doc = `{
"updated": {
"description": "A timestamp when this share was last updated. You cannot change this value.",
"type": "string"
}
},
"web.CRUDable": {},
"web.Rights": {}
}
},
"models.List": {
"type": "object",
"properties": {
"background_information": {
"description": "Holds extra information about the background set since some background providers require attribution or similar. If not null, the background can be accessed at /lists/{listID}/background",
"type": "object"
"description": "Holds extra information about the background set since some background providers require attribution or similar. If not null, the background can be accessed at /lists/{listID}/background"
},
"created": {
"description": "A timestamp when this list was created. You cannot change this value.",
@ -7736,7 +7922,9 @@ var doc = `{
"updated": {
"description": "A timestamp when this list was last updated. You cannot change this value.",
"type": "string"
}
},
"web.CRUDable": {},
"web.Rights": {}
}
},
"models.ListDuplicate": {
@ -7749,7 +7937,9 @@ var doc = `{
"namespace_id": {
"description": "The target namespace ID",
"type": "integer"
}
},
"web.CRUDable": {},
"web.Rights": {}
}
},
"models.ListUser": {
@ -7776,7 +7966,9 @@ var doc = `{
"user_id": {
"description": "The username.",
"type": "string"
}
},
"web.CRUDable": {},
"web.Rights": {}
}
},
"models.Message": {
@ -7829,7 +8021,9 @@ var doc = `{
"updated": {
"description": "A timestamp when this namespace was last updated. You cannot change this value.",
"type": "string"
}
},
"web.CRUDable": {},
"web.Rights": {}
}
},
"models.NamespaceUser": {
@ -7856,7 +8050,9 @@ var doc = `{
"user_id": {
"description": "The username.",
"type": "string"
}
},
"web.CRUDable": {},
"web.Rights": {}
}
},
"models.NamespaceWithLists": {
@ -7906,7 +8102,9 @@ var doc = `{
"updated": {
"description": "A timestamp when this namespace was last updated. You cannot change this value.",
"type": "string"
}
},
"web.CRUDable": {},
"web.Rights": {}
}
},
"models.RelatedTaskMap": {
@ -7954,7 +8152,9 @@ var doc = `{
"updated": {
"description": "A timestamp when this filter was last updated. You cannot change this value.",
"type": "string"
}
},
"web.CRUDable": {},
"web.Rights": {}
}
},
"models.Subscription": {
@ -7978,7 +8178,9 @@ var doc = `{
"user": {
"description": "The user who made this subscription",
"$ref": "#/definitions/user.User"
}
},
"web.CRUDable": {},
"web.Rights": {}
}
},
"models.Task": {
@ -8113,7 +8315,9 @@ var doc = `{
"updated": {
"description": "A timestamp when this task was last updated. You cannot change this value.",
"type": "string"
}
},
"web.CRUDable": {},
"web.Rights": {}
}
},
"models.TaskAssginee": {
@ -8124,7 +8328,9 @@ var doc = `{
},
"user_id": {
"type": "integer"
}
},
"web.CRUDable": {},
"web.Rights": {}
}
},
"models.TaskAttachment": {
@ -8144,7 +8350,9 @@ var doc = `{
},
"task_id": {
"type": "integer"
}
},
"web.CRUDable": {},
"web.Rights": {}
}
},
"models.TaskCollection": {
@ -8192,7 +8400,9 @@ var doc = `{
"items": {
"type": "string"
}
}
},
"web.CRUDable": {},
"web.Rights": {}
}
},
"models.TaskComment": {
@ -8212,7 +8422,9 @@ var doc = `{
},
"updated": {
"type": "string"
}
},
"web.CRUDable": {},
"web.Rights": {}
}
},
"models.TaskRelation": {
@ -8237,7 +8449,9 @@ var doc = `{
"task_id": {
"description": "The ID of the \"base\" task, the task which has a relation to another.",
"type": "integer"
}
},
"web.CRUDable": {},
"web.Rights": {}
}
},
"models.Team": {
@ -8275,7 +8489,9 @@ var doc = `{
"updated": {
"description": "A timestamp when this relation was last updated. You cannot change this value.",
"type": "string"
}
},
"web.CRUDable": {},
"web.Rights": {}
}
},
"models.TeamList": {
@ -8302,7 +8518,9 @@ var doc = `{
"updated": {
"description": "A timestamp when this relation was last updated. You cannot change this value.",
"type": "string"
}
},
"web.CRUDable": {},
"web.Rights": {}
}
},
"models.TeamMember": {
@ -8323,7 +8541,9 @@ var doc = `{
"username": {
"description": "The username of the member. We use this to prevent automated user id entering.",
"type": "string"
}
},
"web.CRUDable": {},
"web.Rights": {}
}
},
"models.TeamNamespace": {
@ -8350,7 +8570,9 @@ var doc = `{
"updated": {
"description": "A timestamp when this relation was last updated. You cannot change this value.",
"type": "string"
}
},
"web.CRUDable": {},
"web.Rights": {}
}
},
"models.TeamUser": {
@ -8386,7 +8608,8 @@ var doc = `{
"type": "string",
"maxLength": 250,
"minLength": 1
}
},
"web.Auth": {}
}
},
"models.TeamWithRight": {
@ -8427,7 +8650,9 @@ var doc = `{
"updated": {
"description": "A timestamp when this relation was last updated. You cannot change this value.",
"type": "string"
}
},
"web.CRUDable": {},
"web.Rights": {}
}
},
"models.UserWithRight": {
@ -8462,7 +8687,8 @@ var doc = `{
"type": "string",
"maxLength": 250,
"minLength": 1
}
},
"web.Auth": {}
}
},
"notifications.DatabaseNotification": {
@ -8481,8 +8707,7 @@ var doc = `{
"type": "string"
},
"notification": {
"description": "The actual content of the notification.",
"type": "object"
"description": "The actual content of the notification."
},
"read_at": {
"description": "When this notification is marked as read, this will be updated with the current timestamp.",
@ -8674,7 +8899,8 @@ var doc = `{
"type": "string",
"maxLength": 250,
"minLength": 1
}
},
"web.Auth": {}
}
},
"v1.LinkShareAuth": {
@ -8694,14 +8920,6 @@ var doc = `{
}
}
},
"v1.UserDeletionRequest": {
"type": "object",
"properties": {
"password": {
"type": "string"
}
}
},
"v1.UserDeletionRequestConfirm": {
"type": "object",
"properties": {
@ -8721,6 +8939,14 @@ var doc = `{
}
}
},
"v1.UserPasswordConfirmation": {
"type": "object",
"properties": {
"password": {
"type": "string"
}
}
},
"v1.UserSettings": {
"type": "object",
"properties": {
@ -8846,6 +9072,9 @@ var doc = `{
"task_attachments_enabled": {
"type": "boolean"
},
"task_comments_enabled": {
"type": "boolean"
},
"totp_enabled": {
"type": "boolean"
},
@ -8920,6 +9149,13 @@ func (s *s) ReadDoc() string {
a, _ := json.Marshal(v)
return string(a)
},
"escape": func(v interface{}) string {
// escape tabs
str := strings.Replace(v.(string), "\t", "\\t", -1)
// replace " with \", and if that results in \\", replace that with \\\"
str = strings.Replace(str, "\"", "\\\"", -1)
return strings.Replace(str, "\\\\\"", "\\\\\\\"", -1)
},
}).Parse(doc)
if err != nil {
return doc

View File

@ -2904,6 +2904,80 @@
}
}
},
"/migration/vikunja-file/migrate": {
"post": {
"security": [
{
"JWTKeyAuth": []
}
],
"description": "Imports all projects, tasks, notes, reminders, subtasks and files from a Vikunjda data export into Vikunja.",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"migration"
],
"summary": "Import all lists, tasks etc. from a Vikunja data export",
"parameters": [
{
"type": "string",
"description": "The Vikunja export zip file.",
"name": "import",
"in": "formData",
"required": true
}
],
"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/vikunja-file/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"
}
}
}
}
},
"/migration/wunderlist/auth": {
"get": {
"security": [
@ -6318,7 +6392,7 @@
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/v1.UserDeletionRequest"
"$ref": "#/definitions/v1.UserPasswordConfirmation"
}
}
],
@ -6410,7 +6484,7 @@
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/v1.UserDeletionRequest"
"$ref": "#/definitions/v1.UserPasswordConfirmation"
}
}
],
@ -6436,6 +6510,106 @@
}
}
},
"/user/export/download": {
"post": {
"security": [
{
"JWTKeyAuth": []
}
],
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"user"
],
"summary": "Download a user data export.",
"parameters": [
{
"description": "User password to confirm the download.",
"name": "password",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/v1.UserPasswordConfirmation"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/models.Message"
}
},
"400": {
"description": "Something's invalid.",
"schema": {
"$ref": "#/definitions/web.HTTPError"
}
},
"500": {
"description": "Internal server error.",
"schema": {
"$ref": "#/definitions/models.Message"
}
}
}
}
},
"/user/export/request": {
"post": {
"security": [
{
"JWTKeyAuth": []
}
],
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"user"
],
"summary": "Request a user data export.",
"parameters": [
{
"description": "User password to confirm the data export request.",
"name": "password",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/v1.UserPasswordConfirmation"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/models.Message"
}
},
"400": {
"description": "Something's invalid.",
"schema": {
"$ref": "#/definitions/web.HTTPError"
}
},
"500": {
"description": "Internal server error.",
"schema": {
"$ref": "#/definitions/models.Message"
}
}
}
}
},
"/user/password": {
"post": {
"security": [
@ -7214,8 +7388,7 @@
"type": "string"
},
"info": {
"description": "This can be used to supply extra information from an image provider to clients",
"type": "object"
"description": "This can be used to supply extra information from an image provider to clients"
},
"thumb": {
"type": "string"
@ -7365,7 +7538,9 @@
"updated": {
"description": "A timestamp when this bucket was last updated. You cannot change this value.",
"type": "string"
}
},
"web.CRUDable": {},
"web.Rights": {}
}
},
"models.BulkAssignees": {
@ -7377,7 +7552,9 @@
"items": {
"$ref": "#/definitions/user.User"
}
}
},
"web.CRUDable": {},
"web.Rights": {}
}
},
"models.BulkTask": {
@ -7519,7 +7696,9 @@
"updated": {
"description": "A timestamp when this task was last updated. You cannot change this value.",
"type": "string"
}
},
"web.CRUDable": {},
"web.Rights": {}
}
},
"models.DatabaseNotifications": {
@ -7538,8 +7717,7 @@
"type": "string"
},
"notification": {
"description": "The actual content of the notification.",
"type": "object"
"description": "The actual content of the notification."
},
"read": {
"description": "Whether or not to mark this notification as read or unread.\nTrue is read, false is unread.",
@ -7548,7 +7726,9 @@
"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": {
@ -7584,7 +7764,9 @@
"updated": {
"description": "A timestamp when this label was last updated. You cannot change this value.",
"type": "string"
}
},
"web.CRUDable": {},
"web.Rights": {}
}
},
"models.LabelTask": {
@ -7597,7 +7779,9 @@
"label_id": {
"description": "The label id you want to associate with a task.",
"type": "integer"
}
},
"web.CRUDable": {},
"web.Rights": {}
}
},
"models.LabelTaskBulk": {
@ -7609,7 +7793,9 @@
"items": {
"$ref": "#/definitions/models.Label"
}
}
},
"web.CRUDable": {},
"web.Rights": {}
}
},
"models.LinkSharing": {
@ -7654,15 +7840,16 @@
"updated": {
"description": "A timestamp when this share was last updated. You cannot change this value.",
"type": "string"
}
},
"web.CRUDable": {},
"web.Rights": {}
}
},
"models.List": {
"type": "object",
"properties": {
"background_information": {
"description": "Holds extra information about the background set since some background providers require attribution or similar. If not null, the background can be accessed at /lists/{listID}/background",
"type": "object"
"description": "Holds extra information about the background set since some background providers require attribution or similar. If not null, the background can be accessed at /lists/{listID}/background"
},
"created": {
"description": "A timestamp when this list was created. You cannot change this value.",
@ -7719,7 +7906,9 @@
"updated": {
"description": "A timestamp when this list was last updated. You cannot change this value.",
"type": "string"
}
},
"web.CRUDable": {},
"web.Rights": {}
}
},
"models.ListDuplicate": {
@ -7732,7 +7921,9 @@
"namespace_id": {
"description": "The target namespace ID",
"type": "integer"
}
},
"web.CRUDable": {},
"web.Rights": {}
}
},
"models.ListUser": {
@ -7759,7 +7950,9 @@
"user_id": {
"description": "The username.",
"type": "string"
}
},
"web.CRUDable": {},
"web.Rights": {}
}
},
"models.Message": {
@ -7812,7 +8005,9 @@
"updated": {
"description": "A timestamp when this namespace was last updated. You cannot change this value.",
"type": "string"
}
},
"web.CRUDable": {},
"web.Rights": {}
}
},
"models.NamespaceUser": {
@ -7839,7 +8034,9 @@
"user_id": {
"description": "The username.",
"type": "string"
}
},
"web.CRUDable": {},
"web.Rights": {}
}
},
"models.NamespaceWithLists": {
@ -7889,7 +8086,9 @@
"updated": {
"description": "A timestamp when this namespace was last updated. You cannot change this value.",
"type": "string"
}
},
"web.CRUDable": {},
"web.Rights": {}
}
},
"models.RelatedTaskMap": {
@ -7937,7 +8136,9 @@
"updated": {
"description": "A timestamp when this filter was last updated. You cannot change this value.",
"type": "string"
}
},
"web.CRUDable": {},
"web.Rights": {}
}
},
"models.Subscription": {
@ -7961,7 +8162,9 @@
"user": {
"description": "The user who made this subscription",
"$ref": "#/definitions/user.User"
}
},
"web.CRUDable": {},
"web.Rights": {}
}
},
"models.Task": {
@ -8096,7 +8299,9 @@
"updated": {
"description": "A timestamp when this task was last updated. You cannot change this value.",
"type": "string"
}
},
"web.CRUDable": {},
"web.Rights": {}
}
},
"models.TaskAssginee": {
@ -8107,7 +8312,9 @@
},
"user_id": {
"type": "integer"
}
},
"web.CRUDable": {},
"web.Rights": {}
}
},
"models.TaskAttachment": {
@ -8127,7 +8334,9 @@
},
"task_id": {
"type": "integer"
}
},
"web.CRUDable": {},
"web.Rights": {}
}
},
"models.TaskCollection": {
@ -8175,7 +8384,9 @@
"items": {
"type": "string"
}
}
},
"web.CRUDable": {},
"web.Rights": {}
}
},
"models.TaskComment": {
@ -8195,7 +8406,9 @@
},
"updated": {
"type": "string"
}
},
"web.CRUDable": {},
"web.Rights": {}
}
},
"models.TaskRelation": {
@ -8220,7 +8433,9 @@
"task_id": {
"description": "The ID of the \"base\" task, the task which has a relation to another.",
"type": "integer"
}
},
"web.CRUDable": {},
"web.Rights": {}
}
},
"models.Team": {
@ -8258,7 +8473,9 @@
"updated": {
"description": "A timestamp when this relation was last updated. You cannot change this value.",
"type": "string"
}
},
"web.CRUDable": {},
"web.Rights": {}
}
},
"models.TeamList": {
@ -8285,7 +8502,9 @@
"updated": {
"description": "A timestamp when this relation was last updated. You cannot change this value.",
"type": "string"
}
},
"web.CRUDable": {},
"web.Rights": {}
}
},
"models.TeamMember": {
@ -8306,7 +8525,9 @@
"username": {
"description": "The username of the member. We use this to prevent automated user id entering.",
"type": "string"
}
},
"web.CRUDable": {},
"web.Rights": {}
}
},
"models.TeamNamespace": {
@ -8333,7 +8554,9 @@
"updated": {
"description": "A timestamp when this relation was last updated. You cannot change this value.",
"type": "string"
}
},
"web.CRUDable": {},
"web.Rights": {}
}
},
"models.TeamUser": {
@ -8369,7 +8592,8 @@
"type": "string",
"maxLength": 250,
"minLength": 1
}
},
"web.Auth": {}
}
},
"models.TeamWithRight": {
@ -8410,7 +8634,9 @@
"updated": {
"description": "A timestamp when this relation was last updated. You cannot change this value.",
"type": "string"
}
},
"web.CRUDable": {},
"web.Rights": {}
}
},
"models.UserWithRight": {
@ -8445,7 +8671,8 @@
"type": "string",
"maxLength": 250,
"minLength": 1
}
},
"web.Auth": {}
}
},
"notifications.DatabaseNotification": {
@ -8464,8 +8691,7 @@
"type": "string"
},
"notification": {
"description": "The actual content of the notification.",
"type": "object"
"description": "The actual content of the notification."
},
"read_at": {
"description": "When this notification is marked as read, this will be updated with the current timestamp.",
@ -8657,7 +8883,8 @@
"type": "string",
"maxLength": 250,
"minLength": 1
}
},
"web.Auth": {}
}
},
"v1.LinkShareAuth": {
@ -8677,14 +8904,6 @@
}
}
},
"v1.UserDeletionRequest": {
"type": "object",
"properties": {
"password": {
"type": "string"
}
}
},
"v1.UserDeletionRequestConfirm": {
"type": "object",
"properties": {
@ -8704,6 +8923,14 @@
}
}
},
"v1.UserPasswordConfirmation": {
"type": "object",
"properties": {
"password": {
"type": "string"
}
}
},
"v1.UserSettings": {
"type": "object",
"properties": {
@ -8829,6 +9056,9 @@
"task_attachments_enabled": {
"type": "boolean"
},
"task_comments_enabled": {
"type": "boolean"
},
"totp_enabled": {
"type": "boolean"
},

View File

@ -12,7 +12,6 @@ definitions:
info:
description: This can be used to supply extra information from an image provider
to clients
type: object
thumb:
type: string
url:
@ -125,6 +124,8 @@ 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:
@ -133,6 +134,8 @@ definitions:
items:
$ref: '#/definitions/user.User'
type: array
web.CRUDable: {}
web.Rights: {}
type: object
models.BulkTask:
properties:
@ -259,6 +262,8 @@ 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:
@ -274,7 +279,6 @@ definitions:
type: string
notification:
description: The actual content of the notification.
type: object
read:
description: |-
Whether or not to mark this notification as read or unread.
@ -284,6 +288,8 @@ 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:
@ -314,6 +320,8 @@ 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:
@ -324,6 +332,8 @@ 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:
@ -332,6 +342,8 @@ definitions:
items:
$ref: '#/definitions/models.Label'
type: array
web.CRUDable: {}
web.Rights: {}
type: object
models.LinkSharing:
properties:
@ -372,6 +384,8 @@ definitions:
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:
@ -379,7 +393,6 @@ definitions:
description: Holds extra information about the background set since some background
providers require attribution or similar. If not null, the background can
be accessed at /lists/{listID}/background
type: object
created:
description: A timestamp when this list was created. You cannot change this
value.
@ -429,6 +442,8 @@ 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:
@ -438,6 +453,8 @@ definitions:
namespace_id:
description: The target namespace ID
type: integer
web.CRUDable: {}
web.Rights: {}
type: object
models.ListUser:
properties:
@ -461,6 +478,8 @@ definitions:
user_id:
description: The username.
type: string
web.CRUDable: {}
web.Rights: {}
type: object
models.Message:
properties:
@ -504,6 +523,8 @@ 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:
@ -527,6 +548,8 @@ definitions:
user_id:
description: The username.
type: string
web.CRUDable: {}
web.Rights: {}
type: object
models.NamespaceWithLists:
properties:
@ -568,6 +591,8 @@ 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:
@ -606,6 +631,8 @@ definitions:
description: A timestamp when this filter was last updated. You cannot change
this value.
type: string
web.CRUDable: {}
web.Rights: {}
type: object
models.Subscription:
properties:
@ -624,6 +651,8 @@ definitions:
user:
$ref: '#/definitions/user.User'
description: The user who made this subscription
web.CRUDable: {}
web.Rights: {}
type: object
models.Task:
properties:
@ -745,6 +774,8 @@ 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:
@ -752,6 +783,8 @@ definitions:
type: string
user_id:
type: integer
web.CRUDable: {}
web.Rights: {}
type: object
models.TaskAttachment:
properties:
@ -765,6 +798,8 @@ definitions:
type: integer
task_id:
type: integer
web.CRUDable: {}
web.Rights: {}
type: object
models.TaskCollection:
properties:
@ -802,6 +837,8 @@ definitions:
items:
type: string
type: array
web.CRUDable: {}
web.Rights: {}
type: object
models.TaskComment:
properties:
@ -815,6 +852,8 @@ definitions:
type: integer
updated:
type: string
web.CRUDable: {}
web.Rights: {}
type: object
models.TaskRelation:
properties:
@ -834,6 +873,8 @@ definitions:
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.Team:
properties:
@ -864,6 +905,8 @@ 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:
@ -887,6 +930,8 @@ 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:
@ -905,6 +950,8 @@ 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:
@ -928,6 +975,8 @@ 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:
@ -958,6 +1007,7 @@ definitions:
maxLength: 250
minLength: 1
type: string
web.Auth: {}
type: object
models.TeamWithRight:
properties:
@ -990,6 +1040,8 @@ definitions:
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:
@ -1018,6 +1070,7 @@ definitions:
maxLength: 250
minLength: 1
type: string
web.Auth: {}
type: object
notifications.DatabaseNotification:
properties:
@ -1033,7 +1086,6 @@ definitions:
type: string
notification:
description: The actual content of the notification.
type: object
read_at:
description: When this notification is marked as read, this will be updated
with the current timestamp.
@ -1172,6 +1224,7 @@ definitions:
maxLength: 250
minLength: 1
type: string
web.Auth: {}
type: object
v1.LinkShareAuth:
properties:
@ -1185,11 +1238,6 @@ definitions:
email), `upload`, `initials`, `default`.
type: string
type: object
v1.UserDeletionRequest:
properties:
password:
type: string
type: object
v1.UserDeletionRequestConfirm:
properties:
token:
@ -1202,6 +1250,11 @@ definitions:
old_password:
type: string
type: object
v1.UserPasswordConfirmation:
properties:
password:
type: string
type: object
v1.UserSettings:
properties:
default_list_id:
@ -1292,6 +1345,8 @@ definitions:
type: boolean
task_attachments_enabled:
type: boolean
task_comments_enabled:
type: boolean
totp_enabled:
type: boolean
user_deletion_enabled:
@ -3287,6 +3342,55 @@ paths:
summary: Get migration status
tags:
- migration
/migration/vikunja-file/migrate:
post:
consumes:
- application/json
description: Imports all projects, tasks, notes, reminders, subtasks and files
from a Vikunjda data export into Vikunja.
parameters:
- description: The Vikunja export zip file.
in: formData
name: import
required: true
type: string
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: Import all lists, tasks etc. from a Vikunja data export
tags:
- migration
/migration/vikunja-file/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
/migration/wunderlist/auth:
get:
description: Returns the auth url where the user needs to get its auth code.
@ -5554,7 +5658,7 @@ paths:
name: credentials
required: true
schema:
$ref: '#/definitions/v1.UserDeletionRequest'
$ref: '#/definitions/v1.UserPasswordConfirmation'
produces:
- application/json
responses:
@ -5615,7 +5719,7 @@ paths:
name: credentials
required: true
schema:
$ref: '#/definitions/v1.UserDeletionRequest'
$ref: '#/definitions/v1.UserPasswordConfirmation'
produces:
- application/json
responses:
@ -5634,6 +5738,68 @@ paths:
summary: Request the deletion of the user
tags:
- user
/user/export/download:
post:
consumes:
- application/json
parameters:
- description: User password to confirm the download.
in: body
name: password
required: true
schema:
$ref: '#/definitions/v1.UserPasswordConfirmation'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/models.Message'
"400":
description: Something's invalid.
schema:
$ref: '#/definitions/web.HTTPError'
"500":
description: Internal server error.
schema:
$ref: '#/definitions/models.Message'
security:
- JWTKeyAuth: []
summary: Download a user data export.
tags:
- user
/user/export/request:
post:
consumes:
- application/json
parameters:
- description: User password to confirm the data export request.
in: body
name: password
required: true
schema:
$ref: '#/definitions/v1.UserPasswordConfirmation'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/models.Message'
"400":
description: Something's invalid.
schema:
$ref: '#/definitions/web.HTTPError'
"500":
description: Internal server error.
schema:
$ref: '#/definitions/models.Message'
security:
- JWTKeyAuth: []
summary: Request a user data export.
tags:
- user
/user/password:
post:
consumes:

View File

@ -49,7 +49,7 @@ func notifyUsersScheduledForDeletion() {
return
}
log.Debugf("Found %d users scheduled for deletion", len(users))
log.Debugf("Found %d users scheduled for deletion to notify", len(users))
for _, user := range users {
if time.Since(user.DeletionLastReminderSent) < time.Hour*24 {
@ -64,6 +64,8 @@ func notifyUsersScheduledForDeletion() {
number = 3
}
log.Debugf("Notifying user %d of the deletion of their account...", user.ID)
err = notifications.Notify(user, &AccountDeletionNotification{
User: user,
NotificationNumber: number,

View File

@ -21,7 +21,7 @@ type CreatedEvent struct {
User *User
}
// TopicName defines the name for CreatedEvent
// Name defines the name for CreatedEvent
func (t *CreatedEvent) Name() string {
return "user.created"
}

View File

@ -227,11 +227,17 @@ type AccountDeletionNotification struct {
// ToMail returns the mail notification for AccountDeletionNotification
func (n *AccountDeletionNotification) ToMail() *notifications.Mail {
durationString := "in " + strconv.Itoa(n.NotificationNumber) + " days"
if n.NotificationNumber == 1 {
durationString = "tomorrow"
}
return notifications.NewMail().
Subject("Your Vikunja account will be deleted in "+strconv.Itoa(n.NotificationNumber)+" days").
Subject("Your Vikunja account will be deleted "+durationString).
Greeting("Hi "+n.User.GetName()+",").
Line("You recently requested the deletion of your Vikunja account.").
Line("We will delete your account in "+strconv.Itoa(n.NotificationNumber)+" days.").
Line("We will delete your account "+durationString+".").
Line("If you changed your mind, simply click the link below to cancel the deletion and follow the instructions there:").
Action("Abort the deletion", config.ServiceFrontendurl.GetString()).
Line("Have a nice day!")

View File

@ -98,6 +98,8 @@ type User struct {
DeletionScheduledAt time.Time `xorm:"datetime null" json:"-"`
DeletionLastReminderSent time.Time `xorm:"datetime null" json:"-"`
ExportFileID int64 `xorm:"bigint null" json:"-"`
// A timestamp when this task was created. You cannot change this value.
Created time.Time `xorm:"created not null" json:"created"`
// A timestamp when this task was last updated. You cannot change this value.

View File

@ -24,13 +24,13 @@ import (
"xorm.io/xorm"
)
const issuerLocal = `local`
const IssuerLocal = `local`
// CreateUser creates a new user and inserts it into the database
func CreateUser(s *xorm.Session, user *User) (newUser *User, err error) {
if user.Issuer == "" {
user.Issuer = issuerLocal
user.Issuer = IssuerLocal
}
// Check if we have all needed information
@ -45,7 +45,7 @@ func CreateUser(s *xorm.Session, user *User) (newUser *User, err error) {
return nil, err
}
if user.Issuer == issuerLocal {
if user.Issuer == IssuerLocal {
// Hash the password
user.Password, err = HashPassword(user.Password)
if err != nil {
@ -76,7 +76,7 @@ func CreateUser(s *xorm.Session, user *User) (newUser *User, err error) {
}
// Dont send a mail if no mailer is configured
if !config.MailerEnabled.GetBool() || user.Issuer != issuerLocal {
if !config.MailerEnabled.GetBool() || user.Issuer != IssuerLocal {
return newUserOut, err
}
@ -112,8 +112,8 @@ func HashPassword(password string) (string, error) {
func checkIfUserIsValid(user *User) error {
if user.Email == "" ||
(user.Issuer != issuerLocal && user.Subject == "") ||
(user.Issuer == issuerLocal && (user.Password == "" ||
(user.Issuer != IssuerLocal && user.Subject == "") ||
(user.Issuer == IssuerLocal && (user.Password == "" ||
user.Username == "")) {
return ErrNoUsernamePassword{}
}
@ -143,7 +143,7 @@ func checkIfUserExists(s *xorm.Session, user *User) (err error) {
Subject: user.Subject,
}
if user.Issuer != issuerLocal {
if user.Issuer != IssuerLocal {
userToCheck.Email = ""
}
@ -155,7 +155,7 @@ func checkIfUserExists(s *xorm.Session, user *User) (err error) {
return err
}
}
if exists && user.Issuer == issuerLocal {
if exists && user.Issuer == IssuerLocal {
return ErrUserEmailExists{user.ID, user.Email}
}

63
pkg/utils/write_to_zip.go Normal file
View File

@ -0,0 +1,63 @@
// 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 utils
import (
"archive/zip"
"fmt"
"io"
"strconv"
)
// Change to deflate to gain better compression
// see http://golang.org/pkg/archive/zip/#pkg-constants
const CompressionUsed = zip.Deflate
func WriteBytesToZip(filename string, data []byte, writer *zip.Writer) (err error) {
header := &zip.FileHeader{
Name: filename,
Method: CompressionUsed,
}
w, err := writer.CreateHeader(header)
if err != nil {
return err
}
_, err = w.Write(data)
return
}
// WriteFilesToZip writes a bunch of files from the db to a zip file. It exprects a map with the file id
// as key and its content as io.ReadCloser.
func WriteFilesToZip(files map[int64]io.ReadCloser, wr *zip.Writer) (err error) {
for fid, file := range files {
header := &zip.FileHeader{
Name: "files/" + strconv.FormatInt(fid, 10),
Method: CompressionUsed,
}
w, err := wr.CreateHeader(header)
if err != nil {
return err
}
_, err = io.Copy(w, file)
if err != nil {
return fmt.Errorf("error writing file %d: %s", fid, err)
}
_ = file.Close()
}
return nil
}