Compare commits

...

310 Commits

Author SHA1 Message Date
Christopher Leggett 5b0628233d
Fixed one more leftover formatting oddity. 2020-06-21 23:52:24 -04:00
Christopher Leggett c6980a390f
Fixed some leftovers from copy-paste. 2020-06-21 23:50:53 -04:00
Christopher Leggett 2859c71253
Added section to full-docker-example.md for Caddy v2. 2020-06-21 23:33:37 -04:00
kolaente 150c3f032c
Prevent crashing when trying to register with an empty payload 2020-06-21 20:54:46 +02:00
kolaente bfc4dd05ed
Add docs for restore 2020-06-21 17:34:34 +02:00
kolaente cd812b4232
update theme 2020-06-21 17:32:38 +02:00
konrad e4f150bbe3 Restore command (#593)
Add waiting for changes to config file

Add max size for config files

Restore files

Restore database file

Expose migrate to

Move init stuff to seperate package

Add restoring config file

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#593
2020-06-21 15:30:48 +00:00
kolaente db0126968a
Improve memory usage of dump by not loading all files in memory prior to adding them to the zip 2020-06-20 11:48:45 +02:00
kolaente c12bac0c96
Return errors when dumping 2020-06-20 11:37:51 +02:00
konrad fba333866d Add dump command (#592)
Fix files location in dump

Fix gitignore

Add docs

Add dumps to gitignore

Move dump to seperate package

logging

Dump files

Dump version

Dump database

Dump config

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#592
2020-06-19 21:29:02 +00:00
konrad d02d413c5e Sentry integration (#591)
Use sentry echo integration to send errors

Only capture errors not already handled by echo

Add sentry panic handler

Add sentry library

Add sentry init

Add sentry config

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#591
2020-06-19 18:47:15 +00:00
renovate c51662c270 Update module src.techknowlogick.com/xormigrate to v1.3.0 (#590)
Update module src.techknowlogick.com/xormigrate to v1.3.0

Reviewed-on: vikunja/api#590
2020-06-18 18:51:06 +00:00
kolaente 021e3e307b
Remove build date from binary 2020-06-18 19:26:53 +02:00
kolaente b4cdb75d15
Update xorm.io/xorm 1.0.1 -> 1.0.2 2020-06-18 19:22:58 +02:00
kolaente ad6ef03c0c
Update xorm.io/xorm 1.0.1 -> 1.0.2 2020-06-18 19:22:43 +02:00
kolaente 5930d5aabf
Update web handler 2020-06-18 18:48:48 +02:00
kolaente 3c5925c827
Add plausible to docs 2020-06-18 18:33:30 +02:00
renovate b7c8c1f533 Update module prometheus/client_golang to v1.7.0 (#589)
Update module prometheus/client_golang to v1.7.0

Reviewed-on: vikunja/api#589
2020-06-17 21:24:13 +00:00
renovate bf41b2ed9f Update module spf13/afero to v1.3.0 (#588)
Update module spf13/afero to v1.3.0

Reviewed-on: vikunja/api#588
2020-06-17 16:52:36 +00:00
kolaente 525a547500
Remove min length for labels, lists, namespaces, tasks and teams 2020-06-17 18:52:23 +02:00
kolaente ca4d5000ed
Add list background information when getting all lists 2020-06-16 18:57:08 +02:00
kolaente 24c1098736
Fix resetting list, label & namespace colors 2020-06-15 11:32:07 +02:00
konrad 47d7e713af Repeat tasks after completion (#587)
Fix integration tests

Add updating start and end date from current date

Add updating reminders from current date

Add updating reminders from current date

Add updating due date from current date

Add updating flag

Add tests

Add field to repeat a task after current date

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#587
2020-06-14 11:04:38 +00:00
kolaente 3ba26ce67e
Fix docs theme build 2020-06-13 23:44:05 +02:00
konrad ab1d58959f Ensure task dates are in the future if a task has a repeating interval (#586)
Ensure task dates are in the future if a task has a repeating interval

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#586
2020-06-13 21:32:31 +00:00
konrad f17e378a93 Add migration test in drone (#585)
Add test migration for postgres & mysql

More logging

Set database type explicitly to sqlite

Only extract vikunja binary

Add seperate build step to speed up tests

Add migration test in drone

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#585
2020-06-13 20:45:18 +00:00
kolaente f5e44d9eb3
Reorganize cmd init functions 2020-06-13 19:44:45 +02:00
kolaente d9d00ba60c
Fix migration 20200516123847 2020-06-13 19:33:48 +02:00
kolaente a15b1bebba
Fix migration 20200425182634 2020-06-13 19:27:12 +02:00
kolaente 980be0e4e0
update theme 2020-06-12 23:37:47 +02:00
konrad a0ffe89056 List Background upload (#582)
Add filesize check when uploading a new file

Fix error 500 if the uploaded background was not an unsplash one

Add upload background setting to info endpoint

Add config docs

Fix lint

Return list background type if it was uploaded

Add file upload

Add docs

Save uploaded backgrounds as file

Add background upload handler

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#582
2020-06-11 17:31:37 +00:00
renovate bd11c9650e Update module lib/pq to v1.7.0 (#581)
Update module lib/pq to v1.7.0

Reviewed-on: vikunja/api#581
2020-06-08 21:17:38 +00:00
kolaente 32a5dff78d
Fix migration to add position to task
Signed-off-by: kolaente <k@knt.li>
2020-06-07 18:28:49 +02:00
renovate 8ef2da8f5f Update module stretchr/testify to v1.6.1 (#580)
Update module stretchr/testify to v1.6.1

Reviewed-on: vikunja/api#580
2020-06-05 13:26:48 +00:00
renovate 60e802d77f Update module go-redis/redis/v7 to v7.4.0 (#579)
Update module go-redis/redis/v7 to v7.4.0

Reviewed-on: vikunja/api#579
2020-06-05 13:26:35 +00:00
renovate 966acf51d8 Update golang.org/x/crypto commit hash to 70a84ac (#578)
Update golang.org/x/crypto commit hash to 70a84ac

Reviewed-on: vikunja/api#578
2020-06-05 13:26:21 +00:00
renovate b5b84e6bbd Update golang.org/x/crypto commit hash to 279210d (#577)
Update golang.org/x/crypto commit hash to 279210d

Reviewed-on: vikunja/api#577
2020-06-03 08:53:09 +00:00
renovate f72aa4e52f Update src.techknowlogick.com/xgo commit hash to a09175e (#576)
Update src.techknowlogick.com/xgo commit hash to a09175e

Reviewed-on: vikunja/api#576
2020-06-02 07:33:43 +00:00
renovate d9f1d456d3 Update src.techknowlogick.com/xgo commit hash to eeb7c0a (#575)
Update src.techknowlogick.com/xgo commit hash to eeb7c0a

Reviewed-on: vikunja/api#575
2020-06-02 06:39:25 +00:00
renovate 67f4c9f941 Update module src.techknowlogick.com/xormigrate to v1.2.1 (#574)
Update module src.techknowlogick.com/xormigrate to v1.2.1

Reviewed-on: vikunja/api#574
2020-06-01 10:33:58 +00:00
kolaente d63666cece
Fix getting unsplash thumbnails for non "photo-*" urls 2020-05-31 22:40:27 +02:00
kolaente d8a6acda96
Fix proxying unsplash images (security) 2020-05-31 22:36:25 +02:00
kolaente 03ef48a0ae
Properly ping unsplash when using unsplash images 2020-05-31 22:06:59 +02:00
kolaente 8f35b9d579
Set unsplash empty collection caching to one hour 2020-05-31 20:50:51 +02:00
kolaente ebfa982c72
Fix misspell 2020-05-31 18:12:32 +02:00
kolaente 2fa4fcc202
Fix caching of initial unsplash results per page 2020-05-31 18:09:46 +02:00
kolaente ad67154e26
Add cache for initial unsplash collection 2020-05-31 17:59:39 +02:00
kolaente 250c45d1b9
Remove migration dependency to models 2020-05-30 17:09:35 +02:00
kolaente dc036d44db
Update theme 2020-05-30 17:00:52 +02:00
renovate 9c4c265767 Update alpine Docker tag to v3.12 (#573)
Update alpine Docker tag to v3.12

Reviewed-on: vikunja/api#573
2020-05-29 21:44:01 +00:00
kolaente a525787ab7
Update and fix staticcheck 2020-05-29 22:15:21 +02:00
kolaente aae1bc3cab
Load the list when setting a background 2020-05-29 22:12:16 +02:00
kolaente 42ddee8d6f
Add logging if downloading an image from unsplash fails 2020-05-29 22:11:49 +02:00
renovate 54b18b3c59 Update module lib/pq to v1.6.0 (#572)
Update module lib/pq to v1.6.0

Reviewed-on: vikunja/api#572
2020-05-29 17:47:28 +00:00
kolaente 5a04f1ecf4
Add option to disable totp for everyone 2020-05-29 17:15:59 +02:00
kolaente a0fb8bd32d
Add unsplash image proxy for images and thumbnails 2020-05-29 15:33:46 +02:00
konrad c685250c96 Add test mail command (#571)
Add test mail command

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#571
2020-05-29 13:10:06 +00:00
renovate 9ea1104764 Update module stretchr/testify to v1.6.0 (#570)
Update module stretchr/testify to v1.6.0

Reviewed-on: vikunja/api#570
2020-05-29 09:37:34 +00:00
kolaente 2ef2c7523d
Also return unsplash info when searching 2020-05-27 22:12:12 +02:00
konrad e5e30d0915 List Backgrounds (#568)
Return the updated list when setting a list background

Add swagger docs for unsplash methods

Add unsplash info to search results

Fix misspell

Fix lint

Add rights check for setting and getting backgrounds

Show unsplash information when loading a single list

Make application id for pingbacks configurable

Remove old backgrounds when setting a new one

Return 404 if the list does not have a background

Implement getting list backgrounds

Implement actually setting a photo from unsplash as list background

go mod tidy

Add migration for background file id

Roughly implement setting a list background from unsplash

Implement saving a background

Add migration for unsplash photo table

Add unsplash search

Fix parsing page param

Fix parsing page param

Fix background config

Add unsplash wrapper library

Add enabled background providers to info endpoint

Add config options for backgrounds

Add unsplash background provider

Add routing handler for backgrounds

Add basic background provider interface

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#568
2020-05-26 20:07:55 +00:00
kolaente c37b776f7a
Add docker setup guide from start to finish 2020-05-24 13:50:17 +02:00
kolaente e9bc3246ce
Add todoist migrator to available migrators in info endpoint if it is enabled 2020-05-24 12:51:38 +02:00
konrad e89e6d47d4 Todoist Migration (#566)
Add swagger docs + fix lint

Add parsing logic + fix fixtures

Fix test init

Add logging to creating labels and debug logs

Add creating labels when migrating

Finish test fixtures

Started adding fixtures for testing

Add method and test structures to convert todoist to vikunja

Add basic structure to migrate everything

Add all structs for todoist api

Add docs for config options

Add routes for todoist migrator

Add api token exchange

Add basic structure for todoist migration

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#566
2020-05-23 20:50:54 +00:00
kolaente 292c815000
Add restarting commands to all example docker compose files 2020-05-23 19:34:44 +02:00
kolaente f0b1de5ec6
Use the db logger instance for logging migration related stuff 2020-05-23 18:33:11 +02:00
kolaente 4204af255c
Add ability to run the docker container with configurable user and group ids 2020-05-22 21:18:11 +02:00
renovate b0948a37d4 Update module go-redis/redis/v7 to v7.3.0 (#565)
Update module go-redis/redis/v7 to v7.3.0

Reviewed-on: vikunja/api#565
2020-05-21 08:56:39 +00:00
kolaente cc47d11792
"fix" gocyclo check 2020-05-19 17:41:37 +02:00
kolaente b6b82e6f54
Merge branch 'release/0.13' 2020-05-19 17:28:32 +02:00
kolaente 16ed5fd982
Release preparations 2020-05-19 17:27:37 +02:00
kolaente 24aa68f090
Don't return all tasks when a user has no lists 2020-05-19 17:23:49 +02:00
kolaente 98ef052967
Don't return all tasks when a user has no lists 2020-05-19 17:22:10 +02:00
kolaente 8a01b20e1e
Fix setting a list identifier to empty 2020-05-16 13:13:11 +02:00
kolaente 5a86f44fc3
Generate a random list identifier based on the list title 2020-05-16 12:58:37 +02:00
konrad fe43173b6c Ensure consistent naming of title fields (#528)
Remove task text and namespace name in migration

Fix lint

Add migration for namespace title

Fix renaming namespace name to title

Rename namespace name field to title

Drop text column at the end of the migration

Add migration for task text to title

Rename task text to title

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#528
2020-05-16 10:17:44 +00:00
konrad 587ce92dc9 Improve getting all namespaces performance (#526)
Improve getting all namespaces performance

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#526
2020-05-15 15:10:22 +00:00
konrad 8758fb6ac5 Fix case-insensitive task search for postgresql (#524)
"Fix" gocyclo

Fix case-insensitive task search for postgresql

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#524
2020-05-15 14:12:20 +00:00
kolaente 92e5e2db6a
Make the _unix suffix optional when sorting tasks 2020-05-15 16:07:36 +02:00
renovate 851f0d6c08 Update src.techknowlogick.com/xgo commit hash to 209a5cf (#523)
Update src.techknowlogick.com/xgo commit hash to 209a5cf

Reviewed-on: vikunja/api#523
2020-05-15 13:28:38 +00:00
kolaente 333444b4e1
Improve metrics performance 2020-05-15 14:42:32 +02:00
konrad 28a5c1f7b2 Remove dependencies on build step to speed up test pipeline (#521)
Make all steps depend on fetch tags to let them execute in parallel

Cleanup

Remove dependency from the build step everywhere

Add generation step for tests

Remove dependencies on build step to speed up test pipeline

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#521
2020-05-15 12:06:12 +00:00
renovate 4533ac6b28 Update module spf13/cobra to v1 (#511)
Update module spf13/cobra to v1

Reviewed-on: vikunja/api#511
2020-05-14 19:09:06 +00:00
kolaente 91a3b7aba2
0.13 release preperations 2020-05-12 22:11:46 +02:00
kolaente 4137d4aed2
Fix creating new things with a link share auth 2020-05-12 15:51:48 +02:00
kolaente cb095d70df
Add explanation to docs about cors 2020-05-12 11:10:07 +02:00
kolaente e43e601912
Add traefik 2 example configuration 2020-05-11 22:02:15 +02:00
kolaente 55cd40d175
Fix team rights not updating for namespace rights 2020-05-11 18:39:30 +02:00
renovate 55cd74efca Update module go-testfixtures/testfixtures/v3 to v3.2.0 (#505)
Update module go-testfixtures/testfixtures/v3 to v3.2.0

Reviewed-on: vikunja/api#505
2020-05-11 10:10:15 +00:00
renovate a9d0079bf3 Update golang.org/x/crypto commit hash to 06a226f (#504)
Update golang.org/x/crypto commit hash to 06a226f

Reviewed-on: vikunja/api#504
2020-05-11 09:02:23 +00:00
konrad 8ac158cdb4 Task Filter Fixes (#495)
Fix gocyclo

Fix misspell

Error codes docs

Filter fixes

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#495
2020-05-09 15:48:56 +00:00
renovate def2362682 Update module spf13/viper to v1.7.0 (#494)
Update module spf13/viper to v1.7.0

Reviewed-on: vikunja/api#494
2020-05-09 13:44:17 +00:00
kolaente 18f6e31b54
Fix parsing array style comparators by query param 2020-05-09 15:08:58 +02:00
kolaente 7e1d0a81bf
Change totp secret datatype from varchar to text 2020-05-09 14:45:57 +02:00
renovate f30e405229 Update module lib/pq to v1.5.2 (#491)
Update module lib/pq to v1.5.2

Reviewed-on: vikunja/api#491
2020-05-08 13:23:26 +00:00
kolaente 563fe16bd4
Add checking and logging when trying to put a task into a nonexisting bucket 2020-05-07 10:20:10 +02:00
kolaente a30a358b73
Update theme 2020-05-06 21:03:15 +02:00
kolaente 497499e221
Fix reference to reverse proxies in docs 2020-05-06 21:02:58 +02:00
kolaente 1fdc51078e
Add docs for changing frontend url 2020-05-06 20:44:48 +02:00
renovate 60f343a926 Update module lib/pq to v1.5.1 (#485)
Update module lib/pq to v1.5.1

Reviewed-on: vikunja/api#485
2020-05-05 08:59:08 +00:00
konrad fd3ccd6130 Fix total label count when getting all labels (#477)
Fix total label count when getting all labels

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#477
2020-05-03 21:28:36 +00:00
kolaente a97ce76f1f
Fix bucket ID being reset with no need to do so 2020-05-03 22:25:48 +02:00
kolaente 83cf6e191c
Fix not moving its bucket when moving a task between lists 2020-05-03 21:33:59 +02:00
kolaente f0ac6e89be
Remove setting task bucket to 0 2020-05-03 21:24:28 +02:00
renovate 23950c0602 Update module lib/pq to v1.5.0 (#476)
Update module lib/pq to v1.5.0

Reviewed-on: vikunja/api#476
2020-05-03 18:14:57 +00:00
renovate 19a05a7c2c Update golang.org/x/crypto commit hash to 4b2356b (#475)
Update golang.org/x/crypto commit hash to 4b2356b

Reviewed-on: vikunja/api#475
2020-05-01 10:06:00 +00:00
renovate 299e856736 Update module prometheus/client_golang to v1.6.0 (#463)
Update module prometheus/client_golang to v1.6.0

Reviewed-on: vikunja/api#463
2020-04-28 18:50:58 +00:00
renovate 6d95dc16c2 Update golang.org/x/crypto commit hash to 729f1e8 (#458)
Update golang.org/x/crypto commit hash to 729f1e8

Reviewed-on: vikunja/api#458
2020-04-27 18:56:31 +00:00
kolaente fa28ddc2f7
Fix pagination count for task collection 2020-04-27 19:28:19 +02:00
kolaente 56dbb564ea
Fix link share creation & creating admin link shares without admin rights 2020-04-27 12:51:09 +02:00
renovate 711124f5c0 Update module go-testfixtures/testfixtures/v3 to v3.1.2 (#457)
Update module go-testfixtures/testfixtures/v3 to v3.1.2

Reviewed-on: vikunja/api#457
2020-04-27 09:10:08 +00:00
kolaente 5606f987fa
Add creating a new first bucket when creating a new list 2020-04-26 15:51:59 +02:00
kolaente f0cb3b5e36
Fix removing the last bucket 2020-04-26 00:57:47 +02:00
konrad 231dc3913f Add real buckets for tasks which don't have one (#446)
Add docs for error code

Add moving new tasks into the default bucket when none was provided

Add moving tasks in default bucket when deleting one

Fix tests again

Add test for removing a bucket

Fix tests

Prevent removing the last bucket

Remove the empty pseudo bucket

Add migration to create a new bucket for each list (and put all tasks in it

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#446
2020-04-25 20:32:02 +00:00
renovate 2b1fc441e6 Update golang.org/x/crypto commit hash to 4bdfaf4 (#438)
Update golang.org/x/crypto commit hash to 4bdfaf4

Reviewed-on: vikunja/api#438
2020-04-24 15:23:22 +00:00
konrad e433289832 Task Position (#412)
Fix misspell

Fix sorting tasks with null values

Fix sorting by priority for postgres

Merge branch 'master' into feature/position

Add community link

Update golang.org/x/crypto commit hash to 44a6062 (#429)

Update golang.org/x/crypto commit hash to 44a6062

Reviewed-on: vikunja/api#429

Update module lib/pq to v1.4.0 (#428)

Update module lib/pq to v1.4.0

Reviewed-on: vikunja/api#428

Fix updating position

Add ordering tasks in buckets by position

Make task sort by string

Merge branch 'master' into feature/position

Update golang.org/x/crypto commit hash to 3c4aac8 (#419)

Update golang.org/x/crypto commit hash to 3c4aac8

Reviewed-on: vikunja/api#419

Merge branch 'master' into feature/position

Fix moving tasks back into the empty (ID: 0) bucket

Add adding a default position when creating new tasks

Update golang.org/x/crypto commit hash to a76a400 (#411)

Update golang.org/x/crypto commit hash to a76a400

Reviewed-on: vikunja/api#411

Remove unused code

Fix tests

Add migration for position attribute

Add position attribute

Co-authored-by: kolaente <k@knt.li>
Co-authored-by: renovate <renovatebot@kolaente.de>
Reviewed-on: vikunja/api#412
2020-04-24 15:23:03 +00:00
kolaente 86bdd1e386
Add community link 2020-04-24 13:00:29 +02:00
renovate 4f1c0d99ea Update golang.org/x/crypto commit hash to 44a6062 (#429)
Update golang.org/x/crypto commit hash to 44a6062

Reviewed-on: vikunja/api#429
2020-04-22 22:49:56 +00:00
renovate f0792cfb0b Update module lib/pq to v1.4.0 (#428)
Update module lib/pq to v1.4.0

Reviewed-on: vikunja/api#428
2020-04-22 10:16:38 +00:00
renovate dece744685 Update golang.org/x/crypto commit hash to 3c4aac8 (#419)
Update golang.org/x/crypto commit hash to 3c4aac8

Reviewed-on: vikunja/api#419
2020-04-21 07:41:16 +00:00
kolaente 68ab6d2e75
Fix moving tasks back into the empty (ID: 0) bucket 2020-04-20 23:08:38 +02:00
renovate c69f0bf3cb Update golang.org/x/crypto commit hash to a76a400 (#411)
Update golang.org/x/crypto commit hash to a76a400

Reviewed-on: vikunja/api#411
2020-04-20 20:58:32 +00:00
kolaente 59037e4eee
Fix moving tasks in buckets 2020-04-20 18:08:20 +02:00
kolaente 1bd8348be7
Add categories to error docs 2020-04-19 14:33:51 +02:00
konrad ecdecdd94e Kanban (#393)
Fix tests

Add error docs

Add swagger docs for bucket endpoints

Add integration tests

Fix tests

Fix err shadow

Make sure a bucket and a task belong to the same list when adding or updating a task

Add tests

Add getting users of a bucket

Fix log level when testing

Fix lint

Add migration for buckets

Cleanup/Comments/Reorganization

Add Kanban bucket handling

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#393
2020-04-19 07:27:28 +00:00
konrad 28ec44c91f Add moving tasks between lists (#389)
Fix misspell

Add moving tasks between lists

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#389
2020-04-18 12:35:37 +00:00
kolaente 895d9613b5
Add endpoint to disable totp auth 2020-04-18 01:38:49 +02:00
kolaente 87d0c9088d
Add endpoint to get the current users totp status 2020-04-18 00:22:59 +02:00
konrad 24904585a2 Add 2fa for authentification (#383)
Fix user tests

Add swagger docs

Fix lint

Add totp check when logging in

Make totp enrollment work

Add migration for totp table

go mod vendor

Add routes for totp routes

Add route handler for totp routes

Add basic implementation to enroll a user in totp

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#383
2020-04-17 19:25:35 +00:00
kolaente 1dca8e96a7
Add changing email for users 2020-04-17 16:01:45 +02:00
kolaente e1ab2095fa
Update docs theme 2020-04-16 23:31:36 +02:00
renovate 12e18de8ad Update golang.org/x/crypto commit hash to 0848c95 (#371)
Update golang.org/x/crypto commit hash to 0848c95

Reviewed-on: vikunja/api#371
2020-04-15 06:48:46 +00:00
kolaente 025b00247d
Fix replace statements for tail 2020-04-14 21:37:00 +02:00
kolaente 5832991c4d
Fix tests after renaming json fields to snake_case 2020-04-13 23:27:55 +02:00
kolaente 81990f3f80
Update xorm redis cacher to use the xorm logger instead of a special seperate one 2020-04-13 23:18:33 +02:00
kolaente 368cbdf979
Fix gosec in drone 2020-04-13 23:04:59 +02:00
kolaente b8d7c97eb7
Add gosec static analysis 2020-04-13 22:30:09 +02:00
kolaente fb8ac92abf
Change all json fields to snake_case 2020-04-12 22:48:46 +02:00
kolaente 0bfb3a4709
Add configuration options for log level 2020-04-12 22:32:21 +02:00
kolaente 9559a68416
Update module prometheus/client_golang to v1 2020-04-12 22:06:24 +02:00
konrad d28f005552 Update xorm to v1 (#323)
Fix limit for databases other than sqlite

go mod tidy && go mod vendor

Remove unneeded break statements

Make everything work with the new xorm version

Fix xorm logging

Fix lint

Fix redis init

Fix using id field

Fix database init for testing

Change default database log level

Add xorm logger

Use const for postgres

go mod tidy

Merge branch 'master' into update/xorm

# Conflicts:
#	go.mod
#	go.sum
#	vendor/modules.txt

go mod vendor

Fix loading fixtures for postgres

Go mod vendor1

Update xorm to version 1

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#323
2020-04-12 17:29:24 +00:00
renovate 713560702b Update module go-redis/redis to v7 (#309)
Update module go-redis/redis to v7

Reviewed-on: vikunja/api#309
2020-04-11 21:43:04 +00:00
konrad 0ba121fdfb Task filters (#243)
Fix not returning errors

Fix integration tests

Add more tests

Make task filtering actually work

Change tests

Fix using filter conditions

Fix test

Remove unused fields

Fix static check

Remove start and end date fields on task collection

Fix misspell

add filter logic when getting tasks

Add parsing filter query parameters into task filters

Start adding support for filters

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#243
2020-04-11 14:20:33 +00:00
kolaente 0e2449482f
Fix dependencies 2020-04-09 23:06:57 +02:00
renovate 8d1a3f4fd7 Update module spf13/viper to v1.6.3 (#291)
Update module spf13/viper to v1.6.3

Reviewed-on: vikunja/api#291
2020-04-09 20:29:11 +00:00
renovate 1e0f2dde6a Update module go-redis/redis to v6.15.7 (#290)
Update module go-redis/redis to v6.15.7

Reviewed-on: vikunja/api#290
2020-04-09 20:28:58 +00:00
renovate 7abac07837 Update src.techknowlogick.com/xgo commit hash to bb0faa3 (#279)
Update src.techknowlogick.com/xgo commit hash to bb0faa3

Reviewed-on: vikunja/api#279
2020-04-09 04:36:03 +00:00
renovate 15d718fb1a Update module go-redis/redis to v7 (#277)
Update module go-redis/redis to v7

Reviewed-on: vikunja/api#277
2020-04-09 04:35:36 +00:00
renovate 4ca3f714ea Update module stretchr/testify to v1.5.1 (#274)
Update module stretchr/testify to v1.5.1

Reviewed-on: vikunja/api#274
2020-04-08 20:30:48 +00:00
renovate 6d1b123a25 Update module spf13/cobra to v0.0.7 (#271)
Update module spf13/cobra to v0.0.7

Reviewed-on: vikunja/api#271
2020-04-08 19:44:10 +00:00
renovate 13ebb98644 Update module spf13/viper to v1.6.2 (#272)
Update module spf13/viper to v1.6.2

Reviewed-on: vikunja/api#272
2020-04-08 18:43:35 +00:00
renovate dda558fe1c Update module prometheus/client_golang to v0.9.4 (#245)
Update module prometheus/client_golang to v0.9.4

Reviewed-on: vikunja/api#245
2020-04-07 20:42:06 +00:00
renovate 76f19db6e7 Update module labstack/echo/v4 to v4.1.16 (#241)
Update module labstack/echo/v4 to v4.1.16

Reviewed-on: vikunja/api#241
2020-04-07 18:59:53 +00:00
renovate f776b799b8 Update module olekukonko/tablewriter to v0.0.4 (#240)
Update module olekukonko/tablewriter to v0.0.4

Reviewed-on: vikunja/api#240
2020-04-07 16:58:27 +00:00
renovate 746a443660 Update module imdario/mergo to v0.3.9 (#238)
Update module imdario/mergo to v0.3.9

Reviewed-on: vikunja/api#238
2020-04-07 16:05:10 +00:00
renovate 026d3dc80d Update module go-redis/redis to v6.15.7 (#234)
Update module go-redis/redis to v6.15.7

Reviewed-on: vikunja/api#234
2020-04-07 12:31:33 +00:00
renovate fd7dd47d5e Update github.com/gordonklaus/ineffassign commit hash to 7953dde (#233)
Update github.com/gordonklaus/ineffassign commit hash to 7953dde

Reviewed-on: vikunja/api#233
2020-04-07 12:30:57 +00:00
renovate 1e5a1b4892 Update github.com/shurcooL/httpfs commit hash to 8d4bc4b (#229)
Update github.com/shurcooL/httpfs commit hash to 8d4bc4b

Reviewed-on: vikunja/api#229
2020-04-07 08:39:00 +00:00
renovate c7f6748761 Update github.com/jgautheron/goconst commit hash to cda7ea3 (#228)
Update github.com/jgautheron/goconst commit hash to cda7ea3

Reviewed-on: vikunja/api#228
2020-04-07 08:38:00 +00:00
renovate 7efa0069cb Update golang.org/x/crypto commit hash to 056763e (#222)
Update golang.org/x/crypto commit hash to 056763e

Update golang.org/x/lint commit hash to 738671d (#223)

Update golang.org/x/lint commit hash to 738671d

Reviewed-on: vikunja/api#223

Update src.techknowlogick.com/xgo commit hash to c43d4c4 (#224)

Update src.techknowlogick.com/xgo commit hash to c43d4c4

Reviewed-on: vikunja/api#224

Reviewed-on: vikunja/api#222
2020-04-07 08:37:20 +00:00
renovate 424cf80e5c Update golang.org/x/lint commit hash to 738671d (#223)
Update golang.org/x/lint commit hash to 738671d

Reviewed-on: vikunja/api#223
2020-04-07 07:29:05 +00:00
renovate 1448d9de98 Update src.techknowlogick.com/xgo commit hash to c43d4c4 (#224)
Update src.techknowlogick.com/xgo commit hash to c43d4c4

Reviewed-on: vikunja/api#224
2020-04-07 07:28:48 +00:00
renovate cbf01e118a Update github.com/c2h5oh/datasize commit hash to 28bbd47 (#212)
Update github.com/c2h5oh/datasize commit hash to 28bbd47

Reviewed-on: vikunja/api#212
2020-04-06 20:28:57 +00:00
renovate 191942fcd1 Update alpine Docker tag to v3.11 (#160)
Update alpine Docker tag to v3.11

Add github token for renovate (#164)

Add github token for renovate

Add github token for renovate

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#164

Update Renovate Configuration (#161)

Add labels to renovate

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#161

Co-authored-by: konrad <konrad@kola-entertainments.de>
Reviewed-on: vikunja/api#160
2020-04-06 06:40:42 +00:00
konrad d5c8f2541d Add github token for renovate (#164)
Add github token for renovate

Add github token for renovate

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#164
2020-04-05 19:16:21 +00:00
konrad 6e9ad3d808 Update Renovate Configuration (#161)
Add labels to renovate

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#161
2020-04-05 17:24:06 +00:00
renovate 36fdff5b8b Configure Renovate (#159)
Add renovate.json

Reviewed-on: vikunja/api#159
2020-04-05 15:11:27 +00:00
kolaente 8f4139f44a
0.12 Release Preparations 2020-04-04 22:29:32 +02:00
kolaente e7706a20dd
Fix call to println
Signed-off-by: kolaente <k@knt.li>
2020-04-02 21:38:16 +02:00
kolaente 6d1018e647
Fix searching for config in home directories
Signed-off-by: kolaente <k@knt.li>
2020-04-02 21:13:42 +02:00
kolaente 11246b948f
Fix getting the authenticated user with caldav
Signed-off-by: kolaente <k@knt.li>
2020-04-01 22:51:57 +02:00
konrad 7b619bd084 Colors for lists and namespaces (#155)
Add Hex Color properties migration

Add Hex Color properties

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#155
2020-03-22 21:09:32 +00:00
kolaente 35f05cbbec
Show lists as archived if their namespace is archived
Signed-off-by: kolaente <k@knt.li>
2020-03-22 21:39:57 +01:00
kolaente 0a1887d6e7
Fix archiving namespaces
Signed-off-by: kolaente <k@knt.li>
2020-03-22 19:02:53 +01:00
kolaente c6d22bb1f8
Fix updating lists with an identifier
Signed-off-by: kolaente <k@knt.li>
2020-03-22 18:45:10 +01:00
kolaente 6132863f4a
Fix getting archived lists and namespaces
Signed-off-by: kolaente <k@knt.li>
2020-03-22 18:24:54 +01:00
kolaente 26ffd2fd22
Fix getting one namespace
Signed-off-by: kolaente <k@knt.li>
2020-03-21 13:55:42 +01:00
funkythings 51c74de1de expand relative path ~/.config/vikunja to $HOME/.config/vikunja **WINDOWS** (#147)
apply correct formatting

add quotes to make the yaml gods happy

log path of config file used by viper

resolve merge conflicts: windows compatible $HOME path

Prepare changelog & readme for 0.11 release

expand relative path ~/.config/vikunja to $HOME/.config/vikunja (#146)

update dependencies

expand relative path ~/.config/vikunja

Co-authored-by: Julian <juliangaal@protonmail.com>
Co-authored-by: konrad <konrad@kola-entertainments.de>

Co-authored-by: Julian Gaal <gjulian@uos.de>
Reviewed-on: vikunja/api#147
Reviewed-by: konrad <konrad@kola-entertainments.de>
2020-03-20 21:21:44 +00:00
konrad 5126330a10 Add support for archiving lists and namespaces (#152)
Add query param to get all lists including archived ones

Add query param to get all namespaces including archived ones

Fix getting lists by namespace only not archived lists

Fix misspell

Fix lint

Merge branch 'master' into feature/archive-lists-namespaces

Add docs for error codes

Fix archive error codes

Don't let archived lists show up in general lists

Fix updating description

Fix updating lists with link shares

More comments

Fix un-archiving lists

Move check for archiving a list to canWrite Check

Add more tests

Add more checks

Add checks for namespaces and lists

Add namespace edit

Add tests

Add migrations and filter

Add basic tests

Add is archived property to lists and namespaces

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#152
2020-03-15 21:50:39 +00:00
konrad 4472020ee9 Add workaround for timezones on windows (#151)
Add workaround for timezones on windows

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#151
2020-03-09 22:41:08 +00:00
kolaente 7de26a462f
Add build time to compile flags 2020-03-02 20:24:26 +01:00
kolaente f2f17b11e8
Change release bucket 2020-03-01 22:51:50 +01:00
konrad cdd068cdb6 Add empty avatar provider (#149)
Fix lint

Add docs for avatar configuration

Add default avatar provider

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#149
2020-03-01 21:10:25 +00:00
konrad 584e3af237 Add proxying gravatar requests for user avatars (#148)
Fix getting avatar based on email

Remove avatarUrl from user struct

Fix staticcheck

Add default avatar size

Add config option for caching avatars

go mod vendor

Add swagger docs

Add proxying gravatar requests for user avatars

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#148
2020-03-01 20:30:37 +00:00
kolaente 7ddfa76a84
Prepare changelog & readme for 0.11 release 2020-03-01 17:32:38 +01:00
funkythings 1e03c39141 expand relative path ~/.config/vikunja to $HOME/.config/vikunja (#146)
update dependencies

expand relative path ~/.config/vikunja

Authored-by: Julian Gaal <gjulian@uos.de>
Reviewed-on: vikunja/api#146
Reviewed-by: konrad <konrad@kola-entertainments.de>
2020-02-29 19:51:47 +00:00
konrad 88bf8f3df0 Fix updating dates when marking a task as done (#145)
Fix lint

Fix reminders not being updated

Fix updating dates when marking a task as done

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#145
2020-02-26 21:09:45 +00:00
kolaente c551706b52
Make sure the author is returned when creating a new comment 2020-02-25 21:18:42 +01:00
konrad 1f039c4cda Task Comments (#138)
Add swagger docs

Add integration tests

Add tests

Add task comment test fixtures

Add config option to enable/disable task comments

Add custom error if a task comment does not exist

Fix lint

Add getting author when getting a single comment

Fix getting comments/comments author

Add rights check to ReadAll

+ actually get the comment author

Add migration and table definitions

Add routes

Add ReadOne method

Add basic crud rights

Signed-off-by: kolaente <k@knt.li>

Implement basic crudable functions for task comments

Signed-off-by: kolaente <k@knt.li>

Start adding task comments

Signed-off-by: kolaente <k@knt.li>
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#138
2020-02-19 21:57:56 +00:00
konrad 5901cf64b4 Fix inserting task structure with related tasks (#142)
Fix typo

Fix creating related tasks

Add better logging

Fix inserting task structure with related tasks

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#142
2020-02-18 22:00:54 +00:00
kolaente f87bb6d8ef
Update theme 2020-02-18 18:47:19 +01:00
kolaente 021a715aff
Fix frontend url for wunderlist migration in docs 2020-02-17 19:52:26 +01:00
konrad e95a6eeb11 Explicitly disable wunderlist migration by default (#141)
Fix wunderlist callback link in docs

Explicitly disable wunderlist migration by default

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#141
2020-02-17 17:29:17 +00:00
Dpvh f46e9cb64e Fixed typo in docker-compose example (#140)
Reviewed-on: vikunja/api#140
Reviewed-by: konrad <konrad@kola-entertainments.de>
2020-02-17 16:47:55 +00:00
jtojnar ce5be947b4 Add postgres support (#135)
Revert fixture fixes for postgres

Use postgres connection string with spaces instead of url

Fix label order

Make postgres tests in ci less verbose

Add sequence update script

Skip resets in postgres

Remove option to skip resets in postgres

Make postgres tests in ci verboseq

Update test fixtures database

Fix file tests on postgres

Add postgres options to sample config

Make sure tests init test fixtures before running the actual tests

Fix issues with IDs too big to fit in an int

Fix duplicate auto incremented IDs

Refactor / Fix team tests

Refactor team member tests

Fix team member create

Fix label test

Fix getting labels

Fix test fixtures for postgresql

Fix connection string params

Disable ssl mode on postgres integration tests

Disable ssl mode on postgres tests

Use sprintf to create the connection string for postgresql

fixup! Add postgres support

Add postgres support

Added generate as a make dependency for make build

Clarify docs on building

Co-authored-by: kolaente <k@knt.li>
Co-authored-by: Jan Tojnar <jtojnar@gmail.com>
Reviewed-on: vikunja/api#135
2020-02-16 21:42:04 +00:00
kolaente 7e42724439
Added generate as a make dependency for make build
Clarify docs on building

Signed-off-by: kolaente <k@knt.li>
2020-02-15 11:35:24 +01:00
konrad caf91d1904 Update xorm to use the new import path (#133)
Fix ineffassign

Fix getting all labels including the ones not associated to a task

Signed-off-by: kolaente <k@knt.li>

Fix logging sql queries

Signed-off-by: kolaente <k@knt.li>

Start fixing getting all labels

Update xormigrate

Update xorm to use the new import path

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#133
2020-02-14 16:34:25 +00:00
shilch 014ec2be69 Use relative url in .gitmodules (#132)
Use relative url in .gitmodules

Co-authored-by: Simon Hilchenbach <simon@hilchenba.ch>
Reviewed-on: vikunja/api#132
Reviewed-by: konrad <konrad@kola-entertainments.de>
2020-02-13 21:18:49 +00:00
kolaente 5d96c62a47
Fix time zone settings not working in Docker 2020-02-08 23:18:41 +01:00
kolaente 1c3b35fa6f
Add more logging to web handler methods 2020-02-08 22:45:38 +01:00
kolaente d84a160054
Remove double user field 2020-02-08 22:12:06 +01:00
konrad db2d868eed Return iso dates for everything date related from the api (#130)
Remove traces of unix timestamp

Revert renaming reminder table column

Fix staticcheck

Remove unused table call

Add migration for renaming reminders table

Fix issues with using TimeStamp

Fix lint

Updated all created / updated fields to use TimeStamps

Add comments

Convert all created / updated fields to datetime

Add time util package

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#130
2020-02-08 12:48:49 +00:00
kolaente 1f99238019
Update copyright header 2020-02-07 17:27:45 +01:00
konrad f603b41d99 Better efficency for loading teams (#128)
Fix staticcheck

Better performance for getting teams on a namespace

Better performance for getting teams on a list

Fix lint

Fix swagger

Signed-off-by: kolaente <k@knt.li>

Make loading a single full team more efficent

Signed-off-by: kolaente <k@knt.li>

Make loading teams more efficent

Signed-off-by: kolaente <k@knt.li>

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#128
2020-01-27 17:28:17 +00:00
konrad 2abb858859 Add rate limit by ip for non-authenticated routes (#127)
Add rate limit by ip for non-authenticated routes

Signed-off-by: kolaente <k@knt.li>

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#127
2020-01-26 19:53:47 +00:00
konrad a464d1760c Add logging for invalid model errors (#126)
Add logging for invalid model errors

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#126
2020-01-26 19:40:23 +00:00
konrad fc65052ba0 Add config options for task attachments (#125)
Add config options for task attachments

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#125
2020-01-26 19:10:31 +00:00
konrad b2b1546a8f Add config options for cors handling (#124)
Add config options for cors handling

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#124
2020-01-26 19:09:54 +00:00
konrad 7e9446ea07 Refactor User and DB handling (#123)
fix copyright date

Add more user tests

More user tests

More user tests

Start refactoring user tests

Docs

Fix lint

Fix db fixtures init in tests

Fix models test

Fix loading fixtures

Fix ineffasign

Fix lint

Fix integration tests

Fix init of test engine creation

Fix user related tests

Better handling of creating test enging

Moved all fixtures to db package

Moved all fixtures to db package

Moved user related stuff to seperate package

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#123
2020-01-26 17:08:06 +00:00
konrad 8c33e24e92 Migration Improvements (#122)
Update swagger docs

Update docs

Let the wunderlist migrator use the registerRoutes function

Add migration status table

Add migration status

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#122
2020-01-20 19:48:46 +00:00
kolaente 0654ead831
Update version link in README 2020-01-19 22:27:19 +01:00
kolaente 2b80025cc0
Update changelog for v0.10 2020-01-19 22:25:38 +01:00
konrad 3081338a37 Use redis INCRBY and DECRBY when updating metrics values (#121)
Move test coverage processing to a seperate command

Use redis INCRBY and DECRBY when updating metrics values

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#121
2020-01-19 17:26:26 +00:00
konrad 9e39399689 Migration (#120)
Go mod tidy

[skip ci] Add modules/migration to docs

[skip ci] update date

fmt

Merge branch 'master' into feature/migration

# Conflicts:
#	pkg/routes/api/v1/info.go

Add docs on how to create a migrator

Add available migrators to /info endpoint

Return a message once everything was migrated successfully

Add swagger docs for wunderlist migration

Docs for migration [skip ci]

Fix due date fixture in migration test

Fix staticcheck

Fix lint

Logging and cleanup

Make the migrator work with real data

Add routes for migration

Fix misspell

Add method to store a full vikunja structure into vikunja

Add getting all data from wunderlist

Add attachment migration from wunderlist

Add done and done at to wunderlist migrator

Add todo

Add wunderlist auth url implementation

Fix lint

Finish wunderlist migration

Added all structs for the wunderlist migratior

Fix owner field being null for user shared namespaces (#119)

Update copyright year (#118)

Add option to disable registration (#117)

Added migrator interface

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#120
2020-01-19 16:52:16 +00:00
konrad 1d2cdf0fb8 Fix owner field being null for user shared namespaces (#119) 2020-01-09 19:24:08 +00:00
konrad a0c5e4e985 Update copyright year (#118) 2020-01-09 17:33:22 +00:00
konrad 402cef3c15 Add option to disable registration (#117) 2019-12-30 23:27:02 +00:00
kolaente b9f0ba84ab
Add motd config option to docs 2019-12-25 17:27:10 +01:00
konrad 7cdc997191 Fix new tasks not getting a new task index (#116) 2019-12-08 15:10:34 +00:00
konrad 720df3cbed Add task identifier (#115) 2019-12-07 22:28:45 +00:00
konrad 1a47d7d80d Use utf8mb4 instead of plain utf8 (#114) 2019-12-07 20:14:13 +00:00
konrad 62e550bf35 Add user token renew (#113) 2019-12-07 19:52:04 +00:00
kolaente 10ab8ef4d9
Fix task collection tests 2019-12-07 17:21:37 +01:00
kolaente d483fe8beb
Fix passing sort_by and order_by as query path arrays 2019-12-07 16:57:19 +01:00
kolaente 1555f04bd2
Fix sorting tasks by bool values
There was a bug where it would return all tasks with a true value before the ones with a false value. This is the exact opposite of what the db does, leading to wrongly sorted values
2019-12-07 16:56:18 +01:00
konrad d8399e374c Sort Order for tasks (#110) 2019-12-07 14:30:51 +00:00
shilch e890001ee1 Consistent copyright text in file headers (#112) 2019-12-04 19:39:56 +00:00
shilch 38653b49c7 Add tests for md5 generation (#111) 2019-12-04 19:19:38 +00:00
konrad 7e4deab8f7 Task collection improvements (#109) 2019-12-01 13:38:11 +00:00
konrad d96831fe3a Endpoint to get tasks on a list (#108) 2019-11-29 22:59:20 +00:00
kolaente 4c55b4455c
update theme 2019-11-25 19:12:42 +01:00
kolaente b6308f783b
Add files volume to docker compose docs 2019-11-25 19:08:24 +01:00
kolaente 843c1e5193
Update docs with a traefik configuration 2019-11-25 19:07:11 +01:00
kolaente 6045f4a426
Prepare changelog & readme for 0.9 release 2019-11-24 19:38:23 +01:00
kolaente 7be14239a3
[skip ci] Add changelog in repo 2019-11-24 19:33:16 +01:00
konrad 27ba31366c Fix default logging settings (#107) 2019-11-24 18:17:59 +00:00
kolaente 9dc419e854
Add file volume to the docker image 2019-11-24 18:36:43 +01:00
kolaente 37efd5cb49
Fixed removing reminders 2019-11-24 16:13:16 +01:00
kolaente dcec9511dc
Fixed a bug where deleting an attachment would cause a nil panic 2019-11-19 23:07:48 +01:00
kolaente c203d73b33
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 2019-11-03 23:08:26 +01:00
konrad 9be5ab248c Add endpoint to get a single task (#106) 2019-11-02 20:33:18 +00:00
kolaente ed4c17892e
Fixed building docs theme 2019-10-30 21:53:57 +01:00
kolaente 886110a20a
update docs theme 2019-10-30 21:47:16 +01:00
konrad 8948a5f219 Improve pagination (#105) 2019-10-23 21:11:40 +00:00
kolaente 89f385a53d
Move from markdown lists to Vikunja for roadmap [skip ci] 2019-10-20 20:52:41 +02:00
kolaente 82f11c4ec2
Fixed not getting all labels when retrieving a list with all tasks 2019-10-20 17:56:53 +02:00
kolaente eb279fdba3
Refactored getting task IDs for labels 2019-10-20 17:55:54 +02:00
kolaente 48e422b379
[skip ci] Prepare the roadmap for migration 2019-10-20 17:54:32 +02:00
kolaente 854fde1e4c
Fixed panic when using link share and metrics 2019-10-19 22:34:33 +02:00
kolaente b653278e42
Reverted telegram notification in CI "fix" 2019-10-19 18:18:39 +02:00
kolaente 35e4ab198f
Fixed telegram notification in CI 2019-10-19 16:57:39 +02:00
kolaente b81cd6128a
Fixed error when setting max file size on 32-Bit systems 2019-10-18 17:30:25 +02:00
konrad 2169464983 Task Attachments (#104) 2019-10-16 20:52:29 +00:00
kolaente e2f481a6e5
Fixed migration for task relations 2019-10-13 11:44:56 +02:00
kolaente af1d1e1921
[skip ci] updated todo 2019-09-25 20:45:20 +02:00
konrad 8fe33fd616 Task Relations (#103) 2019-09-25 18:44:41 +00:00
konrad 1272255975 Added percent done to tasks (#102) 2019-09-21 10:52:10 +00:00
kolaente 1e2ec17343
[skip ci] update todo 2019-09-20 18:45:28 +02:00
konrad 2f267f3f68 Moved `teams_{namespace|list}_*` to `{namespace|list}_teams_*` for better consistency (#101) 2019-09-20 16:39:02 +00:00
konrad 694fc62495 Refactored getting all lists for a namespace (#100) 2019-09-20 16:16:31 +00:00
konrad 8d5a2685c4 Fixed labels being displayed multiple times if they were associated with more than one task (#99) 2019-09-20 15:52:09 +00:00
konrad 71ef86e0df Added more infos to a link share auth (#98) 2019-09-08 19:22:54 +00:00
konrad 6140920cb8 Fixed rate limit panic when authenticatin with a link share auth token (#97) 2019-09-08 19:11:42 +00:00
konrad fdd1624121 Small link share fixes (#96) 2019-09-07 13:19:23 +00:00
kolaente 158ad631e1
[skip ci] update todo 2019-09-05 22:48:51 +02:00
kolaente c126d2f55c
[skip ci] update todo 2019-09-05 22:00:21 +02:00
kolaente 1d98e4cabe
Fixed metrics on/off setting 2019-09-01 18:39:03 +02:00
kolaente 43676f045c
Switched default logger to stdout instead of stderr 2019-09-01 18:23:35 +02:00
kolaente ba7db545fe
Added extra depth to logging to correctly show the functions calling the logger in logs 2019-09-01 17:56:22 +02:00
kolaente 021ee48ec3
Made sure all tags are checked out when building in docker 2019-09-01 11:43:01 +02:00
kolaente a324921dce
Updated Readme [skip ci] 2019-09-01 11:01:36 +02:00
konrad 8d57923a7d Sharing of lists via public links (#94) 2019-08-31 20:56:41 +00:00
kolaente 88ea66798b
[skip ci] updated todo 2019-08-27 22:51:27 +02:00
konrad 66cdd79666 Refactor ListTask to Task (#92) 2019-08-14 20:19:04 +00:00
konrad be14634e1e GetUser now returns a pointer (#93) 2019-08-14 19:59:31 +00:00
kolaente 8fbe721453
[skip ci] updated todo 2019-08-13 22:31:05 +02:00
kolaente 5d3e169883
[skip ci] update todo 2019-08-11 14:56:50 +02:00
kolaente 208eb4e68c
Fixed swaggerdocs of description fields 2019-07-21 23:57:19 +02:00
kolaente 8d76280811
[skip ci] updated todo 2019-07-21 23:53:04 +02:00
kolaente 10fbbe1db6
Moved metrics init to seperate methods 2019-07-21 23:44:12 +02:00
konrad 4327a559e5 feature/rate-limit (#91) 2019-07-21 21:27:30 +00:00
kolaente 2e599e792e
[skip ci] update todo 2019-07-20 20:46:06 +02:00
konrad 48826a6ed7 Logger refactoring (#90) 2019-07-20 18:12:10 +00:00
kolaente 15a0963bd1
[skip ci] update todo 2019-07-18 22:54:55 +02:00
konrad 9a7d470ce2 Limit the test pipeline to run only on pull requests (#89) 2019-07-18 20:28:56 +00:00
konrad ef0e7c9769 Use longtext instead of varchar(1000) on description fields (#88) 2019-07-18 19:56:34 +00:00
konrad 12eaddc8ee Added http endpoint to list all users on a list (#87) 2019-07-18 16:38:21 +00:00
konrad b63928850a Simplify structure by having less files (#86) 2019-07-16 14:15:40 +00:00
kolaente 4005cd2f32
[skip ci] Updated todo 2019-07-16 01:04:19 +02:00
kolaente 5cc89fd474
[skip ci] Updated todo 2019-07-16 00:56:25 +02:00
konrad e2d9de191d /info endpoint (#85) 2019-07-15 22:54:38 +00:00
kolaente c3ea45d900
Removed rancher from drone config 2019-07-13 19:23:09 +02:00
konrad 50ca8bd28e Statically compile templates in the final binary (#84) 2019-07-11 19:10:42 +00:00
konrad 1f1a079fd3 Better config handling with constants (#83) 2019-07-06 20:12:26 +00:00
kolaente f1d21ea52b
Use the auth methods to get IDs to avoid unneeded casts 2019-06-28 10:21:48 +02:00
kolaente fc3c5f2187
Updated the web handler with updated param binder 2019-06-28 09:13:17 +02:00
kolaente 5d3b6573ca
Updated echo to use the latest version 2019-06-28 09:01:50 +02:00
konrad 9930f98f8e Compress binaries after building them (#81) 2019-06-22 22:07:58 +00:00
konrad 16825ba7c6 Update echo (#82) 2019-06-22 21:51:58 +00:00
kolaente 4e8c2a7bf6
Fixed avatar url when logging in 2019-06-06 12:28:08 +02:00
kolaente 7f5a6e338c
[skip ci] updated todo 2019-06-06 12:13:15 +02:00
konrad 2763df20c9
[skip ci] updated todo 2019-06-05 22:25:55 +02:00
kolaente 9bc09ebbe5
Fixed user sharing not working 2019-06-04 19:45:09 +02:00
konrad 6746984c97 Simplified the docker image (#80) 2019-06-02 14:37:10 +00:00
konrad 7acf318b28 Fixed duedate spelling issue (#79) 2019-05-31 08:10:56 +00:00
konrad f638fae4fd Add the md5-hashed user email to user objects for use with gravatar (#78) 2019-05-31 07:24:59 +00:00
konrad bd930bf654
[skip ci] rearrange todo 2019-05-30 22:32:24 +02:00
kolaente 2cdb0eed3e
[skip ci] updated todo 2019-05-27 18:30:08 +02:00
konrad 85d08e5697 Fixed check if the user really exists before updating/deleting its rights (#77) 2019-05-25 10:16:55 +00:00
kolaente 4c4eb76fed
[skip ci] updated todo 2019-05-25 11:53:16 +02:00
konrad 50d1f29125 Use the username instead of a user when adding a user to a team or giving it rights (#76) 2019-05-25 09:47:16 +00:00
2012 changed files with 293572 additions and 153984 deletions

View File

@ -6,16 +6,45 @@ workspace:
path: src/code.vikunja.io/api
services:
- name: test-db-unit
- name: test-mysql-unit
image: mariadb:10
environment:
MYSQL_ROOT_PASSWORD: vikunjatest
MYSQL_DATABASE: vikunjatest
- name: test-db-integration
- name: test-mysql-integration
image: mariadb:10
environment:
MYSQL_ROOT_PASSWORD: vikunjatest
MYSQL_DATABASE: vikunjatest
- name: test-mysql-migration
image: mariadb:10
environment:
MYSQL_ROOT_PASSWORD: vikunjatest
MYSQL_DATABASE: vikunjatest
- name: test-postgres-unit
image: postgres:12
environment:
POSTGRES_PASSWORD: vikunjatest
POSTGRES_DB: vikunjatest
- name: test-postgres-integration
image: postgres:12
environment:
POSTGRES_PASSWORD: vikunjatest
POSTGRES_DB: vikunjatest
- name: test-postgres-migration
image: postgres:12
environment:
POSTGRES_PASSWORD: vikunjatest
POSTGRES_DB: vikunjatest
trigger:
branch:
include:
- master
event:
include:
- push
- pull_request
steps:
- name: fetch-tags
@ -29,6 +58,18 @@ steps:
environment:
GOFLAGS: '-mod=vendor'
commands:
- make build
when:
event: [ push, tag, pull_request ]
- name: lint
image: vikunja/golang-build:latest
pull: true
environment:
GOFLAGS: '-mod=vendor'
depends_on: [ build ]
commands:
- make generate
- make lint
- make fmt-check
# - make got-swag # Commented out until we figured out how to get this working on drone
@ -37,7 +78,71 @@ steps:
- make goconst-check
- make gocyclo-check
- make static-check
- make build
- wget -O - -q https://raw.githubusercontent.com/securego/gosec/master/install.sh | sh -s -- -b $GOPATH/bin v2.2.0 # Need to manually install as it does not support being installed via go modules like the rest.
- make gosec-check
when:
event: [ push, tag, pull_request ]
- name: test-migration-prepare
image: kolaente/toolbox:latest
pull: true
commands:
# Get the latest version
- wget https://dl.vikunja.io/api/master/vikunja-master-linux-amd64-full.zip -q -O vikunja-latest.zip
- unzip vikunja-latest.zip vikunja-master-linux-amd64
- name: test-migration-sqlite
image: kolaente/toolbox:latest
pull: true
depends_on: [ test-migration-prepare, build ]
environment:
VIKUNJA_DATABASE_TYPE: sqlite
VIKUNJA_DATABASE_PATH: ./vikunja-migration-test.db
VIKUNJA_LOG_DATABASE: stdout
VIKUNJA_LOG_DATABASELEVEL: debug
commands:
- ./vikunja-master-linux-amd64 migrate
# Run the migrations from the binary build in the step before
- ./vikunja migrate
when:
event: [ push, tag, pull_request ]
- name: test-migration-mysql
image: kolaente/toolbox:latest
pull: true
depends_on: [ test-migration-prepare, build ]
environment:
VIKUNJA_DATABASE_TYPE: mysql
VIKUNJA_DATABASE_HOST: test-mysql-migration
VIKUNJA_DATABASE_USER: root
VIKUNJA_DATABASE_PASSWORD: vikunjatest
VIKUNJA_DATABASE_DATABASE: vikunjatest
VIKUNJA_LOG_DATABASE: stdout
VIKUNJA_LOG_DATABASELEVEL: debug
commands:
- ./vikunja-master-linux-amd64 migrate
# Run the migrations from the binary build in the step before
- ./vikunja migrate
when:
event: [ push, tag, pull_request ]
- name: test-migration-psql
image: kolaente/toolbox:latest
pull: true
depends_on: [ test-migration-prepare, build ]
environment:
VIKUNJA_DATABASE_TYPE: postgres
VIKUNJA_DATABASE_HOST: test-postgres-migration
VIKUNJA_DATABASE_USER: postgres
VIKUNJA_DATABASE_PASSWORD: vikunjatest
VIKUNJA_DATABASE_DATABASE: vikunjatest
VIKUNJA_DATABASE_SSLMODE: disable
VIKUNJA_LOG_DATABASE: stdout
VIKUNJA_LOG_DATABASELEVEL: debug
commands:
- ./vikunja-master-linux-amd64 migrate
# Run the migrations from the binary build in the step before
- ./vikunja migrate
when:
event: [ push, tag, pull_request ]
@ -45,8 +150,9 @@ steps:
image: vikunja/golang-build:latest
pull: true
commands:
- make generate
- make test
depends_on: [ build ]
depends_on: [ fetch-tags ]
when:
event: [ push, tag, pull_request ]
@ -57,8 +163,9 @@ steps:
VIKUNJA_TESTS_USE_CONFIG: 1
VIKUNJA_DATABASE_TYPE: sqlite
commands:
- make generate
- make test
depends_on: [ build ]
depends_on: [ fetch-tags ]
when:
event: [ push, tag, pull_request ]
@ -68,13 +175,32 @@ steps:
environment:
VIKUNJA_TESTS_USE_CONFIG: 1
VIKUNJA_DATABASE_TYPE: mysql
VIKUNJA_DATABASE_HOST: test-db-unit
VIKUNJA_DATABASE_HOST: test-mysql-unit
VIKUNJA_DATABASE_USER: root
VIKUNJA_DATABASE_PASSWORD: vikunjatest
VIKUNJA_DATABASE_DATABASE: vikunjatest
commands:
- make generate
- make test
depends_on: [ build ]
depends_on: [ fetch-tags ]
when:
event: [ push, tag, pull_request ]
- name: test-postgres
image: vikunja/golang-build:latest
pull: true
environment:
VIKUNJA_TESTS_USE_CONFIG: 1
VIKUNJA_DATABASE_TYPE: postgres
VIKUNJA_DATABASE_HOST: test-postgres-unit
VIKUNJA_DATABASE_USER: postgres
VIKUNJA_DATABASE_PASSWORD: vikunjatest
VIKUNJA_DATABASE_DATABASE: vikunjatest
VIKUNJA_DATABASE_SSLMODE: disable
commands:
- make generate
- make test
depends_on: [ fetch-tags ]
when:
event: [ push, tag, pull_request ]
@ -82,8 +208,9 @@ steps:
image: vikunja/golang-build:latest
pull: true
commands:
- make generate
- make integration-test
depends_on: [ build ]
depends_on: [ fetch-tags ]
when:
event: [ push, tag, pull_request ]
@ -94,8 +221,9 @@ steps:
VIKUNJA_TESTS_USE_CONFIG: 1
VIKUNJA_DATABASE_TYPE: sqlite
commands:
- make generate
- make integration-test
depends_on: [ build ]
depends_on: [ fetch-tags ]
when:
event: [ push, tag, pull_request ]
@ -105,13 +233,32 @@ steps:
environment:
VIKUNJA_TESTS_USE_CONFIG: 1
VIKUNJA_DATABASE_TYPE: mysql
VIKUNJA_DATABASE_HOST: test-db-integration
VIKUNJA_DATABASE_HOST: test-mysql-integration
VIKUNJA_DATABASE_USER: root
VIKUNJA_DATABASE_PASSWORD: vikunjatest
VIKUNJA_DATABASE_DATABASE: vikunjatest
commands:
- make generate
- make integration-test
depends_on: [ build ]
depends_on: [ fetch-tags ]
when:
event: [ push, tag, pull_request ]
- name: integration-test-postgres
image: vikunja/golang-build:latest
pull: true
environment:
VIKUNJA_TESTS_USE_CONFIG: 1
VIKUNJA_DATABASE_TYPE: postgres
VIKUNJA_DATABASE_HOST: test-postgres-integration
VIKUNJA_DATABASE_USER: postgres
VIKUNJA_DATABASE_PASSWORD: vikunjatest
VIKUNJA_DATABASE_DATABASE: vikunjatest
VIKUNJA_DATABASE_SSLMODE: disable
commands:
- make generate
- make integration-test
depends_on: [ fetch-tags ]
when:
event: [ push, tag, pull_request ]
@ -148,7 +295,9 @@ steps:
pull: true
commands:
- export PATH=$PATH:$GOPATH/bin
- make generate
- make release-dirs
depends_on: [ fetch-tags ]
- name: static-build-windows
image: techknowlogick/xgo:latest
@ -186,13 +335,21 @@ steps:
- make release-darwin
depends_on: [ before-static-build ]
- name: after-build-static
image: techknowlogick/xgo:latest
- name: after-build-compress
image: kolaente/upx
pull: true
depends_on:
- static-build-windows
- static-build-linux
- static-build-darwin
commands:
- make release-compress
- name: after-build-static
image: techknowlogick/xgo:latest
pull: true
depends_on:
- after-build-compress
commands:
- make release-copy
- make release-check
@ -212,7 +369,7 @@ steps:
- dist/zip/*
detach_sign: true
# Push the releases to our pseudo-s3-bucket
# Push the releases to our pseudo-s3-bucket
- name: release-latest
image: plugins/s3:1
pull: true
@ -226,7 +383,7 @@ steps:
path_style: true
strip_prefix: dist/zip/
source: dist/zip/*
target: /master/
target: /api/master/
depends_on: [ sign-release ]
# Build a debian package and push it to our bucket
@ -260,7 +417,7 @@ steps:
image: plugins/s3:1
pull: true
settings:
bucket: vikunja-deb
bucket: vikunja
access_key:
from_secret: aws_access_key_id
secret_key:
@ -269,10 +426,10 @@ steps:
path_style: true
strip_prefix: debian
source: debian/*/*/*/*/*
target: /
target: /deb/
depends_on: [ deb-structure ]
# Build the docker image and push it to docker hub
# Build the docker image and push it to docker hub
- name: docker
image: plugins/docker
pull: true
@ -283,25 +440,11 @@ steps:
from_secret: docker_password
repo: vikunja/api
auto_tag: true
# Update the instance on try.vikunja.io
- name: rancher
image: peloton/drone-rancher
settings:
url: http://server01.kolaente.de:8080/v1
access_key:
from_secret: RANCHER_ACCESS_KEY
secret_key:
from_secret: RANCHER_SECRET_KEY
service: vikunja-dev/api
docker_image: vikunja/api
confirm: true
depends_on: [ docker ]
depends_on: [ fetch-tags ]
- name: telegram
image: appleboy/drone-telegram
depends_on:
- rancher
- release-latest
settings:
token:
@ -345,7 +488,9 @@ steps:
image: techknowlogick/xgo:latest
pull: true
commands:
- make generate
- make release-dirs
depends_on: [ fetch-tags ]
- name: static-build-windows
image: techknowlogick/xgo:latest
@ -383,13 +528,21 @@ steps:
- make release-darwin
depends_on: [ before-static-build ]
- name: after-build-static
image: techknowlogick/xgo:latest
- name: after-build-compress
image: kolaente/upx
pull: true
depends_on:
- static-build-windows
- static-build-linux
- static-build-darwin
commands:
- make release-compress
- name: after-build-static
image: techknowlogick/xgo:latest
pull: true
depends_on:
- after-build-compress
commands:
- make release-copy
- make release-check
@ -409,7 +562,7 @@ steps:
- dist/zip/*
detach_sign: true
# Push the releases to our pseudo-s3-bucket
# Push the releases to our pseudo-s3-bucket
- name: release-version
image: plugins/s3:1
pull: true
@ -423,7 +576,7 @@ steps:
path_style: true
strip_prefix: dist/zip/
source: dist/zip/*
target: /${DRONE_TAG##v}/
target: /api/${DRONE_TAG##v}/
depends_on: [ sign-release ]
# Build a debian package and push it to our bucket
@ -457,7 +610,7 @@ steps:
image: plugins/s3:1
pull: true
settings:
bucket: vikunja-deb
bucket: vikunja
access_key:
from_secret: aws_access_key_id
secret_key:
@ -466,10 +619,10 @@ steps:
path_style: true
strip_prefix: debian
source: debian/*/*/*/*/*
target: /
target: /deb/
depends_on: [ deb-structure ]
# Build the docker image and push it to docker hub
# Build the docker image and push it to docker hub
- name: docker
image: plugins/docker
pull: true
@ -480,6 +633,7 @@ steps:
from_secret: docker_password
repo: vikunja/api
auto_tag: true
depends_on: [ fetch-tags ]
- name: telegram
image: appleboy/drone-telegram
@ -524,6 +678,15 @@ steps:
- git submodule update --init
- git submodule update --recursive --remote
- name: theme
image: kolaente/yarn
pull: true
group: build-static
commands:
- cd docs/themes/vikunja
- yarn --production=false
- ./node_modules/.bin/gulp prod
- name: build
image: monachus/hugo:v0.54.0
pull: true
@ -543,15 +706,3 @@ steps:
repo: vikunja/docs
context: docs/
dockerfile: docs/Dockerfile
- name: rancher
image: peloton/drone-rancher
settings:
url: http://server01.kolaente.de:8080/v1
access_key:
from_secret: RANCHER_ACCESS_KEY
secret_key:
from_secret: RANCHER_SECRET_KEY
service: vikunja-website/docs
docker_image: vikunja/docs
confirm: true

4
.gitignore vendored
View File

@ -17,3 +17,7 @@ debian/
logs/
docs/public/
docs/resources/
pkg/static/templates_vfsdata.go
files/
!pkg/files/
vikunja-dump*

2
.gitmodules vendored
View File

@ -1,3 +1,3 @@
[submodule "docs/themes/vikunja"]
path = docs/themes/vikunja
url = https://git.kolaente.de/vikunja/theme.git
url = ../theme.git

390
CHANGELOG.md Normal file
View File

@ -0,0 +1,390 @@
# Changelog
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).
All releases can be found on https://code.vikunja.io/api/releases.
## [0.13.1] - 2020-05-19
### Fixed
* Don't get all tasks if a user has no lists
## [0.13] - 2020-05-12
#### Added
* Add 2fa for authentification (#383)
* Add categories to error docs
* Add changing email for users
* Add community link
* Add configuration options for log level
* Add creating a new first bucket when creating a new list
* Add docs for changing frontend url
* Add endpoint to disable totp auth
* Add endpoint to get the current users totp status
* Add explanation to docs about cors
* Add github token for renovate (#164)
* Add gosec static analysis
* Add moving tasks between lists (#389)
* Add real buckets for tasks which don't have one (#446)
* Add traefik 2 example configuration
* Configure Renovate (#159)
* Kanban (#393)
* Task filters (#243)
* Task Position (#412)
#### Fixed
* Add checking and logging when trying to put a task into a nonexisting bucket
* Fix bucket ID being reset with no need to do so
* Fix creating new things with a link share auth
* Fix dependencies
* Fix gosec in drone
* Fix link share creation & creating admin link shares without admin rights
* Fix moving tasks back into the empty (ID: 0) bucket
* Fix moving tasks in buckets
* Fix not moving its bucket when moving a task between lists
* Fix pagination count for task collection
* Fix parsing array style comparators by query param
* Fix reference to reverse proxies in docs
* Fix removing the last bucket
* Fix replace statements for tail
* Fix team rights not updating for namespace rights
* Fix tests after renaming json fields to snake_case
* Fix total label count when getting all labels (#477)
* Remove setting task bucket to 0
* Task Filter Fixes (#495)
#### Changed
* Change all json fields to snake_case
* Change totp secret datatype from varchar to text
* Update alpine Docker tag to v3.11 (#160)
* Update docs theme
* Update github.com/c2h5oh/datasize commit hash to 28bbd47 (#212)
* Update github.com/gordonklaus/ineffassign commit hash to 7953dde (#233)
* Update github.com/jgautheron/goconst commit hash to cda7ea3 (#228)
* Update github.com/shurcooL/httpfs commit hash to 8d4bc4b (#229)
* Update golang.org/x/crypto commit hash to 056763e (#222)
* Update golang.org/x/crypto commit hash to 06a226f (#504)
* Update golang.org/x/crypto commit hash to 0848c95 (#371)
* Update golang.org/x/crypto commit hash to 3c4aac8 (#419)
* Update golang.org/x/crypto commit hash to 44a6062 (#429)
* Update golang.org/x/crypto commit hash to 4b2356b (#475)
* Update golang.org/x/crypto commit hash to 4bdfaf4 (#438)
* Update golang.org/x/crypto commit hash to 729f1e8 (#458)
* Update golang.org/x/crypto commit hash to a76a400 (#411)
* Update golang.org/x/lint commit hash to 738671d (#223)
* Update module go-redis/redis to v6.15.7 (#234)
* Update module go-redis/redis to v6.15.7 (#290)
* Update module go-redis/redis to v7 (#277)
* Update module go-redis/redis to v7 (#309)
* Update module go-testfixtures/testfixtures/v3 to v3.1.2 (#457)
* Update module go-testfixtures/testfixtures/v3 to v3.2.0 (#505)
* Update module imdario/mergo to v0.3.9 (#238)
* Update module labstack/echo/v4 to v4.1.16 (#241)
* Update module lib/pq to v1.4.0 (#428)
* Update module lib/pq to v1.5.0 (#476)
* Update module lib/pq to v1.5.1 (#485)
* Update module lib/pq to v1.5.2 (#491)
* Update module olekukonko/tablewriter to v0.0.4 (#240)
* Update module prometheus/client_golang to v0.9.4 (#245)
* Update module prometheus/client_golang to v1
* Update module prometheus/client_golang to v1.6.0 (#463)
* Update module spf13/cobra to v0.0.7 (#271)
* Update module spf13/viper to v1.6.2 (#272)
* Update module spf13/viper to v1.6.3 (#291)
* Update module spf13/viper to v1.7.0 (#494)
* Update module stretchr/testify to v1.5.1 (#274)
* Update Renovate Configuration (#161)
* Update src.techknowlogick.com/xgo commit hash to bb0faa3 (#279)
* Update src.techknowlogick.com/xgo commit hash to c43d4c4 (#224)
* Update xorm redis cacher to use the xorm logger instead of a special seperate one
* Update xorm to v1 (#323)
## [0.12] - 2020-04-04
#### Added
* Add support for archiving lists and namespaces (#152)
* Colors for lists and namespaces (#155)
* Add build time to compile flags
* Add proxying gravatar requests for user avatars (#148)
* Add empty avatar provider (#149)
* expand relative path ~/.config/vikunja to $HOME/.config/vikunja **WINDOWS** (#147)
* Show lists as archived if their namespace is archived
#### Fixed
* Workaround for timezones on windows (#151)
* Fix getting one namespace
* Fix getting the authenticated user with caldav
* Fix searching for config in home directories
* Fix updating lists with an identifier
#### Changed
* Change release bucket
## [0.11] - 2020-03-01
### Added
* Add config options for cors handling (#124)
* Add config options for task attachments (#125)
* Add generate as a make dependency for make build
* Add logging for invalid model errors (#126)
* Add more logging to web handler methods
* Add postgres support (#135)
* Add rate limit by ip for non-authenticated routes (#127)
* Better efficency for loading teams (#128)
* Expand relative path ~/.config/vikunja to $HOME/.config/vikunja (#146)
* Task Comments (#138)
### Fixed
* Fix typo in docker-compose example (#140)
* Fix frontend url for wunderlist migration in docs
* Fix inserting task structure with related tasks (#142)
* Fix time zone settings not working in Docker
* Fix updating dates when marking a task as done (#145)
* Make sure the author is returned when creating a new comment
* Remove double user field
### Changed
* Explicitly disable wunderlist migration by default (#141)
* Migration Improvements (#122)
* Refactor User and DB handling (#123)
* Return iso dates for everything date related from the api (#130)
* Update copyright header
* Update theme
* Update xorm to use the new import path (#133)
* Use relative url in .gitmodules (#132)
## [0.10] - 2020-01-19
### Added
* Migration (#120)
* Endpoint to get tasks on a list (#108)
* Sort Order for tasks (#110)
* Add files volume to docker compose docs
* Add motd config option to docs
* Add option to disable registration (#117)
* Add task identifier (#115)
* Add tests for md5 generation (#111)
* Add user token renew (#113)
### Fixed
* Fix new tasks not getting a new task index (#116)
* Fix owner field being null for user shared namespaces (#119)
* Fix passing sort_by and order_by as query path arrays
* Fix sorting tasks by bool values
* Fix task collection tests
* Consistent copyright text in file headers (#112)
### Changed
* Task collection improvements (#109)
* Update copyright year (#118)
* Update docs with a traefik configuration
* Use redis INCRBY and DECRBY when updating metrics values (#121)
* Use utf8mb4 instead of plain utf8 (#114)
* Update docs theme
## [0.9] - 2019-11-24
### Added
* Task Attachments (#104)
* Task Relations (#103)
* Add endpoint to get a single task (#106)
* Add file volume to the docker image
* Added extra depth to logging to correctly show the functions calling the logger in logs
* Added more infos to a link share auth (#98)
* Added percent done to tasks (#102)
### 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 deleting an attachment would cause a nil panic
* Fixed building docs theme
* Fixed error when setting max file size on 32-Bit systems
* Fixed labels being displayed multiple times if they were associated with more than one task (#99)
* Fixed metrics on/off setting
* Fixed migration for task relations
* Fixed not getting all labels when retrieving a list with all tasks
* Fixed panic when using link share and metrics
* Fixed rate limit panic when authenticating with a link share auth token (#97)
* Fixed removing reminders
* Small link share fixes (#96)
### Changed
* Improve pagination (#105)
* Moved `teams_{namespace|list}_*` to `{namespace|list}_teams_*` for better consistency (#101)
* Refactored getting all lists for a namespace (#100)
* Refactored getting task IDs for labels
* Switched default logger to stdout instead of stderr
* update docs theme
### Misc
* Move from markdown lists to Vikunja for roadmap
## [0.8] - 2019-09-01
### Added
* Better Caldav support (#73)
* Added settings for max open/idle connections and max connection lifetime (#74)
* /info endpoint (#85)
* Added http endpoint to list all users on a list (#87)
* Rate limits (#91)
* Sharing of lists via public links (#94)
### Changed
* Reminders now use an extra table (#75)
* Use the username instead of a full user object when adding a user to a team or giving it rights (#76)
* Add the md5-hashed user email to user objects for use with gravatar (#78)
* Use the auth methods to get IDs to avoid unneeded casts
* Better config handling with constants (#83)
* Statically compile templates in the final binary (#84)
* Use longtext instead of varchar(1000) on description fields (#88)
* Logger refactoring (#90)
### Fixed
* Fixed `listID` not being returned in tasks
* Fixed tests (#72)
* Fixed metrics endpoint not working
* Fixed check if the user really exists before updating/deleting its rights (#77)
* Fixed duedate spelling issue (#79)
### Misc
* Integration tests (#71)
* Make sure the version works when building in drone
* Switched to another version of xgo
* Simplified the docker image (#80)
* Update echo (#82)
* Compress binaries after building them (#81)
* Simplify structure by having less files (#86)
* Limit the test pipeline to run only on pull requests (#89)
* GetUser now returns a pointer (#93)
* Refactor ListTask to Task (#92)
## [0.7] - 2019-04-05
### Added
* DB migrations (#67)
* More cli options for Vikunja (#66 #68)
* Use query params to sort tasks instead of url params (#61)
* More config paths (#55)
### Fixed
* Fixed Priority not updating when setting it to 0
* Fixed getting lists by namespace
* Fixed rights check (#70 #62)
* Fixed labels not being queried correctly on tasks
* Fixed bulk update label tasks
### Changed
* Hide a user's email address everywhere (#69)
* Refactored `canRead()` to get the list before checking rights #65
* Let rights methods return errors (#64 #63)
* Improved Swagger docs for label tasks
* Docs improvements (#58)
* Logging Handling (#57)
* Rights performance improvements (#54)
### Misc
* Releases also as Debian packages (#56)
## [0.6] - 2019-01-16
### Added
* Added prometheus endpoint to get metrics (#33)
* More unit tests (#34)
* Tests can now use config files (#36)
* Redoc for swagger ui (#39, #46)
* Start and end dates for tasks (#40)
* Get tasks between a date range (#41)
* Bulk edit for tasks (#42)
* More ci checks (#43)
* Task assignees (#44, #47)
* Task labels (#45, #48)
### Fixed
* Fixed path to get all tasks (echo bug)
* Explicitly get the peudonamespace with all shared lists (#32)
* Properly init tabels Redis
* unexpected EOF when using metrics (#35)
* Task sorting in lists (#36)
* Various user fixes (#38)
* Fixed a bug where updating a list would update it with the same values it had
### Changed
* Simplified list rights check (#50)
* Refactored some structs to not expose unneded values via json (#52)
### Misc
* 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/)).
## [0.5] - 2018-12-02
### Added
* Shared lists are now shown in a pseudonamespace with all other namespaces, has the ID -1
* Tasks can have multiple reminders
* Tasks can have subtasks. Subtasks are fully-fleged tasks, but not shown in the task list of a list.
* Tasks can have priorities
### Changed
* Validation not so verbose anymore
* [License](https://git.kolaente.de/vikunja/api/src/branch/master/LICENSE) is now GPLv3
* The crudhandler now has its [own repo](https://git.kolaente.de/vikunja/web) - you can use it in your own projects!
## [0.4] - 2018-11-16
#### Added
* Get all tasks for the authenticated user sorted by their due date
* CalDAV support
* Pagination for everything which returns an array
* Search all the things
* More validation for most of the structs
* Improved Swagger docs (available on `/api/v1/swagger`)
## [0.3] - 2018-11-02
### Added
* Password reset
* Email verification when registering
Misc bugfixes and improvements to the build process
## [0.2] - 2018-10-17
## [0.1] - 2018-09-20

View File

@ -1,45 +1,51 @@
###################################
#Build stage
FROM golang:1.11-alpine AS build-env
##############
# Build stage
FROM golang:1-alpine AS build-env
ARG VIKUNJA_VERSION
ENV TAGS "sqlite"
ENV GO111MODULE=on
ENV GOFLAGS=-mod=vendor
#Build deps
# Build deps
RUN apk --no-cache add build-base git
#Setup repo
# Setup repo
COPY . ${GOPATH}/src/code.vikunja.io/api
WORKDIR ${GOPATH}/src/code.vikunja.io/api
#Checkout version if set
# Checkout version if set
RUN if [ -n "${VIKUNJA_VERSION}" ]; then git checkout "${VIKUNJA_VERSION}"; fi \
&& make clean build
&& make clean generate build
FROM alpine:3.7
###################
# 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.12
LABEL maintainer="maintainers@vikunja.io"
EXPOSE 3456
RUN apk --no-cache add \
bash \
ca-certificates \
curl \
gettext \
linux-pam \
s6 \
sqlite \
su-exec \
tzdata
COPY docker /
COPY --from=build-env /go/src/code.vikunja.io/api/templates /app/vikunja/templates
COPY --from=build-env /go/src/code.vikunja.io/api/vikunja /app/vikunja/vikunja
WORKDIR /app/vikunja/
COPY --from=build-env /go/src/code.vikunja.io/api/vikunja .
ENV VIKUNJA_SERVICE_ROOTPATH=/app/vikunja/
ENTRYPOINT ["/bin/s6-svscan", "/etc/services.d"]
CMD []
# Dynamic permission changing stuff
ENV PUID 1000
ENV PGID 1000
RUN apk --no-cache add shadow && \
addgroup -g ${PGID} vikunja && \
adduser -s /bin/sh -D -G vikunja -u ${PUID} vikunja -h /app/vikunja -H && \
chown vikunja -R /app/vikunja
COPY run.sh /run.sh
# Fix time zone settings not working
RUN apk --no-cache add tzdata
# Files permissions
RUN mkdir /app/vikunja/files && \
chown -R vikunja /app/vikunja/files
VOLUME /app/vikunja/files
CMD ["/run.sh"]
EXPOSE 3456

View File

@ -1,309 +0,0 @@
# Featurecreep
This is the place where I write down ideas to work on at some point.
Sorry for some of them being in German, I'll tranlate them at some point.
## Feature Ideas
* [x] Priorities
* [x] Repeating tasks
* [x] Get all tasks which are due between two given dates
* [x] Subtasks
## Anderes
* [x] Refactor!!!! Delete everything not being used anymore, simplify.
* [x] Drone
* [x] Tests
* [x] Find a nme
* [x] Move packages to a better structure
* [x] Swagger UI
+ [x] Fix CORS
* [x] Use echo.NewHTTPError instead of c.JSON(Message{})
* [x] Better error messages when the model which is sent to the server is wrong
* [x] Better error handling to show useful error messages and status codes
* [x] Viper for config instead of ini
* [x] Docs for installing
* [x] Tests for rights managemnt
* [x] Rights checks:
* [x] Create lists
* [x] Edit lists
* [x] Add tasks
* [x] Edit tasks
* [x] The -1 namespace should also be accessible seperately
### Short Term
* [x] Cacher configurable
* [x] Should throw an error when an id < 1
* [x] /users should also return the rights
* [x] Extra endpoint /teams/members /list/users to update rights without needing to remove and re-add them
* [x] namespaces & listen update does not work, returns 500
* [x] Logging for all errors somewhere
* [x] Ne extra funktion für list exists machen, damit die nicht immer über GetListByID gehen, um sql-abfragen zu sparen
* [x] Rausfinden warum xorm teilweise beim einfügen IDs mit einfügen will -> Das schlägt dann wegen duplicate fehl
* [x] Bei den Structs "AfterLoad" raus, das verbraucht bei Gruppenabfragen zu viele SQL-Abfragen -> Die sollen einfach die entsprechenden Read()-Methoden verwenden (Krassestes bsp. ist GET /namespaces mit so ca 50 Abfragen)
* [x] General search endpoints
* [x] Validation der ankommenden structs, am besten mit https://github.com/go-validator/validator oder mit dem Ding von echo
* [x] Pagination
* Sollte in der Config definierbar sein, wie viel pro Seite angezeigt werden soll, die CRUD-Methoden übergeben dann ein "gibt mir die Seite sowieso" an die CRUDable-Funktionenen, die müssen das dann Auswerten. Geht leider nicht anders, wenn man erst 2342352 Einträge hohlt und die dann nachträglich auf 200 begrenzt ist das ne massive Ressourcenverschwendung.
* [x] Testen, ob man über die Routen methode von echo irgendwie ein swagger spec generieren könnte -> Andere Swagger library
* [x] CalDAV
* [x] Basics
* [x] Reminders
* [x] Discovery, stichwort PROPFIND
* [x] Wir brauchen noch ne gute idee, wie man die listen kriegt, auf die man nur so Zugriff hat (ohne namespace)
* Dazu am Besten nen pseudonamespace anlegen (id -1 oder so), der hat das dann alles
* [x] Testing mit locust: https://locust.io/
* [ ] Endpoint to get all users who have access to a list - regardless of via team, user share or via namespace
#### Userstuff
* [x] Userstuff aufräumen
-> Soweit es geht und Sinnvoll ist auf den neuen Handler umziehen
-> Login/Register/Password-reset geht natürlich nicht
-> Bleibt noch Profile abrufen und Einstellungen -> Macht also keinen Sinn das auf den neuen Handler umzuziehen
* [x] Email-Verifizierung beim Registrieren
* [x] Password Reset
* [ ] New field in user model which holds a url of an avatar image - for now just Gravatar, later more
* [ ] Settings
* [ ] Password update
* [ ] Email update
* [ ] Ob man über email oder Benutzernamen gefunden werden darf
### Bugfixes
* [x] Panic wenn mailer nicht erreichbar -> Als workaround mailer deaktivierbar machen, bzw keine mails verschicken
* [x] "unexpected EOF"
* [x] Beim Login & Password reset gibt die API zurück dass der Nutzer nicht existiert
* [x] Re-check rights checks to see if all information which is compared against is properly read from the db and not only based on user input
* [x] Lists
* [x] List users
* [x] List Teams
* [x] Labels
* [x] Tasks
* [x] Namespaces
* [x] Namespace users
* [x] Namespace teams
* [x] Teams
* [x] Team member handling
* [x] Also check `ReadOne()` for unnessecary database operations since the inital query is already done in `CanRead()`
* [x] Add a `User.AfterLoad()` which obfuscates the email address
* [x] Fix priority not updating to 0
### Docs
* [x] Readme
* [x] Auch noch nen "link" zum Featurecreep
* [x] ToC
* [x] Logo
* [x] How to build -> Docs
* [x] How to dev -> Docs
* [x] License
* [x] Contributing
* [x] Redocs
* [x] Swaggerdocs verbessern
* [x] Descriptions in structs
* [x] Maxlength specify etc. (see swaggo docs)
* [x] Rights
* [x] API
* [x] Anleitung zum Makefile
* [x] How to build from source
* [x] Struktur erklären
* [x] Deploy in die docs
* [x] Docker
* [x] Native (systemd + nginx/apache)
* [x] Backups
* [x] Docs aufsetzen
### Tasks
* [x] Start/Enddatum für Tasks
* [x] Timeline/Calendar view -> Dazu tasks die in einem Bestimmten Bereich due sind, macht dann das Frontend
* [x] Tasks innerhalb eines definierbarem Bereich, sollte aber trotzdem der server machen, so à la "Gib mir alles für diesen Monat"
* [x] Bulk-edit -> Transactions
* [x] Assignees
* [x] Check if something changed at all before running everything
* [x] Don't use `list.ReadOne()`, gets too much unnessecary shit
* [x] Wegen Performance auf eigene endpoints umziehen, wie labels
* [x] "One endpoint to rule them all" -> Array-addable
* [x] Labels
* [x] Check if something changed at all before running everything
* [x] Editable via task edit, like assignees
* [x] "One endpoint to rule them all" -> Array-addable
* [ ] Attachments
* [ ] Task-Templates innerhalb namespaces und Listen (-> Mehrere, die auswählbar sind)
* [ ] Ein Task muss von mehreren Assignees abgehakt werden bis er als done markiert wird
* [ ] Besseres Rechtesystem, damit man so fine-graded sachen machen kann wie "Der da darf aber nur Tasks hinzufügen, aber keine abhaken"
* [ ] Roles which enable or disable chaning certain fields of a task -> includes custm fields
* [ ] Custom fields: Templates at List > Namespace > Global level, overwriting each other
* [ ] Related tasks -> settable with a "kind" of relation like blocked, or just related or so
* [ ] Description should be longtext
* [ ] Pecent done - For now just a float, may later depend on how many sub tasks are done or so
### General features
* [x] Deps nach mod umziehen
* [x] Performance bei rechtchecks verbessern
* User & Teamright sollte sich für n rechte in einer Funktion testen lassen
* [ ] Endpoint um die Rechte mit Beschreibung und code zu kriegen
* [ ] "Smart Lists", Listen nach bestimmten Kriterien gefiltert -> speichern und im pseudonamespace
* [ ] "Performance-Statistik" -> Wie viele Tasks man in bestimmten Zeiträumen so geschafft hat etc
* [ ] IMAP-Integration -> Man schickt eine email an Vikunja und es macht daraus dann nen task -> Achtung missbrauchsmöglichkeiten
* [ ] In und Out webhooks, mit Templates vom Payload
* [ ] Reminders via mail
* [ ] Activity Feed, so à la "der und der hat das und das gemacht etc"
* [ ] Per list
* [ ] For the current user
* [ ] ~~Websockets~~ SSE https://github.com/kljensen/golang-html5-sse-example
* User authenticates (with jwt)
* When updating/creating/etc an event struct is sent to the broker
* The broker has a list of subscribed users
* It then checks who is allowed to the see the event it recieved and sends it
* [ ] Being able to define filters for notifications or turn them silent completely -> Probably frontend only
* [ ] mgl. zum Emailmaskieren haben (in den Nutzereinstellungen, wenn man seine Email nicht an alle Welt rausposaunen will)
* [ ] Mgl. zum Accountlöschen haben (so richtig krass mit emailverifiezierung und dass alle Privaten Listen gelöscht werden und man alle geteilten entweder wem übertragen muss oder auf privat stellen)
* [ ] /info endpoint, in dem dann zb die limits und version etc steht
* [ ] Deprecate /namespaces/{id}/lists in favour of namespace.ReadOne() <-- should also return the lists
* [ ] Bindata for templates
* [ ] `GetUserByID` and the likes should return pointers
* [ ] Colors for lists and namespaces -> Up to the frontend to implement these
* [ ] Some kind of milestones for tasks
* [ ] Create tasks from a text/markdown file (probably frontend only)
* [ ] Label-view: Get a bunch of tasks by label
* [ ] Debian package should have a service file
* [ ] Downloads should be served via nginx (with theme?), minio should only be used for pushing artifacts.
* [ ] User struct should have a field for the avatar url (-> gravatar md5 calculated by the backend)
* [ ] All `ReadAll` methods should return the number of items per page, the number of items on this page, the total pages and the items
-> Check if there's a way to do that efficently. Maybe only implementing it in the web handler.
* [ ] List stats to see how many tasks are done, how many are there in total, how many people have acces to a list etc
* [ ] Colors for tasks
* [ ] Better caldav support
* [x] VTODO
* [x] Fix organizer prop
* [x] Depricate the events thing for now
* [x] PROPFIND/OPTIONS : caldav discovery
* [x] Create new tasks
* [x] Save uid from the client
* [x] Update tasks
* [x] Marking as done
* [x] Fix OPTIONS Requests to the rest of the api being broken
* [x] Parse all props defined in rfc5545
* [x] COMPLETED -> Need to actually save the time the task was completed
* [x] Whenever a task ist created/updated, update the `updated` prop on the list so the etag changes and clients get notified
* [x] Fix not all tasks being displayed (My guess: Something about that f*cking etag)
* [x] Delete tasks
* [x] Last modified
* [x] Content Size
* [x] Modify the caldav lib as proposed in the pr
* [x] Improve login performance, each request taking > 1.5 sec is just too much, maybe just use the default value for hash iterations in the login/register function
* [x] Only show priority when we have one
* [x] Show a proper calendar title
* [x] Fix home principal propfind stuff
* [x] Docs
* [x] Setting to disable caldav completely
* [ ] Make it work with the app
* [ ] Cleanup the whole mess I made with the handlers and storage providers etc -> Probably a good idea to create a seperate storage provider etc for lists and tasks
* [ ] Tests
* [ ] Check if only needed things are queried from the db when accessing dav (for ex. no need to get all tasks when we act
### Refactor
* [x] ListTaskRights, sollte überall gleich funktionieren, gibt ja mittlerweile auch eine Methode um liste von nem Task aus zu kriegen oder so
* [x] Re-check all `{List|Namespace}{User|Team}` if really all parameters need to be exposed via json or are overwritten via param anyway.
* [x] Things like list/task order should use queries and not url params
* [x] Fix lint errors
* [x] Add settings for max open/idle connections and max connection lifetime
* [x] Reminders should use an extra table so we can make reverse lookups aka "give me all tasks with reminders in this period" which we'll need for things like email reminders notifications
* [ ] Teams and users should also have uuids (for users these can be the username)
* [ ] When giving a team or user access to a list/namespace, they should be reffered to by uuid, not numeric id
* [ ] Adding users to a team should also use uuid
* [ ] Check if the team/user really exist before updating them on lists/namespaces
* [ ] Check if the email is properly obfuscated everywhere -> alter GetUser() and add a new method GetUserWithEmail
### Linters
* [x] goconst
* [x] Staticcheck
* [x] gocyclo-check
* [ ] gosec-check -> waiting for mod
* [x] goconst-check
* [ ] golangci -> docker in drone, will probably make all other linters obsolete
### More server settings
* [x] Caldav disable/enable
* [ ] Assignees disable/enable
* [ ] List/Namespace limits
* [ ] Attachements disable/enable
* [ ] Attachements size
* [ ] Templates disable/enable
* [ ] Stats disable/enable
* [ ] Activity notifications disable/enable
* [ ] IMAP integration disable/enable
* [ ] Reminders via mail disable/enable
### Later
* [ ] Backgrounds for lists -> needs uploading and storing and so on
* [ ] Plugins
* [ ] Rename Namespaces to collections (or spaces?)
* [ ] Collections n-n (one list can be in multiple collections)?
* [ ] Rename lists to projects
* [ ] Per-User limits of lists/namespaces
* [ ] Admin-Interface to do stuff like settings and user management
* [ ] Enable/Disable users
* [ ] Better rights, fine-graded
* [ ] Enable/disable allowing user adding to lists/namespaces for specific lists or namespaces
* [ ] Admins should be able to see and mange all the boards
* [ ] Limit registration to users with a defined email domain
* [ ] Close the instance, either no registration or only one with defined email
* [ ] 2fa
* [ ] Custom fields for tasks
* [ ] Sorting lists by members, tasks, teams, last modified, etc
* [ ] "Favourite lists" -> A user can favourize boards which will then show up in a pseudonamespace
* [ ] Public lists
* [ ] Internal lists -> Only registered users can see the list
* [ ] Rights management for both public and internal lists
* [ ] Add new users via to a list which don't have an account yet, they'd get a link to sign up for vikunja.
* [ ] Respect registration email domain limits
* [ ] Export all data from Vikunja to json
* [ ] Watch a (n internal) list -> Will get notification for everything
* [ ] Archive a task instead of deleting
* [ ] Task dependencies
* [ ] Time tracking (possible plugin)
* [ ] IFTTT
* [ ] More sharing features (all of these with the already existing permissions)
* [ ] Invite users per mail
* [ ] Share a link with/without password
* [ ] Comments on tasks
* [ ] @mention users in tasks or comments to get them notified
* [ ] Summary of tasks to do in a configurable interval (every day/week or so)
* [ ] Disable/enable task fields in a list
* [ ] With inheritence from namespaces
* [ ] Custom statuses for tasks, configurable in the list settings
* [ ] With inheritence from namespaces
* [ ] better filters
* [ ] by lables
* [ ] Due dates
* [ ] Start/End dates
* [ ] Assignees
* [ ] Priorities
* [ ] Importer (maybe frontend only)
* [ ] Trello
* [ ] Wunderlist
* [ ] Zenkit
* [ ] Asana
* [ ] Microsoft Todo
* [ ] Nozbe
* [ ] Lanes
* [ ] Nirvana
* [ ] Any.do
* [ ] Good ol' Caldav (Tasks)
* [ ] ClickUp
* [ ] More auth providers
* [ ] LDAP/AD
* [ ] Kerberos
* [ ] SAML (what?)
* [ ] smtp
* [ ] OpenID

View File

@ -19,7 +19,7 @@ GOFMT ?= gofmt -s
GOFLAGS := -v -mod=vendor
EXTRA_GOFLAGS ?=
LDFLAGS := -X "code.vikunja.io/api/pkg/cmd.Version=$(shell git describe --tags --always --abbrev=10 | sed 's/-/+/' | sed 's/^v//' | sed 's/-g/-/')" -X "main.Tags=$(TAGS)"
LDFLAGS := -X "code.vikunja.io/api/pkg/version.Version=$(shell git describe --tags --always --abbrev=10 | sed 's/-/+/' | sed 's/^v//' | sed 's/-g/-/')" -X "main.Tags=$(TAGS)"
PACKAGES ?= $(filter-out code.vikunja.io/api/pkg/integrations,$(shell go list -mod=vendor ./... | grep -v /vendor/))
SOURCES ?= $(shell find . -name "*.go" -type f)
@ -64,12 +64,17 @@ clean:
.PHONY: test
test:
VIKUNJA_SERVICE_ROOTPATH=$(shell pwd) go test $(GOFLAGS) -cover -coverprofile cover.out $(PACKAGES)
# We run everything sequentially and not in parallel to prevent issues with real test databases
VIKUNJA_SERVICE_ROOTPATH=$(shell pwd) go test $(GOFLAGS) -p 1 -cover -coverprofile cover.out $(PACKAGES)
.PHONY: test-coverage
test-coverage: test
go tool cover -html=cover.out -o cover.html
.PHONY: integration-test
integration-test:
VIKUNJA_SERVICE_ROOTPATH=$(shell pwd) go test $(GOFLAGS) code.vikunja.io/api/pkg/integrations
# We run everything sequentially and not in parallel to prevent issues with real test databases
VIKUNJA_SERVICE_ROOTPATH=$(shell pwd) go test $(GOFLAGS) -p 1 code.vikunja.io/api/pkg/integrations
.PHONY: lint
lint:
@ -93,11 +98,19 @@ fmt-check:
fi;
.PHONY: build
build: $(EXECUTABLE)
build: generate $(EXECUTABLE)
.PHONY: generate
generate:
go generate code.vikunja.io/api/pkg/static
$(EXECUTABLE): $(SOURCES)
go build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)' -o $@
.PHONY: compress-build
compress-build:
upx -9 $(EXECUTABLE)
.PHONY: release
release: release-dirs release-windows release-linux release-darwin release-copy release-check release-os-package release-zip
@ -135,11 +148,14 @@ ifneq ($(DRONE_WORKSPACE),'')
mv /build/* $(DIST)/binaries
endif
# Compresses all releases made by make release-* but not mips* releases since upx can't handle these.
.PHONY: release-compress
release-compress:
$(foreach file,$(filter-out $(wildcard $(wildcard $(DIST)/binaries/$(EXECUTABLE)-*mips*)),$(wildcard $(DIST)/binaries/$(EXECUTABLE)-*)), upx -9 $(file);)
.PHONY: release-copy
release-copy:
$(foreach file,$(wildcard $(DIST)/binaries/$(EXECUTABLE)-*),cp $(file) $(DIST)/release/$(notdir $(file));)
mkdir $(DIST)/release/templates -p
cp templates/ $(DIST)/templates/ -R
.PHONY: release-check
release-check:
@ -147,7 +163,7 @@ release-check:
.PHONY: release-os-package
release-os-package:
$(foreach file,$(filter-out %.sha256,$(wildcard $(DIST)/release/$(EXECUTABLE)-*)),mkdir $(file)-full;mv $(file) $(file)-full/; mv $(file).sha256 $(file)-full/; cp config.yml.sample $(file)-full/config.yml; cp $(DIST)/release/templates $(file)-full/ -R; cp LICENSE $(file)-full/; )
$(foreach file,$(filter-out %.sha256,$(wildcard $(DIST)/release/$(EXECUTABLE)-*)),mkdir $(file)-full;mv $(file) $(file)-full/; mv $(file).sha256 $(file)-full/; cp config.yml.sample $(file)-full/config.yml; cp LICENSE $(file)-full/; )
.PHONY: release-zip
release-zip:
@ -156,7 +172,7 @@ release-zip:
# Builds a deb package using fpm from a previously created binary (using make build)
.PHONY: build-deb
build-deb:
fpm -s dir -t deb --url https://vikunja.io -n vikunja -v $(PKGVERSION) --license GPLv3 --directories /opt/vikunja --after-install ./build/after-install.sh --description 'Vikunja is an open-source todo application, written in Go. It lets you create lists,tasks and share them via teams or directly between users.' -m maintainers@vikunja.io ./$(BINLOCATION)=/opt/vikunja/vikunja ./templates=/opt/vikunja ./config.yml.sample=/etc/vikunja/config.yml;
fpm -s dir -t deb --url https://vikunja.io -n vikunja -v $(PKGVERSION) --license GPLv3 --directories /opt/vikunja --after-install ./build/after-install.sh --description 'Vikunja is an open-source todo application, written in Go. It lets you create lists,tasks and share them via teams or directly between users.' -m maintainers@vikunja.io ./$(BINLOCATION)=/opt/vikunja/vikunja ./config.yml.sample=/etc/vikunja/config.yml;
.PHONY: reprepro
reprepro:
@ -176,13 +192,12 @@ do-the-swag:
@hash swag > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
go install $(GOFLAGS) github.com/swaggo/swag/cmd/swag; \
fi
swag init -g pkg/routes/routes.go -s ./pkg/swagger;
swag init -g pkg/routes/routes.go -o ./pkg/swagger;
# Fix the generated swagger file, currently a workaround until swaggo can properly use go mod
sed -i '/"definitions": {/a "code.vikunja.io.web.HTTPError": {"type": "object","properties": {"code": {"type": "integer"},"message": {"type": "string"}}},' docs/docs.go;
sed -i 's/code.vikunja.io\/web.HTTPError/code.vikunja.io.web.HTTPError/g' docs/docs.go;
sed -i 's/package\ docs/package\ swagger/g' docs/docs.go;
sed -i 's/` + \\"`\\" + `/` + "`" + `/g' docs/docs.go;
mv ./docs/docs.go ./pkg/swagger/docs.go;
sed -i '/"definitions": {/a "code.vikunja.io.web.HTTPError": {"type": "object","properties": {"code": {"type": "integer"},"message": {"type": "string"}}},' pkg/swagger/docs.go;
sed -i 's/code.vikunja.io\/web.HTTPError/code.vikunja.io.web.HTTPError/g' pkg/swagger/docs.go;
sed -i 's/package\ docs/package\ swagger/g' pkg/swagger/docs.go;
sed -i 's/` + \\"`\\" + `/` + "`" + `/g' pkg/swagger/docs.go;
.PHONY: misspell-check
misspell-check:
@ -204,7 +219,7 @@ gocyclo-check:
go get -u github.com/fzipp/gocyclo; \
go install $(GOFLAGS) github.com/fzipp/gocyclo; \
fi
for S in $(GOFILES); do gocyclo -over 17 $$S || exit 1; done;
for S in $(GOFILES); do gocyclo -over 29 $$S || exit 1; done;
.PHONY: static-check
static-check:
@ -216,15 +231,17 @@ static-check:
.PHONY: gosec-check
gosec-check:
@hash ./bin/gosec > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
curl -sfL https://raw.githubusercontent.com/securego/gosec/master/install.sh | sh -s 1.2.0; \
@hash gosec > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
echo "Please manually install gosec by running"; \
echo "curl -sfL https://raw.githubusercontent.com/securego/gosec/master/install.sh | bash -s -- -b $GOPATH/bin v2.2.0"; \
exit 1; \
fi
for S in $(PACKAGES); do ./bin/gosec $$S || exit 1; done;
gosec ./...
.PHONY: goconst-check
goconst-check:
@hash goconst > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
go get -u github.com/jgautheron/goconst/cmd/goconst; \
go install $(GOFLAGS) github.com/jgautheron/goconst/cmd/goconst; \
fi
fi;
for S in $(PACKAGES); do goconst $$S || exit 1; done;

View File

@ -2,11 +2,10 @@
[![Build Status](https://drone1.kolaente.de/api/badges/vikunja/api/status.svg)](https://drone1.kolaente.de/vikunja/api)
[![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](LICENSE)
[![Download](https://img.shields.io/badge/download-v0.8-brightgreen.svg)](https://storage.kolaente.de/minio/vikunja/)
[![Download](https://img.shields.io/badge/download-v0.13.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/git.kolaente.de/vikunja/api)](https://goreportcard.com/report/git.kolaente.de/vikunja/api)
[![cover.run](https://cover.run/go/code.vikunja.io/api.svg?style=flat&tag=golang-1.10)](https://cover.run/go?tag=golang-1.10&repo=code.vikunja.io%2Fapi)
# Vikunja API
@ -26,8 +25,10 @@
* Reminder for tasks
* Namespaces: A "group" which bundels multiple lists
* Share lists and namespaces with teams and users with granular permissions
* Plenty of details for tasks
Try it on [try.vikunja.io](https://try.vikunja.io)!
See [the features page](https://vikunja.io/en/features/) on our website for a more exaustive list or
try it on [try.vikunja.io](https://try.vikunja.io)!
## Docs
@ -50,11 +51,12 @@ All docs can be found on [the vikunja home page](https://vikunja.io/docs/).
* [x] Get all your tasks for an interval (day/month/period)
* [x] Labels for tasks
* [x] Assign users to tasks
* [ ] Attachments on tasks
* [ ] More sharing features
* [x] Attachments on tasks
* [x] More sharing features
* [x] Share with individual users
* [ ] Share via a world-readable link with or without password, like Nextcloud
* [ ] Read-only websocket to notify multiple clients of updates when something was changed
* [x] Share via a world-readable link with or without password, like Nextcloud
* [x] Disable registration, making an instance "invite-only"
* [ ] SSE to notify multiple clients of updates when something was changed
* [ ] "Smart Lists" - Create lists based on filters
* [ ] IMAP-Integration - Send an email to Vikunja to create a new task
* [ ] Webhooks - Trigger other events when an action is done (like completing a task)
@ -63,9 +65,8 @@ All docs can be found on [the vikunja home page](https://vikunja.io/docs/).
* [ ] Bulk-edit multiple tasks at once
* [ ] Team-efforts - Requiring a task to be marked as done by multiple members until it's done
* [ ] Global limits for namespaces/lists/tasks
* [ ] Disable registration, making an instance "invite-only"
See [Featurecreep.md](Featurecreep.md) for even more! (mostly ideas, for now)
See [our roadmap](https://my.vikunja.cloud/share/QFyzYEmEYfSyQfTOmIRSwLUpkFjboaBqQCnaPmWd/auth) (hosted on Vikunja!) for even more!
* [ ] [Mobile apps](https://code.vikunja.io/app) (seperate repo) *In Progress*
* [ ] [Webapp](https://code.vikunja.io/frontend) (seperate repo) *In Progress*

View File

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

View File

@ -3,7 +3,7 @@ POST http://localhost:8080/api/v1/login
Content-Type: application/json
{
"username": "user6",
"username": "user3",
"password": "1234"
}

View File

@ -5,7 +5,7 @@ Authorization: Bearer {{auth_token}}
###
# Get one list
GET http://localhost:8080/api/v1/lists/1172
GET http://localhost:8080/api/v1/lists/3
Authorization: Bearer {{auth_token}}
###
@ -82,11 +82,11 @@ Authorization: Bearer {{auth_token}}
###
# Give a user access to that list
PUT http://localhost:8080/api/v1/lists/1172/users
PUT http://localhost:8080/api/v1/lists/3/users
Authorization: Bearer {{auth_token}}
Content-Type: application/json
{"user_id":1, "right":1}
{"userID":"user4", "right":1}
###
@ -169,3 +169,9 @@ Content-Type: application/json
}
###
# Get all users who have access to a list
GET http://localhost:8080/api/v1/lists/3/users
Authorization: Bearer {{auth_token}}
###

View File

@ -11,16 +11,32 @@ service:
# Vikunja will also look in this path for a config file, so you could provide only this variable to point to a folder
# with a config file which will then be used.
rootpath: <rootpath>
# The number of items which gets returned per page
pagecount: 50
# The max number of items which can be returned per page
maxitemsperpage: 50
# If set to true, enables a /metrics endpoint for prometheus to collect metrics about the system
# You'll need to use redis for this in order to enable common metrics over multiple nodes
enablemetrics: false
# Enable the caldav endpoint, see the docs for more details
enablecaldav: true
# Set the motd message, available from the /info endpoint
motd: ""
# Enable sharing of lists via a link
enablelinksharing: true
# Whether to let new users registering themselves or not
enableregistration: true
# Whether to enable task attachments or not
enabletaskattachments: true
# The time zone all timestamps are in
timezone: GMT
# Whether task comments should be enabled or not
enabletaskcomments: true
# Whether totp is enabled. In most cases you want to leave that enabled.
enabletotp: true
# If not empty, enables logging of crashes and unhandled errors in sentry.
sentrydsn: ''
database:
# Database type to use. Supported types are mysql and sqlite.
# Database type to use. Supported types are mysql, postgres and sqlite.
type: "sqlite"
# Database user which is used to connect to the database.
user: "vikunja"
@ -32,12 +48,15 @@ database:
database: "vikunja"
# When using sqlite, this is the path where to store the data
Path: "./vikunja.db"
# Sets the max open connections to the database. Only used when using mysql.
# Sets the max open connections to the database. Only used when using mysql and postgres.
maxopenconnections: 100
# Sets the maximum number of idle connections to the db.
maxidleconnections: 50
# The maximum lifetime of a single db connection in miliseconds.
maxconnectionlifetime: 10000
# Secure connection mode. Only used with postgres.
# (see https://pkg.go.dev/github.com/lib/pq?tab=doc#hdr-Connection_String_Parameters)
sslmode: disable
cache:
# If cache is enabled or not
@ -57,6 +76,17 @@ redis:
# 0 means default database
db: 0
cors:
# Whether to enable or disable cors headers.
# Note: If you want to put the frontend and the api on seperate domains or ports, you will need to enable this.
# Otherwise the frontend won't be able to make requests to the api through the browser.
enable: true
# A list of origins which may access the api.
origins:
- "*"
# How long (in seconds) the results of a preflight request can be cached.
maxage: 0
mailer:
# Whether to enable the mailer or not. If it is disabled, all users are enabled right away and password reset is not possible.
enabled: false
@ -82,13 +112,92 @@ log:
path: <rootpath>logs
# Whether to show any logging at all or none
enabled: true
# Where the error log should go. Possible values are stdout, stderr, file or off to disable error logging.
errors: "stdout"
# Where the normal log should go. Possible values are stdout, stderr, file or off to disable standard logging.
standard: "stdout"
# Change the log level. Possible values (case-insensitive) are CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG.
level: "INFO"
# Whether or not to log database queries. Useful for debugging. Possible values are stdout, stderr, file or off to disable database logging.
database: "off"
# The log level for database log messages. Possible values (case-insensitive) are CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG.
databaselevel: "WARNING"
# Whether to log http requests or not. Possible values are stdout, stderr, file or off to disable http logging.
http: "stdout"
# Echo has its own logging which usually is unnessecary, which is why it is disabled by default. Possible values are stdout, stderr, file or off to disable standard logging.
echo: "off"
echo: "off"
ratelimit:
# whether or not to enable the rate limit
enabled: false
# The kind on which rates are based. Can be either "user" for a rate limit per user or "ip" for an ip-based rate limit.
kind: user
# The time period in seconds for the limit
period: 60
# The max number of requests a user is allowed to do in the configured time period
limit: 100
# The store where the limit counter for each user is stored. Possible values are "memory" or "redis"
store: memory
files:
# The path where files are stored
basepath: ./files # relative to the binary
# The maximum size of a file, as a human-readable string.
# Warning: The max size is limited 2^64-1 bytes due to the underlying datatype
maxsize: 20MB
migration:
# These are the settings for the wunderlist migrator
wunderlist:
# Wheter to enable the wunderlist migrator or not
enable: false
# The client id, required for making requests to the wunderlist api
# You need to register your vikunja instance at https://developer.wunderlist.com/apps/new to get this
clientid:
# The client secret, also required for making requests to the wunderlist api
clientsecret:
# The url where clients are redirected after they authorized Vikunja to access their wunderlist stuff.
# This needs to match the url you entered when registering your Vikunja instance at wunderlist.
# This is usually the frontend url where the frontend then makes a request to /migration/wunderlist/migrate
# with the code obtained from the wunderlist api.
# Note that the vikunja frontend expects this to be /migrate/wunderlist
redirecturl:
todoist:
# Wheter to enable the todoist migrator or not
enable: false
# The client id, required for making requests to the wunderlist api
# You need to register your vikunja instance at https://developer.todoist.com/appconsole.html to get this
clientid:
# The client secret, also required for making requests to the todoist api
clientsecret:
# The url where clients are redirected after they authorized Vikunja to access their todoist items.
# This needs to match the url you entered when registering your Vikunja instance at todoist.
# This is usually the frontend url where the frontend then makes a request to /migration/todoist/migrate
# with the code obtained from the todoist api.
# Note that the vikunja frontend expects this to be /migrate/todoist
redirecturl:
avatar:
# Switch between avatar providers. Possible values are gravatar and default.
# gravatar will fetch the avatar based on the user email.
# default will return a default avatar for every request.
provider: gravatar
# When using gravatar, this is the duration in seconds until a cached gravatar user avatar expires
gravatarexpiration: 3600
backgrounds:
# Whether to enable backgrounds for lists at all.
enabled: false
providers:
upload:
# Whethere to enable uploaded list backgrounds
enabled: false
unsplash:
# Whether to enable setting backgrounds from unsplash as list backgrounds
enabled: false
# You need to create an application for your installation at https://unsplash.com/oauth/applications/new
# and set the access token below.
accesstoken:
# The unsplash application id is only used for pingback and required as per their api guidelines.
# You can find the Application ID in the dashboard for your API application. It should be a numeric ID.
# It will only show in the UI if your application has been approved for Enterprise usage, therefore if
# youre in Demo mode, you can also find the ID in the URL at the end: https://unsplash.com/oauth/applications/:application_id
applicationid:

View File

@ -1,2 +0,0 @@
#!/bin/sh
/bin/true

View File

@ -1,2 +0,0 @@
#!/bin/sh
/bin/true

View File

@ -1,2 +0,0 @@
#!/bin/execlineb -P
/app/vikunja/vikunja

View File

@ -16,9 +16,9 @@ params:
description: The to-do app to organize your life
author: The Vikunja Authors
website: https://vikunja.io
fanthomEnabled: false
fathomUrl: fathom.kolaente.de
fathomSiteID: RYKSD
plausibleEnabled: true
plausibleDomain: vikunja.io
plausibleURL: https://analytics.kolaente.de
menu:
page:
@ -37,3 +37,6 @@ menu:
- name: Code
url: https://code.vikunja.io/
weight: 50
- name: Community
url: https://community.vikunja.io/
weight: 60

View File

@ -59,3 +59,10 @@ which will build a vikunja binary into the working directory. Writing test cases
is highly encouraged and helps developers sleep at night.
Thats it! You are ready to hack on Vikunja. Test changes, push them to the repository, and open a pull request.
## Static assets
Each Vikunja release contains all static assets directly compiled into the binary.
To prevent this during development, use the `dev` tag when developing.
See the [make docs](make.md#statically-compile-all-templates-into-the-binary) about how to compile with static assets for a release.

View File

@ -22,6 +22,7 @@ These tasks are automatically run in our CI every time someone pushes to master
* `make ineffassign-check`
* `make misspell-check`
* `make goconst-check`
* `make generate`
* `make build`
### clean
@ -64,10 +65,27 @@ make build
Builds a `vikunja`-binary in the root directory of the repo for the platform it is run on.
### Statically compile all templates into the binary
{{< highlight bash >}}
make generate
{{< /highlight >}}
This generates static code with all templates, meaning no template need to be referenced at runtime.
### Compress the built binary
{{< highlight bash >}}
make compress-build
{{< /highlight >}}
Go binaries are very big.
To make the vikunja binary smaller, we can compress it using [upx](https://upx.github.io/).
### Build Releases
{{< highlight bash >}}
make build
make release
{{< /highlight >}}
Builds binaries for all platforms and zips them with a copy of the `templates/` folder.
@ -75,7 +93,7 @@ All built zip files are stored into `dist/zips/`. Binaries are stored in `dist/b
binaries bundled with `templates` are stored in `dist/releases/`.
All cross-platform binaries built using this series of commands are built with the help of
[xgo](https://github.com/karalabe/xgo). The make command will automatically install the
[xgo](https://github.com/techknowlogick/xgo). The make command will automatically install the
binary to be able to use it.
`make release` is actually just a shortcut to execute `make release-dirs release-windows release-linux release-darwin release-copy release-check release-os-package release-zip`.
@ -85,6 +103,7 @@ binary to be able to use it.
* `release-copy` bundles binaries with a copy of `templates/` to then be zipped
* `release-check` creates sha256 checksums for each binary which will be included in the zip file
* `release-os-package` bundles a binary with a copy of the `templates/` folder, the `sha256` checksum file, a sample `config.yml` and a copy of the license in a folder for each architecture
* `release-compress` compresses all build binaries, see `compress-build`
* `release-zip` makes a zip file for the files created by `release-os-package`
### Build debian packages

View File

@ -0,0 +1,96 @@
---
date: "2020-01-19:16:00+02:00"
title: "Migrations"
draft: false
type: "doc"
menu:
sidebar:
parent: "development"
---
# Writing a migrator for Vikunja
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.
### Structure
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
The migrator interface is defined as follows:
```go
// Migrator is the basic migrator interface which is shared among all migrators
type Migrator interface {
// 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
// AuthURL returns a url for clients to authenticate against.
// The use case for this are Oauth flows, where the server token should remain hidden and not
// known to the frontend.
AuthURL() string
// 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
}
```
### Defining http routes
Once your migrator implements the migration interface, it becomes possible to use the helper http handlers.
Their usage is very similar to the [general web handler](https://kolaente.dev/vikunja/web#user-content-defining-routes-using-the-standard-web-handler):
The `RegisterRoutes(m)` method registers all routes with the scheme `/[MigratorName]/(auth|migrate|status)` for the
authUrl, Status and Migrate methods.
```go
// This is an example for the Wunderlist migrator
if config.MigrationWunderlistEnable.GetBool() {
wunderlistMigrationHandler := &migrationHandler.MigrationWeb{
MigrationStruct: func() migration.Migrator {
return &wunderlist.Migration{}
},
}
wunderlistMigrationHandler.RegisterRoutes(m)
}
```
You should also document the routes with [swagger annotations]({{< ref "../practical-instructions/swagger-docs.md" >}}).
### Insertion helper method
There is a method available in the `migration` package which takes a fully nested Vikunja structure and creates it with all relations.
This means you start by adding a 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`.
Then call the method like so:
```go
fullVikunjaHierachie, err := convertWunderlistToVikunja(wContent)
if err != nil {
return
}
err = migration.InsertFromStructure(fullVikunjaHierachie, user)
```
### Configuration
You should add at least an option to enable or disable the migration.
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.
#### 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`.

View File

@ -16,17 +16,29 @@ In general, this api repo has the following structure:
* `docs`
* `pkg`
* `caldav`
* `cmd`
* `config`
* `db`
* `fixtures`
* `files`
* `integration`
* `log`
* `mail`
* `metrics`
* `migration`
* `models`
* `modules`
* `migration`
* `handler`
* `wunderlist`
* `red`
* `routes`
* `api/v1`
* `static`
* `swagger`
* `user`
* `utils`
* `version`
* `REST-Tests`
* `templates`
* `vendor`
@ -66,6 +78,21 @@ how to interpret which env variables for config etc.
If you want to add a new config parameter, you should add default value in this package.
### db
This package contains the db connection handling and db fixtures for testing.
Each other package gets its db connection object from this package.
### files
This package is responsible for all file-related things.
This means it handles saving and retrieving files from the db and the underlying file system.
### integration
All integration tests live here.
See [integration tests]({{< ref "test.md" >}}#integration-tests) for more details.
### log
Similar to `config`, this will set up the logging, based on differen logging backends.
@ -85,7 +112,7 @@ To learn how it works and how to add new metrics, take a look at [how metrics wo
This package handles all migrations.
All migrations are stored and executed here.
To learn more, take a look at the [migrations docs]({{< ref "../development/migrations.md">}}).
To learn more, take a look at the [migrations docs]({{< ref "../development/db-migrations.md">}}).
### models
@ -97,6 +124,12 @@ Because this package is pretty huge, there are several documents and how-to's ab
* [Adding a feature]({{< ref "../practical-instructions/feature.md">}})
* [Making calls to the database]({{< ref "../practical-instructions/database.md">}})
### modules
#### migration
See [writing a migrator]({{< ref "migration.md" >}}).
### red (redis)
This package initializes a connection to a redis server.
@ -117,11 +150,19 @@ To add a new route, see [adding a new route]({{< ref "../practical-instructions/
This is where all http-handler functions for the api are stored.
Every handler function which does not use the standard web handler should live here.
### static
All static files generated by `make generate` live here.
### swagger
This is where the [generated]({{< ref "make.md#generate-swagger-definitions-from-code-comments">}} [api docs]({{< ref "../usage/api.md">}}) live.
You usually don't need to touch this package.
### user
All user-related things like registration etc. live in this package.
### utils
A small package, containing some helper functions:
@ -131,6 +172,12 @@ A small package, containing some helper functions:
See their function definitions for instructions on how to use them.
### version
The single purpouse of this package is to hold the current vikunja version which gets overridden through build flags
each time `make release` or `make build` is run.
It is a seperate package to avoid import cycles with other packages.
## REST-Tests
Holds all kinds of test files to directly test the api from inside of [jetbrains ide's](https://www.jetbrains.com/help/idea/http-client-in-product-code-editor.html).

View File

@ -42,3 +42,29 @@ The integration tests use the same config and fixtures as the unit tests and the
see at the beginning of this document.
To run integration tests, use `make integration-test`.
# Initializing db fixtures when writing tests
All db fixtures for all tests live in the `pkg/db/fixtures/` folder as yaml files.
Each file has the same name as the table the fixtures are for.
You should put new fixtures in this folder.
When initializing db fixtures, you are responsible for defining which tables your package needs in your test init function.
Usually, this is done as follows (this code snippet is taken from the `user` package):
```go
err = db.InitTestFixtures("users")
if err != nil {
log.Fatal(err)
}
```
In your actual tests, you then load the fixtures into the in-memory db like so:
```go
db.LoadAndAssertFixtures(t)
```
This will load all fixtures you defined in your test init method.
You should always use this method to load fixtures, the only exception is when your package tests require extra test
fixtures other than db fixtures (like files).

View File

@ -27,7 +27,7 @@ You can feed this function directly into xorm's `Limit`-Function like so:
{{< highlight golang >}}
lists := []List{}
err := x.Limit(getLimitFromPageIndex(pageIndex)).Find(&lists)
err := x.Limit(getLimitFromPageIndex(pageIndex, itemsPerPage)).Find(&lists)
{{< /highlight >}}
// TODO: Add a full example from start to finish, like a tutorial on how to create a new endpoint?

View File

@ -29,6 +29,24 @@ To restore it, simply pipe it back into the `mysql` command:
mysql -u <user> -p -h <db-host> <database> < vkunja-backup.sql
{{< /highlight >}}
## PostgreSQL
To create a backup from PostgreSQL use the `pg_dump` command:
{{< highlight bash >}}
pg_dump -U <user> -h <db-host> <database> > vikunja-backup.sql
{{< /highlight >}}
You might be prompted for the password of the database user.
To restore it, simply pipe it back into the `psql` command:
{{< highlight bash >}}
psql -U <user> -h <db-host> <database> < vikunja-backup.sql
{{< /highlight >}}
For more information, please visit the [relevant PostgreSQL documentation](https://www.postgresql.org/docs/12/backup-dump.html).
## SQLite
To backup sqllite databases, it is enough to copy the database elsewhere.

View File

@ -18,6 +18,10 @@ All libraries are bundeled inside the repo in the `vendor/` folder, so all it bo
3. Clone the repo with `git clone https://code.vikunja.io/api`
3. Run `make build` in the source of this repo. This will build a binary in the root of the repo which will be able to run on your system.
*Note:* Static ressources such as email templates are built into the binary.
For these to work, you may need to run `make generate` before building the vikunja binary.
When builing entirely with `make`, you dont need to do this, `make generate` will be run automatically when running `make build`.
# Build for different architectures
To build for other platforms and architectures than the one you're currently on, simply run `make release` or `make release-{linux|windows|darwin}`.

View File

@ -54,16 +54,32 @@ service:
# Vikunja will also look in this path for a config file, so you could provide only this variable to point to a folder
# with a config file which will then be used.
rootpath: <the path of the executable>
# The number of items which gets returned per page
pagecount: 50
# The max number of items which can be returned per page
maxitemsperpage: 50
# If set to true, enables a /metrics endpoint for prometheus to collect metrics about the system
# You'll need to use redis for this in order to enable common metrics over multiple nodes
enablemetrics: false
# Enable the caldav endpoint, see the docs for more details
enablecaldav: true
# Set the motd message, available from the /info endpoint
motd: ""
# Enable sharing of lists via a link
enablelinksharing: true
# Whether to let new users registering themselves or not
enableregistration: true
# Whether to enable task attachments or not
enabletaskattachments: true
# The time zone all timestamps are in
timezone: GMT
# Whether task comments should be enabled or not
enabletaskcomments: true
# Whether totp is enabled. In most cases you want to leave that enabled.
enabletotp: true
# If not empty, enables logging of crashes and unhandled errors in sentry.
sentrydsn: ''
database:
# Database type to use. Supported types are mysql and sqlite.
# Database type to use. Supported types are mysql, postgres and sqlite.
type: "sqlite"
# Database user which is used to connect to the database.
user: "vikunja"
@ -75,12 +91,15 @@ database:
database: "vikunja"
# When using sqlite, this is the path where to store the data
Path: "./vikunja.db"
# Sets the max open connections to the database. Only used when using mysql.
# Sets the max open connections to the database. Only used when using mysql and postgres.
maxopenconnections: 100
# Sets the maximum number of idle connections to the db.
maxidleconnections: 50
# The maximum lifetime of a single db connection in miliseconds.
maxconnectionlifetime: 10000
# Secure connection mode. Only used with postgres.
# (see https://pkg.go.dev/github.com/lib/pq?tab=doc#hdr-Connection_String_Parameters)
sslmode: disable
cache:
# If cache is enabled or not
@ -100,6 +119,17 @@ redis:
# 0 means default database
db: 0
cors:
# Whether to enable or disable cors headers.
# Note: If you want to put the frontend and the api on seperate domains or ports, you will need to enable this.
# Otherwise the frontend won't be able to make requests to the api through the browser.
enable: true
# A list of origins which may access the api.
origins:
- *
# How long (in seconds) the results of a preflight request can be cached.
maxage: 0
mailer:
# Whether to enable the mailer or not. If it is disabled, all users are enabled right away and password reset is not possible.
enabled: false
@ -125,14 +155,93 @@ log:
path: <rootpath>logs
# Whether to show any logging at all or none
enabled: true
# Where the error log should go. Possible values are stdout, stderr, file or off to disable error logging.
errors: "stdout"
# Where the normal log should go. Possible values are stdout, stderr, file or off to disable standard logging.
standard: "stdout"
# Change the log level. Possible values (case-insensitive) are CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG.
level: "INFO"
# Whether or not to log database queries. Useful for debugging. Possible values are stdout, stderr, file or off to disable database logging.
database: "off"
# The log level for database log messages. Possible values (case-insensitive) are CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG.
databaselevel: "WARNING"
# Whether to log http requests or not. Possible values are stdout, stderr, file or off to disable http logging.
http: "stdout"
# Echo has its own logging which usually is unnessecary, which is why it is disabled by default. Possible values are stdout, stderr, file or off to disable standard logging.
echo: "off"
ratelimit:
# whether or not to enable the rate limit
enabled: false
# The kind on which rates are based. Can be either "user" for a rate limit per user or "ip" for an ip-based rate limit.
kind: user
# The time period in seconds for the limit
period: 60
# The max number of requests a user is allowed to do in the configured time period
limit: 100
# The store where the limit counter for each user is stored. Possible values are "memory" or "redis"
store: memory
files:
# The path where files are stored
basepath: ./files # relative to the binary
# The maximum size of a file, as a human-readable string.
# Warning: The max size is limited 2^64-1 bytes due to the underlying datatype
maxsize: 20MB
migration:
# These are the settings for the wunderlist migrator
wunderlist:
# Wheter to enable the wunderlist migrator or not
enable: false
# The client id, required for making requests to the wunderlist api
# You need to register your vikunja instance at https://developer.wunderlist.com/apps/new to get this
clientid:
# The client secret, also required for making requests to the wunderlist api
clientsecret:
# The url where clients are redirected after they authorized Vikunja to access their wunderlist stuff.
# This needs to match the url you entered when registering your Vikunja instance at wunderlist.
# This is usually the frontend url where the frontend then makes a request to /migration/wunderlist/migrate
# with the code obtained from the wunderlist api.
# Note that the vikunja frontend expects this to be /migrate/wunderlist
redirecturl:
todoist:
# Wheter to enable the todoist migrator or not
enable: false
# The client id, required for making requests to the wunderlist api
# You need to register your vikunja instance at https://developer.todoist.com/appconsole.html to get this
clientid:
# The client secret, also required for making requests to the todoist api
clientsecret:
# The url where clients are redirected after they authorized Vikunja to access their todoist items.
# This needs to match the url you entered when registering your Vikunja instance at todoist.
# This is usually the frontend url where the frontend then makes a request to /migration/todoist/migrate
# with the code obtained from the todoist api.
# Note that the vikunja frontend expects this to be /migrate/todoist
redirecturl:
avatar:
# Switch between avatar providers. Possible values are gravatar and default.
# gravatar will fetch the avatar based on the user email.
# default will return a default avatar for every request.
provider: gravatar
# When using gravatar, this is the duration in seconds until a cached gravatar user avatar expires
gravatarexpiration: 3600
backgrounds:
# Whether to enable backgrounds for lists at all.
enabled: false
providers:
upload:
# Whethere to enable uploaded list backgrounds
enabled: false
unsplash:
# Whether to enable setting backgrounds from unsplash as list backgrounds
enabled: false
# You need to create an application for your installation at https://unsplash.com/oauth/applications/new
# and set the access token below.
accesstoken:
# The unsplash application id is only used for pingback and required as per their api guidelines.
# You can find the Application ID in the dashboard for your API application. It should be a numeric ID.
# It will only show in the UI if your application has been approved for Enterprise usage, therefore if
# youre in Demo mode, you can also find the ID in the URL at the end: https://unsplash.com/oauth/applications/:application_id
applicationid:
{{< /highlight >}}

View File

@ -0,0 +1,221 @@
---
date: "2020-05-24:00:00+02:00"
title: "Docker Walkthrough"
draft: false
type: "doc"
menu:
sidebar:
parent: "setup"
---
# Setup with docker from start to finish
This tutorial assumes basic knowledge of docker.
It is aimed at beginners and should get you up and running quickly.
We'll use [docker compose](https://docs.docker.com/compose/) to make handling the bunch of containers easier.
> If you have any issues setting up vikunja, please don't hesitate to reach out to us via [matrix](https://riot.im/app/#/room/!dCRiCiLaCCFVNlDnYs:matrix.org?via=matrix.org), the [community forum](https://community.vikunja.io/) or even [email](mailto:hello@vikunja.io).
## Preparations (optional)
Create a directory for the project where all data and the compose file will live in.
## Create all necessary files
Create a `docker-compose.yml` file with the following contents in your directory:
{{< highlight yaml >}}
version: '3'
services:
db:
image: mariadb:10
environment:
MYSQL_ROOT_PASSWORD: supersecret
MYSQL_DATABASE: vikunja
volumes:
- ./db:/var/lib/mysql
restart: unless-stopped
api:
image: vikunja/api
environment:
VIKUNJA_DATABASE_HOST: db
VIKUNJA_DATABASE_PASSWORD: supersecret
VIKUNJA_DATABASE_TYPE: mysql
VIKUNJA_DATABASE_USER: root
VIKUNJA_DATABASE_DATABASE: vikunja
volumes:
- ./files:/app/vikunja/files
depends_on:
- db
restart: unless-stopped
frontend:
image: vikunja/frontend
restart: unless-stopped
proxy:
image: nginx
ports:
- 80:80
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
- api
- frontend
restart: unless-stopped
{{< /highlight >}}
This defines four services, each with their own container:
* An api service which runs the vikunja api. Most of the core logic lives here.
* The frontend which will make vikunja actually usable for most people.
* A database container which will store all lists, tasks, etc. We're using mariadb here, but you're free to use mysql or postgres if you want.
* A proxy service which makes the frontend and api available on the same port, redirecting all requests to `/api` to the api container.
If you already have a proxy on your host, you may want to check out the [reverse proxy examples]() to use that.
By default, it uses port 80 on the host.
To change to something different, you'll need to change the `ports` section in the service definition.
The number before the colon is the host port - This is where you can reach vikunja from the outside once all is up and running.
For the proxy service we'll need another bit of configuration.
Create an `nginx.conf` in your directory (next to the `docker-compose.yml` file) and add the following contents to it:
{{< highlight conf >}}
server {
listen 80;
location / {
proxy_pass http://frontend:80;
}
location /api/ {
proxy_pass http://api:3456;
}
}
{{< /highlight >}}
This is a simple proxy configuration which will forward all requests to `/api/` to the api container and everything else to the frontend.
**Note:** Even if you want to make your installation available under a different port, you don't need to change anything in this configuration.
## Run it
Run `sudo docker-compose up` in your directory and take a look at the output you get.
When first started, Vikunja will set up the database and run all migrations etc.
Once it is ready, you should see a message like this one in your console:
```
api_1 | 2020-05-24T11:15:37.560386009Z: INFO ▶ cmd/func1 025 Vikunja version 0.13.1+19-e9bc3246ce, built at Sun, 24 May 2020 11:10:36 +0000
api_1 | ⇨ http server started on [::]:3456
```
This indicates all setup has been successful.
If you get any errors, see below:
### Troubleshooting
Vikunja might not run on the first try.
There are a few potential issues that could be causing this.
#### No connection to the database
Indicated by an error message like this one from the api container:
```
2020/05/23 15:37:59 Config File "config" Not Found in "[/app/vikunja /etc/vikunja /app/vikunja/.config/vikunja]"
2020/05/23 15:37:59 Using default config.
2020-05-23T15:37:59.974435725Z: CRITICAL ▶ migration/Migrate 002 Migration failed: dial tcp 172.19.0.2:3306: connect: connection refused
```
Especially when using mysql, this can happen on first start, because the mysql database container will take a few seconds to start.
Vikunja does not know the container is not ready, therefore it will just try to connect to the db, fail since it is not ready and exit.
If you're using the docker compose example from above, you may notice the `restart: unless-stopped` option at the api service.
This tells docker to restart the api container if it exits, unless you explicitly stop it.
Therefore, it should "magically fix itself" by automatically restarting the container.
After a few seconds (or minutes) you should see a log message like this one from the mariadb container:
```
2020-05-24 11:42:15 0 [Note] mysqld: ready for connections.
Version: '10.4.12-MariaDB-1:10.4.12+maria~bionic' socket: '/var/run/mysqld/mysqld.sock' port: 3306 mariadb.org binary distribution
```
The next restart of Vikunja should be successful.
If not, there might be a different error or a bug with Vikunja, please reach out to us in that case.
(If you have an idea about how we could improve this, we'd like to hear it!)
#### "Not a directory"
If you get an error like this one:
```
ERROR: for vikunja_proxy_1 Cannot start service proxy: OCI runtime create failed: container_linux.go:349: starting container process caused "process_linux.go:449: container init caused \"rootfs_linux.go:58: mounting \\\"vikunja/nginx.conf\\\" to rootfs \\\"/var/lib/docker/overlay2/9c8b8f9419c29dad0d1233fbb0a3c36cf403dabd7a55d6f0a47b0c1dd6029994/merged\\\" at \\\"/var/lib/docker/overlay2/9c8b8f9419c29dad0d1233fbb0a3c36cf403dabd7a55d6f0a47b0c1dd6029994/merged/etc/nginx/conf.d/default.conf\\\" caused \\\"not a directory\\\"\"": unknown: Are you trying to mount a directory onto a file (or vice-versa)? Check if the specified host path exists and is the expected type
```
this means docker tried to mount a directory from the host to a file in the container.
This can happen if you did not create the `nginx.conf` file.
Because there is a volume mount for it in the `docker-compose.yml`, Docker will create a folder because non exists, assuming you want to mount a folder into the container.
To fix this, create the file and restart the containers again.
#### Migration failed: commands out of sync
If you get an error like this one:
```
2020/05/23 15:53:38 Config File "config" Not Found in "[/app/vikunja /etc/vikunja /app/vikunja/.config/vikunja]"
2020/05/23 15:53:38 Using default config.
2020-05-23T15:53:38.762747276Z: CRITICAL ▶ migration/Migrate 002 Migration failed: commands out of sync. Did you run multiple statements at once?
```
This is a mysql issue.
Currently, we don't have a better solution than to completely wipe the database files and start over.
To do this, first stop everything by running `sudo docker-compose down`, then remove the `db/` folder in your current folder with `sudo rm -rf db` and start the whole stack again with `sudo docker-compose up -d`.
## Try it
Head over to `http://<host-ip or url>/api/v1/info` in a browser.
You should see something like this:
{{< highlight json >}}
{
"version": "0.13.1+19-e9bc3246ce",
"frontend_url": "http://localhost:8080/",
"motd": "test",
"link_sharing_enabled": true,
"max_file_size": "20MB",
"registration_enabled": true,
"available_migrators": [
"wunderlist",
"todoist"
],
"task_attachments_enabled": true
}
{{< /highlight >}}
This shows you can reach the api through the api proxy.
Now head over to `http://<host-ip or url>/` which should show the login mask.
## Make it persistent
Currently, Vikunja runs in foreground in your terminal.
For a real-world scenario this is not the best way.
Back in your terminal, stop the stack by pressing `CTRL-C` on your keyboard.
Then run `sudo docker-compose up -d` in your again.
The `-d` flag at the end of the command will tell docker to run the containers in the background.
If you need to check the logs after that, you can run `sudo docker-compose logs`.
Vikunja does not have any default users, you'll need to register and account.
After that, you can use it.
## Tear it all down
If you want to completely stop all containers run `sudo docker-compose down` in your terminal.
## Improve this guide
We'll happily accept suggestions and improvements for this guide.
Please [reach out to us](https://vikunja.io/contact/) if you have any.

View File

@ -11,7 +11,154 @@ menu:
# Full docker example
This docker compose configuration will run Vikunja with backend and frontend with a mariadb as database.
It uses an nginx container to proxy backend and frontend into a single port.
It uses an nginx container or traefik on the host to proxy backend and frontend into a single port.
For all available configuration options, see [configuration]({{< ref "config.md">}}).
### Redis
To use redis, you'll need to add this to the config examples below:
{{< highlight yaml >}}
version: '3'
services:
api:
image: vikunja/api
environment:
VIKUNJA_REDIS_ENABLED: 1
VIKUNJA_REDIS_HOST: 'redis:6379'
VIKUNJA_CACHE_ENABLED: 1
VIKUNJA_CACHE_TYPE: redis
volumes:
- ./files:/app/vikunja/files
redis:
image: redis
{{< /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/).
We also make a few assumtions here which you'll most likely need to adjust for your traefik setup:
* Your domain is `vikunja.example.com`
* The entrypoint you want to make vikunja available from is called `https`
* The tls cert resolver is called `acme`
{{< highlight yaml >}}
version: '3'
services:
api:
image: vikunja/api
environment:
VIKUNJA_DATABASE_HOST: db
VIKUNJA_DATABASE_PASSWORD: supersecret
VIKUNJA_DATABASE_TYPE: mysql
VIKUNJA_DATABASE_USER: vikunja
VIKUNJA_DATABASE_DATABASE: vikunja
volumes:
- ./files:/app/vikunja/files
networks:
- web
- default
depends_on:
- db
restart: unless-stopped
labels:
- "traefik.enable=true"
- "traefik.http.routers.vikunja-api.rule=Host(`vikunja.example.com`) && PathPrefix(`/api/v1`)"
- "traefik.http.routers.vikunja-api.entrypoints=https"
- "traefik.http.routers.vikunja-api.tls.certResolver=acme"
frontend:
image: vikunja/frontend
labels:
- "traefik.enable=true"
- "traefik.http.routers.vikunja-frontend.rule=Host(`vikunja.example.com`)"
- "traefik.http.routers.vikunja-frontend.entrypoints=https"
- "traefik.http.routers.vikunja-frontend.tls.certResolver=acme"
networks:
- web
- default
restart: unless-stopped
db:
image: mariadb:10
environment:
MYSQL_ROOT_PASSWORD: supersupersecret
MYSQL_USER: vikunja
MYSQL_PASSWORD: supersecret
MYSQL_DATABASE: vikunja
volumes:
- ./db:/var/lib/mysql
restart: unless-stopped
command: --max-connections=1000
networks:
web:
external: true
{{< /highlight >}}
## Example with traefik 1
This example assumes [traefik](https://traefik.io) in version 1 installed and configured to [use docker as a configuration provider](https://docs.traefik.io/v1.7/configuration/backends/docker/).
{{< highlight yaml >}}
version: '3'
services:
api:
image: vikunja/api
environment:
VIKUNJA_DATABASE_HOST: db
VIKUNJA_DATABASE_PASSWORD: supersecret
VIKUNJA_DATABASE_TYPE: mysql
VIKUNJA_DATABASE_USER: vikunja
VIKUNJA_DATABASE_DATABASE: vikunja
volumes:
- ./files:/app/vikunja/files
networks:
- web
- default
depends_on:
- db
restart: unless-stopped
labels:
- "traefik.docker.network=web"
- "traefik.enable=true"
- "traefik.frontend.rule=Host:vikunja.example.com;PathPrefix:/api/v1"
- "traefik.port=3456"
- "traefik.protocol=http"
frontend:
image: vikunja/frontend
labels:
- "traefik.docker.network=web"
- "traefik.enable=true"
- "traefik.frontend.rule=Host:vikunja.example.com;PathPrefix:/"
- "traefik.port=80"
- "traefik.protocol=http"
networks:
- web
- default
restart: unless-stopped
db:
image: mariadb:10
environment:
MYSQL_ROOT_PASSWORD: supersupersecret
MYSQL_USER: vikunja
MYSQL_PASSWORD: supersecret
MYSQL_DATABASE: vikunja
volumes:
- ./db:/var/lib/mysql
restart: unless-stopped
command: --max-connections=1000
networks:
web:
external: true
{{< /highlight >}}
## Example with nginx as proxy
You'll need to save this nginx configuration on your host under `nginx.conf`
(or elsewhere, but then you'd need to adjust the proxy mount at the bottom of the compose file):
@ -30,7 +177,7 @@ server {
}
{{< /highlight >}}
### Without redis
`docker-compose.yml` config:
{{< highlight yaml >}}
version: '3'
@ -43,6 +190,7 @@ services:
MYSQL_DATABASE: vikunja
volumes:
- ./db:/var/lib/mysql
restart: unless-stopped
api:
image: vikunja/api
environment:
@ -51,10 +199,14 @@ services:
VIKUNJA_DATABASE_TYPE: mysql
VIKUNJA_DATABASE_USER: root
VIKUNJA_DATABASE_DATABASE: vikunja
volumes:
- ./files:/app/vikunja/files
depends_on:
- db
restart: unless-stopped
frontend:
image: vikunja/frontend
restart: unless-stopped
proxy:
image: nginx
ports:
@ -64,9 +216,21 @@ services:
depends_on:
- api
- frontend
restart: unless-stopped
{{< /highlight >}}
### With redis
## Example with Caddy v2 as proxy
You will need the following `Caddyfile` on your host (or elsewhere, but then you'd need to adjust the proxy mount at the bottom of the compose file):
{{< highlight conf >}}
vikunja.example.com {
reverse_proxy /api/* api:3456
reverse_proxy frontend:80
}
{{< /highlight >}}
`docker-compose.yml` config:
{{< highlight yaml >}}
version: '3'
@ -79,8 +243,7 @@ services:
MYSQL_DATABASE: vikunja
volumes:
- ./db:/var/lib/mysql
redis:
image: redis
restart: unless-stopped
api:
image: vikunja/api
environment:
@ -89,22 +252,23 @@ services:
VIKUNJA_DATABASE_TYPE: mysql
VIKUNJA_DATABASE_USER: root
VIKUNJA_DATABASE_DATABASE: vikunja
VIKUNJA_REDIS_ENABLED: 1
VIKUNJA_REDIS_HOST: 'redis:6379'
VIKUNJA_CACHE_ENABLED: 1
VIKUNJA_CACHE_TYPE: redis
volumes:
- ./files:/app/vikunja/files
depends_on:
- db
- redis
restart: unless-stopped
frontend:
image: vikunja/frontend
proxy:
image: nginx
restart: unless-stopped
caddy:
image: caddy
restart: unless-stopped
ports:
- 80:80
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
- "80:80"
- "443:443"
depends_on:
- api
- frontend
{{< /highlight >}}
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
{{< /highlight >}}

View File

@ -55,6 +55,7 @@ After=network.target
# Depending on how you configured Vikunja, you may want to uncomment these:
#Requires=mysql.service
#Requires=mariadb.service
#Requires=postgresql.service
#Requires=redis.service
[Service]
@ -105,7 +106,7 @@ docker run -p 3456:3456 vikunja/api
{{< /highlight >}}
to run with a standard configuration.
This will expose
This will expose vikunja on port `3456` on the host running the container.
You can mount a local configuration like so:
@ -116,6 +117,18 @@ docker run -p 3456:3456 -v /path/to/config/on/host.yml:/app/vikunja/config.yml:r
Though it is recommended to use eviroment variables or `.env` files to configure Vikunja in docker.
See [config]({{< ref "config.md">}}) for a list of available configuration options.
### Files volume
By default the container stores all files uploaded and used through vikunja inside of `/app/vikunja/files` which is created as a docker volume.
You should mount the volume somewhere to the host to permanently store the files and don't loose them if the container restarts.
### Setting user and group id of the user running vikunja
You can set the user and group id of the user running vikunja with the `PUID` and `PGID` evironment variables.
This follows the pattern used by [the linuxserver.io](https://docs.linuxserver.io/general/understanding-puid-and-pgid) docker images.
This is useful to solve general permission problems when host-mounting volumes such as the volume used for task attachments.
### Docker compose
To run the backend with a mariadb database you can use this example [docker-compose](https://docs.docker.com/compose/) file:
@ -131,13 +144,15 @@ services:
VIKUNJA_DATABASE_TYPE: mysql
VIKUNJA_DATABASE_USER: root
VIKUNJA_SERVICE_JWTSECRET: <generated secret>
volumes:
- ./files:/app/vikunja/files
db:
image: mariadb:10
environment:
MYSQL_ROOT_PASSWORD: supersecret
MYSQL_DATABASE: vikunja
volumes:
- ./db:/var/lib/mysql
- ./db:/var/lib/mysql
{{< /highlight >}}
See [full docker example]({{< ref "full-docker-example.md">}}) for more varations of this config.

View File

@ -17,6 +17,20 @@ Unzip them and store them somewhere your server can access them.
You also need to configure a rewrite condition to internally redirect all requests to `index.html` which handles all urls.
## API URL configuration
By default, the frontend assumes it can reach the api at `/api/v1` relative to the frontend url.
This means that if you make the frontend available at, say `https://vikunja.example.com`, it tries to reach the api
at `https://vikunja.example.com/api/v1`.
In this scenario it is not possible for the frontend and the api to live on seperate servers or even just seperate
ports on the same server with [the use of a reverse proxy]({{< ref "reverse-proxies.md">}}).
To make configurations like this possible, the api url can be set in the `index.html` file of the frontend releases.
Just open the file with a text editor - there are comments which will explain how to set the url.
**Note:** This needs to be done again after every update.
(If you have a good idea for a better solution than this, we'd love to [hear it](https://vikunja.io/contact/))
## Docker
The docker image is based on nginx and just contains all nessecary files for the frontend.
@ -31,6 +45,16 @@ which will run the docker image and expose port 80 on the host.
See [full docker example]({{< ref "full-docker-example.md">}}) for more varations of this config.
### Setting user and group id of the user running vikunja
You can set the user and group id of the user running vikunja with the `PUID` and `PGID` evironment variables.
This follows the pattern used by [the linuxserver.io](https://docs.linuxserver.io/general/understanding-puid-and-pgid) docker images.
### API URL configuration in docker
When running the frontend with docker, it is possible to set the environment variable `$VIKUNJA_API_URL` to the api url.
It is therefore not needed to change the url manually inside the docker container.
## NGINX
Below are two example configurations which you can put in your `nginx.conf`:

View File

@ -81,4 +81,23 @@ Starts Vikunja's REST api server.
Usage:
{{< highlight bash >}}
$ vikunja web
{{< /highlight >}}
{{< /highlight >}}
### `dump`
Creates a zip file with all vikunja-related files.
This includes config, version, all files and the full database.
Usage:
{{< highlight bash >}}
$ vikunja dump
{{< /highlight >}}
### `restore`
Restores a previously created dump from a zip file, see `dump`.
Usage:
{{< highlight bash >}}
$ vikunja restore <path to dump zip file>
{{< /highlight >}}

View File

@ -12,6 +12,14 @@ menu:
This document describes the different errors Vikunja can return.
### Generic
| ErrorCode | HTTP Status Code | Description |
|-----------|------------------|-------------|
| 0001 | 403 | Generic forbidden error. |
### User
| ErrorCode | HTTP Status Code | Description |
|-----------|------------------|-------------|
| 1001 | 400 | A user with this username already exists. |
@ -26,31 +34,99 @@ This document describes the different errors Vikunja can return.
| 1012 | 412 | Email address of the user not confirmed. |
| 1013 | 412 | New password is empty. |
| 1014 | 412 | Old password is empty. |
| 1015 | 412 | Totp is already enabled for this user. |
| 1016 | 412 | Totp is not enabled for this user. |
| 1017 | 412 | The provided Totp passcode is invalid. |
### Validation
| ErrorCode | HTTP Status Code | Description |
|-----------|------------------|-------------|
| 2001 | 400 | ID cannot be empty or 0. |
| 2002 | 400 | Some of the request data was invalid. The response contains an aditional array with all invalid fields. |
### List
| ErrorCode | HTTP Status Code | Description |
|-----------|------------------|-------------|
| 3001 | 404 | The list does not exist. |
| 3004 | 403 | The user needs to have read permissions on that list to perform that action. |
| 3005 | 400 | The list title cannot be empty. |
| 3006 | 404 | The list share does not exist. |
| 3007 | 400 | A list with this identifier already exists. |
| 3008 | 412 | The list is archived and can therefore only be accessed read only. This is also true for all tasks associated with this list. |
### Task
| ErrorCode | HTTP Status Code | Description |
|-----------|------------------|-------------|
| 4001 | 400 | The list task text cannot be empty. |
| 4002 | 404 | The list task does not exist. |
| 4003 | 403 | All bulk editing tasks must belong to the same list. |
| 4004 | 403 | Need at least one task when bulk editing tasks. |
| 4005 | 403 | The user does not have the right to see the task. |
| 4006 | 403 | The user tried to set a parent task as the task itself. |
| 4007 | 400 | The user tried to create a task relation with an invalid kind of relation. |
| 4008 | 409 | The user tried to create a task relation which already exists. |
| 4009 | 404 | The task relation does not exist. |
| 4010 | 400 | Cannot relate a task with itself. |
| 4011 | 404 | The task attachment does not exist. |
| 4012 | 400 | The task attachment is too large. |
| 4013 | 400 | The task sort param is invalid. |
| 4014 | 400 | The task sort order is invalid. |
| 4015 | 404 | The task comment does not exist. |
| 4016 | 403 | Invalid task field. |
| 4017 | 403 | Invalid task filter comparator. |
| 4018 | 403 | Invalid task filter concatinator. |
### Namespace
| ErrorCode | HTTP Status Code | Description |
|-----------|------------------|-------------|
| 5001 | 404 | The namspace does not exist. |
| 5003 | 403 | The user does not have access to the specified namespace. |
| 5006 | 400 | The namespace name cannot be empty. |
| 5009 | 403 | The user needs to have namespace read access to perform that action. |
| 5010 | 403 | This team does not have access to that namespace. |
| 5011 | 409 | This user has already access to that namespace. |
| 5012 | 412 | The namespace is archived and can therefore only be accessed read only. |
### Team
| ErrorCode | HTTP Status Code | Description |
|-----------|------------------|-------------|
| 6001 | 400 | The team name cannot be emtpy. |
| 6002 | 404 | The team does not exist. |
| 6004 | 409 | The team already has access to that namespace or list. |
| 6005 | 409 | The user is already a member of that team. |
| 6006 | 400 | Cannot delete the last team member. |
| 6007 | 403 | The team does not have access to the list to perform that action. |
### User List Access
| ErrorCode | HTTP Status Code | Description |
|-----------|------------------|-------------|
| 7002 | 409 | The user already has access to that list. |
| 7003 | 403 | The user does not have access to that list. |
### Label
| ErrorCode | HTTP Status Code | Description |
|-----------|------------------|-------------|
| 8001 | 403 | This label already exists on that task. |
| 8002 | 404 | The label does not exist. |
| 8003 | 403 | The user does not have access to this label. |
| 9001 | 403 | The right is invalid. |
### Right
| ErrorCode | HTTP Status Code | Description |
|-----------|------------------|-------------|
| 9001 | 403 | The right is invalid. |
### Kanban
| ErrorCode | HTTP Status Code | Description |
|-----------|------------------|-------------|
| 10001 | 404 | The bucket does not exist. |
| 10002 | 400 | The bucket does not belong to that list. |
| 10003 | 412 | You cannot remove the last bucket on a list. |

View File

@ -0,0 +1,25 @@
---
date: "2019-09-25:00:00+02:00"
title: "Task Relation kinds"
draft: false
type: "doc"
menu:
sidebar:
parent: "usage"
---
# Available task relation kinds
| Code | Description |
|------|-------------|
| subtask | Task is a subtask of the other task. This is the opposite of `parenttask`. |
| parenttask | Task is a parent task of the other task. This is the opposite of `subtask`. |
| related | Both tasks are related to each other. How is not more specified. |
| duplicateof | Task is a duplicate of the other task. This is the opposite of `duplicates`. |
| duplicates | Task duplicates the other task. This is the opposite of `duplicateof`. |
| blocking | Task is blocking the other task. This is the opposite of `blocked`. |
| blocked | Task is blocked by the other task. This is the opposite of `blocking`. |
| precedes | Task precedes the other task. This is the opposite of `follows`. |
| follows | Task follows the other task. This is the opposite of `precedes`. |
| copiedfrom | Task is copied from the other task. This is the opposite of `copiedto`. |
| copiedto | Task is copied to the other task. This is the opposite of `copiedfrom`. |

2
docs/themes/vikunja vendored

@ -1 +1 @@
Subproject commit 611b91ba5df3c88e9262e040eb1e6fa772919fc8
Subproject commit f50566db25df9fa03243ba06d17511e050d4be95

102
go.mod
View File

@ -1,4 +1,4 @@
// Vikunja is a todo-list application to facilitate your life.
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
@ -17,65 +17,79 @@
module code.vikunja.io/api
require (
cloud.google.com/go v0.34.0 // indirect
code.vikunja.io/web v0.0.0-20190507193736-edb39812af9c
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc
4d63.com/embedfiles v1.0.0 // indirect
4d63.com/tz v1.1.0
code.vikunja.io/web v0.0.0-20200618164749-a5f3d450d39a
gitea.com/xorm/xorm-redis-cache v0.2.0
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a
github.com/beevik/etree v1.1.0 // indirect
github.com/c2h5oh/datasize v0.0.0-20200112174442-28bbd4740fee
github.com/client9/misspell v0.3.4
github.com/cweill/gotests v1.5.3
github.com/d4l3k/messagediff v1.2.1 // indirect
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/fzipp/gocyclo v0.0.0-20150627053110-6acd4345c835
github.com/garyburd/redigo v1.6.0 // indirect
github.com/go-openapi/jsonpointer v0.19.0 // indirect
github.com/go-openapi/jsonreference v0.19.0 // indirect
github.com/go-openapi/spec v0.19.0 // indirect
github.com/go-openapi/swag v0.19.0 // indirect
github.com/go-redis/redis v6.15.2+incompatible
github.com/go-sql-driver/mysql v1.4.1
github.com/go-xorm/builder v0.3.4
github.com/go-xorm/core v0.6.2
github.com/go-xorm/tests v0.5.6 // indirect
github.com/go-xorm/xorm v0.7.1
github.com/go-xorm/xorm-redis-cache v0.0.0-20180727005610-859b313566b2
github.com/gordonklaus/ineffassign v0.0.0-20180909121442-1003c8bd00dc
github.com/imdario/mergo v0.3.7
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/jgautheron/goconst v0.0.0-20170703170152-9740945f5dcb
github.com/labstack/echo/v4 v4.1.5
github.com/labstack/gommon v0.2.8
github.com/getsentry/sentry-go v0.6.1
github.com/go-openapi/jsonreference v0.19.3 // indirect
github.com/go-openapi/spec v0.19.4 // indirect
github.com/go-redis/redis/v7 v7.4.0
github.com/go-sql-driver/mysql v1.5.0
github.com/go-testfixtures/testfixtures/v3 v3.2.0
github.com/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf
github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334
github.com/imdario/mergo v0.3.9
github.com/jgautheron/goconst v0.0.0-20200227150835-cda7ea3bf591
github.com/kr/text v0.2.0 // indirect
github.com/labstack/echo/v4 v4.1.16
github.com/labstack/gommon v0.3.0
github.com/laurent22/ical-go v0.1.1-0.20181107184520-7e5d6ade8eef
github.com/mailru/easyjson v0.0.0-20190403194419-1ea4449da983 // indirect
github.com/mattn/go-oci8 v0.0.0-20181130072307-052f5d97b9b6 // indirect
github.com/mattn/go-runewidth v0.0.4 // indirect
github.com/mattn/go-sqlite3 v1.10.0
github.com/olekukonko/tablewriter v0.0.1
github.com/onsi/ginkgo v1.7.0 // indirect
github.com/onsi/gomega v1.4.3 // indirect
github.com/lib/pq v1.7.0
github.com/mailru/easyjson v0.7.0 // indirect
github.com/mattn/go-sqlite3 v2.0.3+incompatible
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/olekukonko/tablewriter v0.0.4
github.com/onsi/ginkgo v1.12.0 // indirect
github.com/onsi/gomega v1.9.0 // indirect
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
github.com/pelletier/go-toml v1.4.0 // indirect
github.com/prometheus/client_golang v0.9.2
github.com/pquerna/otp v1.2.0
github.com/prometheus/client_golang v1.7.0
github.com/samedi/caldav-go v3.0.0+incompatible
github.com/spf13/afero v1.2.2 // indirect
github.com/spf13/cobra v0.0.3
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749
github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd
github.com/spf13/afero v1.3.0
github.com/spf13/cobra v1.0.0
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/viper v1.3.2
github.com/stretchr/testify v1.3.0
github.com/swaggo/swag v1.5.0
golang.org/x/crypto v0.0.0-20190506204251-e1dfcc566284
golang.org/x/lint v0.0.0-20190409202823-959b441ac422
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c // indirect
google.golang.org/appengine v1.5.0 // indirect
github.com/spf13/viper v1.7.0
github.com/stretchr/testify v1.6.1
github.com/swaggo/swag v1.6.3
github.com/ulule/limiter/v3 v3.5.0
github.com/urfave/cli v1.22.2 // indirect
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9
golang.org/x/lint v0.0.0-20200302205851-738671d3881b
golang.org/x/net v0.0.0-20200602114024-627f9648deb9 // indirect
golang.org/x/text v0.3.3 // indirect
golang.org/x/tools v0.0.0-20200410194907-79a7a3126eef // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
gopkg.in/d4l3k/messagediff.v1 v1.2.1
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
gopkg.in/testfixtures.v2 v2.5.3
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a
src.techknowlogick.com/xgo v0.0.0-20190507142556-a5b29ecb0ff4
src.techknowlogick.com/xormigrate v0.0.0-20190321151057-24497c23c09c
honnef.co/go/tools v0.0.1-2020.1.4
src.techknowlogick.com/xgo v0.0.0-20200602060627-a09175ea9056
src.techknowlogick.com/xormigrate v1.3.0
xorm.io/builder v0.3.7
xorm.io/core v0.7.3
xorm.io/xorm v1.0.2
)
replace (
github.com/labstack/echo/v4 => github.com/kolaente/echo/v4 v4.0.0-20190507190305-3725a216d803 // Branch: feature/report-method, PR https://github.com/labstack/echo/pull/1332
github.com/coreos/bbolt => go.etcd.io/bbolt v1.3.4
github.com/coreos/go-systemd => github.com/coreos/go-systemd/v22 v22.0.0
github.com/hpcloud/tail => github.com/jeffbean/tail v1.0.1 // See https://github.com/hpcloud/tail/pull/159
github.com/samedi/caldav-go => github.com/kolaente/caldav-go v3.0.1-0.20190524174923-9e5cd1688227+incompatible // Branch: feature/dynamic-supported-components, PR: https://github.com/samedi/caldav-go/pull/6 and https://github.com/samedi/caldav-go/pull/7
gopkg.in/fsnotify.v1 => github.com/kolaente/fsnotify v1.4.10-0.20200411160148-1bc3c8ff4048 // See https://github.com/fsnotify/fsnotify/issues/328 and https://github.com/golang/go/issues/26904
)
go 1.13

802
go.sum
View File

@ -1,302 +1,892 @@
cloud.google.com/go v0.33.1/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
4d63.com/embedfiles v0.0.0-20190311033909-995e0740726f h1:oyYjGRBNq1TxAIG8aHqtxlvqUfzdZf+MbcRb/oweNfY=
4d63.com/embedfiles v0.0.0-20190311033909-995e0740726f/go.mod h1:HxEsUxoVZyRxsZML/S6e2xAuieFMlGO0756ncWx1aXE=
4d63.com/embedfiles v1.0.0 h1:AR4j5WItSJwBX9SapkvmQUGLPlgCHQZaCDQ52zLXzZM=
4d63.com/embedfiles v1.0.0/go.mod h1:U0e+fedkrGPVJiU29PWZQ7pHHZRPiQAzwDJocZ4d3PE=
4d63.com/tz v1.1.0 h1:Hi58WbeFjiUH4XOWuCpl5iSzuUuw1axZzTqIfMKPKrg=
4d63.com/tz v1.1.0/go.mod h1:SHGqVdL7hd2ZaX2T9uEiOZ/OFAUfCCLURdLPJsd8ZNs=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0 h1:eOI3/cP2VTU6uZLDYAoic+eyzzB9YyGmJ7eIjl8rOPg=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
code.vikunja.io/web v0.0.0-20190507193736-edb39812af9c h1:L8aPCsaLQe9qytRavkRqipse64EbDK8mFijm+9SKf7I=
code.vikunja.io/web v0.0.0-20190507193736-edb39812af9c/go.mod h1:9dOotUqYZJhDhimNh4Xo4e2i+8cR+qPFEQNCUzaplsI=
cloud.google.com/go v0.37.4 h1:glPeL3BQJsbF6aIIYfZizMwc5LTYz250bDMjttbBGAU=
cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
code.vikunja.io/web v0.0.0-20200618164749-a5f3d450d39a h1:RiLIcnTTBP43QlL7nL0ko+PkzaBUCp7NmgogPeZBx5I=
code.vikunja.io/web v0.0.0-20200618164749-a5f3d450d39a/go.mod h1:q3to9xazLf9XoqIRk1Y+YCjGr5TYgpQFNSVclCKrmEQ=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
gitea.com/xorm/tests v0.7.0 h1:pFcaxTGGAWw3rDuVfhBdyr+mX1uzdTtncyAKxkCQ/IE=
gitea.com/xorm/tests v0.7.0/go.mod h1:ngmhQrSBgihBbOqw1hdReSQJAnTlbStYTn0vruUFwDc=
gitea.com/xorm/xorm-redis-cache v0.2.0 h1:qglRHt6/7vJmDeld6j+n10M9PmruAh+Le2lgNraFu3g=
gitea.com/xorm/xorm-redis-cache v0.2.0/go.mod h1:juYdjkmIKvLbPkdfBVKGVJ2daFQIJAgKsn4mL4ZK8Zk=
github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/CloudyKit/fastprinter v0.0.0-20170127035650-74b38d55f37a/go.mod h1:EFZQ978U7x8IRnstaskI3IysnWY5Ao3QgZUKOXlsAdw=
github.com/CloudyKit/jet v2.1.3-0.20180809161101-62edd43e4f88+incompatible/go.mod h1:HPYO+50pSWkPoj9Q/eq0aRGByCL6ScRlUmiEX5Zgm+w=
github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY=
github.com/Joker/jade v1.0.1-0.20190614124447-d475f43051e7/go.mod h1:6E6s8o2AE4KhCrqr6GRJjdC/gNfTdxkIXvuGZZda2VM=
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.0 h1:rmGxhojJlM0tuKtfdvliR84CFHljx9ag64t2xmVkjK4=
github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU=
github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
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/alexbrainman/sspi v0.0.0-20180613141037-e580b900e9f5 h1:P5U+E4x5OkVEKQDklVPmzs71WM56RTTRqV4OrDC//Y4=
github.com/alexbrainman/sspi v0.0.0-20180613141037-e580b900e9f5/go.mod h1:976q2ETgjT2snVCf2ZaBnyBbVoPERGjUz+0sofzEfro=
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
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/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs=
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/c2h5oh/datasize v0.0.0-20200112174442-28bbd4740fee h1:BnPxIde0gjtTnc9Er7cxvBk8DHLWhEux0SxayC8dP6I=
github.com/c2h5oh/datasize v0.0.0-20200112174442-28bbd4740fee/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M=
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 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
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/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
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=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cweill/gotests v1.5.3 h1:k3t4wW/x/YNixWZJhUIn+mivmK5iV1tJVOwVYkx0UcU=
github.com/cweill/gotests v1.5.3/go.mod h1:XZYOJkGVkCRoymaIzmp9Wyi3rUgfA3oOnkuljYrjFV8=
github.com/d4l3k/messagediff v1.2.1 h1:ZcAIMYsUg0EAp9X+tt8/enBE/Q8Yd5kzPynLyKptt9U=
github.com/d4l3k/messagediff v1.2.1/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkEQxENCrlLo=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denisenkom/go-mssqldb v0.0.0-20181014144952-4e0d7dc8888f h1:WH0w/R4Yoey+04HhFxqZ6VX6I0d7RMyw5aXQ9UTvQPs=
github.com/denisenkom/go-mssqldb v0.0.0-20181014144952-4e0d7dc8888f/go.mod h1:xN/JuLBIz4bjkxNmByTiV1IbhfnYb6oo99phBn4Eqhc=
github.com/denisenkom/go-mssqldb v0.0.0-20190707035753-2be1aa521ff4 h1:YcpmyvADGYw5LqMnHqSkyIELsHCGF6PkrmM31V8rF7o=
github.com/denisenkom/go-mssqldb v0.0.0-20190707035753-2be1aa521ff4/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM=
github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM=
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/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/flosch/pongo2 v0.0.0-20190707114632-bbf5a6c351f4/go.mod h1:T9YF2M40nIgbVgp3rreNmTged+9HrbNTIQf1PsaIiTA=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
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/fzipp/gocyclo v0.0.0-20150627053110-6acd4345c835 h1:roDmqJ4Qes7hrDOsWsMCce0vQHz3xiMPjJ9m4c2eeNs=
github.com/fzipp/gocyclo v0.0.0-20150627053110-6acd4345c835/go.mod h1:BjL/N0+C+j9uNX+1xcNuM9vdSIcXCZrQZUYbXOFbgN8=
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=
github.com/getsentry/sentry-go v0.6.1 h1:K84dY1/57OtWhdyr5lbU78Q/+qgzkEyGc/ud+Sipi5k=
github.com/getsentry/sentry-go v0.6.1/go.mod h1:0yZBuzSvbZwBnvaF9VwZIMen3kXscY8/uasKtAX1qG8=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/gzip v0.0.1/go.mod h1:fGBJBCdt6qCZuCAOwWuFhBB4OOq9EFqlo5dEaFhhu5w=
github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y=
github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
github.com/gin-gonic/gin v1.6.2/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8=
github.com/go-openapi/jsonpointer v0.17.0 h1:nH6xp8XdXHx8dqveo0ZuJBluCO2qGrPbDNZ0dwoRHP0=
github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
github.com/go-openapi/jsonpointer v0.19.0 h1:FTUMcX77w5rQkClIzDtTxvn6Bsa894CcrzNj2MMfeg8=
github.com/go-openapi/jsonpointer v0.19.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonreference v0.17.0 h1:yJW3HCkTHg7NOA+gZ83IPHzUSnUzGXhGmsdiCcMexbA=
github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
github.com/go-openapi/jsonreference v0.19.0 h1:BqWKpV1dFd+AuiKlgtddwVIFQsuMpxfBDBHGfM2yNpk=
github.com/go-openapi/jsonreference v0.19.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
github.com/go-openapi/jsonreference v0.19.3 h1:5cxNfTy0UVC3X8JL5ymxzyoUZmo8iZb+jeTWn7tUa8o=
github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
github.com/go-openapi/spec v0.19.0 h1:A4SZ6IWh3lnjH0rG0Z5lkxazMGBECtrZcbyYQi+64k4=
github.com/go-openapi/spec v0.19.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
github.com/go-openapi/spec v0.19.4 h1:ixzUSnHTd6hCemgtAJgluaTSGYpLNpJY4mA2DIkdOAo=
github.com/go-openapi/spec v0.19.4/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
github.com/go-openapi/swag v0.19.0 h1:Kg7Wl7LkTPlmc393QZQ/5rQadPhi7pBVEMZxyTi0Ii8=
github.com/go-openapi/swag v0.19.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
github.com/go-redis/redis v6.15.2+incompatible h1:9SpNVG76gr6InJGxoZ6IuuxaCOQwDAhzyXg+Bs+0Sb4=
github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
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-redis/redis/v7 v7.2.0 h1:CrCexy/jYWZjW0AyVoHlcJUeZN19VWlbepTh1Vq6dJs=
github.com/go-redis/redis/v7 v7.2.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg=
github.com/go-redis/redis/v7 v7.4.0 h1:7obg6wUoj05T0EpY0o8B59S9w5yeMWql7sw2kwNW1x4=
github.com/go-redis/redis/v7 v7.4.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg=
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-xorm/builder v0.3.2 h1:pSsZQRRzJNapKEAEhigw3xLmiLPeAYv5GFlpYZ8+a5I=
github.com/go-xorm/builder v0.3.2/go.mod h1:v8mE3MFBgtL+RGFNfUnAMUqqfk/Y4W5KuwCFQIEpQLk=
github.com/go-xorm/builder v0.3.4 h1:FxkeGB4Cggdw3tPwutLCpfjng2jugfkg6LDMrd/KsoY=
github.com/go-xorm/builder v0.3.4/go.mod h1:KxkQkNN1DpPKTedxXyTQcmH+rXfvk4LZ9SOOBoZBAxw=
github.com/go-xorm/core v0.6.0 h1:tp6hX+ku4OD9khFZS8VGBDRY3kfVCtelPfmkgCyHxL0=
github.com/go-xorm/core v0.6.0/go.mod h1:d8FJ9Br8OGyQl12MCclmYBuBqqxsyeedpXciV5Myih8=
github.com/go-xorm/core v0.6.2 h1:EJLcSxf336POJr670wKB55Mah9f93xzvGYzNRgnT8/Y=
github.com/go-xorm/core v0.6.2/go.mod h1:bwPIfLdm/FzWgVUH8WPVlr+uJhscvNGFcaZKXsI3n2c=
github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:9wScpmSP5A3Bk8V3XHWUcJmYTh+ZnlHVyc+A4oZYS3Y=
github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:56xuuqnHyryaerycW3BfssRdxQstACi0Epw/yC5E2xM=
github.com/go-xorm/tests v0.5.6 h1:E4nmVkKfHQAm+i2/pmOJ5JUej6sORVcvwl6/LQybif4=
github.com/go-xorm/tests v0.5.6/go.mod h1:s8J/EnVBcXQR93dN7Jy6Dwlo92HUP5nTgKWF1wGeCDg=
github.com/go-xorm/xorm v0.7.1 h1:Kj7mfuqctPdX60zuxP6EoEut0f3E6K66H6hcoxiHUMc=
github.com/go-xorm/xorm v0.7.1/go.mod h1:EHS1htMQFptzMaIHKyzqpHGw6C9Rtug75nsq6DA9unI=
github.com/go-xorm/xorm-redis-cache v0.0.0-20180727005610-859b313566b2 h1:57QbyUkFcFjipHJQstYR5owRxsQzgD8/OAO/hr4yl/E=
github.com/go-xorm/xorm-redis-cache v0.0.0-20180727005610-859b313566b2/go.mod h1:xxK9FGkFXrau9/vGdDYSOyQfSgKXBV7iHXpQfNuv6B0=
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-testfixtures/testfixtures/v3 v3.2.0 h1:FGAW3z5UzmrZGjR/dZp1u3Tbld0SDmirLO4RrR5++7Q=
github.com/go-testfixtures/testfixtures/v3 v3.2.0/go.mod h1:RZctY24ixituGC73XlAV1gkCwYMVwiSwPm26MNlQIhE=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
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/gofrs/uuid v3.2.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/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/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
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=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0 h1:oOuy+ugB+P/kBdUnG5QaMXSIyJ1q38wWSojYCb3z5VQ=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/gordonklaus/ineffassign v0.0.0-20180909121442-1003c8bd00dc h1:cJlkeAx1QYgO5N80aF5xRGstVsRQwgLR7uA2FnP1ZjY=
github.com/gordonklaus/ineffassign v0.0.0-20180909121442-1003c8bd00dc/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
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/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
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/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf h1:vc7Dmrk4JwS0ZPS6WZvWlwDflgDTA26jItmbSj83nug=
github.com/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU=
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/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
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-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/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
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=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
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/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI=
github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
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/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334 h1:VHgatEHNcBFEB7inlalqfNqw65aNkM1lGX2yt3NmbS8=
github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE=
github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg=
github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI=
github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0=
github.com/iris-contrib/i18n v0.0.0-20171121225848-987a633949d0/go.mod h1:pMCz62A0xJL6I+umB2YTlFRwWXaDFA0jy+5HzGiJjqI=
github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw=
github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 h1:vr3AYkKovP8uR8AvSGGUK1IDqRa5lAAvEkZG1LKaCRc=
github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ=
github.com/jackc/pgx v3.2.0+incompatible h1:0Vihzu20St42/UDsvZGdNE6jak7oi/UOeMzwMPHkgFY=
github.com/jackc/pgx v3.2.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I=
github.com/jgautheron/goconst v0.0.0-20170703170152-9740945f5dcb h1:D5s1HIu80AcMGcqmk7fNIVptmAubVHHaj3v5Upex6Zs=
github.com/jgautheron/goconst v0.0.0-20170703170152-9740945f5dcb/go.mod h1:82TxjOpWQiPmywlbIaB2ZkqJoSYJdLGPgAJDvM3PbKc=
github.com/jackc/pgx v3.6.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I=
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM=
github.com/jcmturner/gofork v1.0.0 h1:J7uCkflzTEhUZ64xqKnkDxq3kzc96ajM1Gli5ktUem8=
github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
github.com/jcmturner/goidentity/v6 v6.0.1 h1:VKnZd2oEIMorCTsFBnJWbExfNN7yZr3EhJAxwOkZg6o=
github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg=
github.com/jcmturner/gokrb5/v8 v8.2.0 h1:lzPl/30ZLkTveYsYZPKMcgXc8MbnE6RsTd4F9KgiLtk=
github.com/jcmturner/gokrb5/v8 v8.2.0/go.mod h1:T1hnNppQsBtxW0tCHMHTkAt8n/sABdzZgZdoFrZaZNM=
github.com/jcmturner/rpc/v2 v2.0.2 h1:gMB4IwRXYsWw4Bc6o/az2HJgFUA1ffSh90i26ZJ6Xl0=
github.com/jcmturner/rpc/v2 v2.0.2/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc=
github.com/jeffbean/tail v1.0.1 h1:mRuCwa9iq5kH1SBFAIWWFsg+NEi5+VVEf2E2ORVkKp8=
github.com/jeffbean/tail v1.0.1/go.mod h1:+MhJ+VPZMpv8Ui6WRzpJFuWFKxBCZgVOo5HAmlw1sFc=
github.com/jgautheron/goconst v0.0.0-20200227150835-cda7ea3bf591 h1:x/BpEhm6aL26o4TLtcU0loJ7B3+69jielrGc70V7Yb4=
github.com/jgautheron/goconst v0.0.0-20200227150835-cda7ea3bf591/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
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/juju/errors v0.0.0-20181118221551-089d3ea4e4d5/go.mod h1:W54LbzXuIE0boCoNJfwqpmkKJ1O4TCTZMetAt6jGk7Q=
github.com/juju/loggo v0.0.0-20180524022052-584905176618/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U=
github.com/juju/testing v0.0.0-20180920084828-472a3e8b2073/go.mod h1:63prj8cnj0tU0S9OHjGJn+b1h0ZghCndfnbQolrYTwA=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
github.com/kataras/golog v0.0.9/go.mod h1:12HJgwBIZFNGL0EJnMRhmvGA0PQGx8VFwrZtM4CqbAk=
github.com/kataras/iris/v12 v12.0.1/go.mod h1:udK4vLQKkdDqMGJJVd/msuMtN6hpYJhg/lSzuxjhO+U=
github.com/kataras/neffos v0.0.10/go.mod h1:ZYmJC07hQPW67eKuzlfY7SO3bC0mw83A3j6im82hfqw=
github.com/kataras/pio v0.0.0-20190103105442-ea782b38602d/go.mod h1:NV88laa9UiiDuX9AhMbDPkGYSPugBOV6yTZB1l2K9Z0=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.9.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.9.6/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
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=
github.com/kolaente/echo/v4 v4.0.0-20190507190305-3725a216d803 h1:SmVrdODUE6KErl2Xo7nqNOqGG1X8UaXnTeU7zzJiNQw=
github.com/kolaente/echo/v4 v4.0.0-20190507190305-3725a216d803/go.mod h1:3LbYC6VkwmUnmLPZ8WFdHdQHG77e9GQbjyhWdb1QvC4=
github.com/kolaente/fsnotify v1.4.10-0.20200411160148-1bc3c8ff4048 h1:j/BtLkVyqtAXvlb22nic/pSlWLuGx4gddouWVt65HDI=
github.com/kolaente/fsnotify v1.4.10-0.20200411160148-1bc3c8ff4048/go.mod h1:dv6KyzAg9UuJWiE1pwkvvB2i0TvcQM6QhdsXLZ7K5KI=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
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 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/labstack/gommon v0.2.8 h1:JvRqmeZcfrHC5u6uVleB4NxxNbzx6gpbJiQknDbKQu0=
github.com/labstack/gommon v0.2.8/go.mod h1:/tj9csK2iPSBvn+3NLM9e52usepMtrd5ilFYA+wQNJ4=
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.7-0.20190627175217-8fb7b5be270f h1:fNJtR+TNyxTdYCZU40fc8Or8RyBqMOKYNv+Zay5gjvk=
github.com/labstack/echo/v4 v4.1.7-0.20190627175217-8fb7b5be270f/go.mod h1:kU/7PwzgNxZH4das4XNsSpBSOD09XIF5YEPzjpkGnGE=
github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g=
github.com/labstack/echo/v4 v4.1.16 h1:8swiwjE5Jkai3RPfZoahp8kjVCRNq+y7Q0hPji2Kz0o=
github.com/labstack/echo/v4 v4.1.16/go.mod h1:awO+5TzAjvL8XpibdsfXxPgHr+orhtXZJZIQCVjogKI=
github.com/labstack/gommon v0.2.9 h1:heVeuAYtevIQVYkGj6A41dtfT91LrvFG220lavpWhrU=
github.com/labstack/gommon v0.2.9/go.mod h1:E8ZTmW9vw5az5/ZyHWCp0Lw4OH2ecsaBP1C/NKavGG4=
github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0=
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
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=
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU=
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.6.0 h1:I5DPxhYJChW9KYc66se+oKFFQX6VuQrKiprsX6ivRZc=
github.com/lib/pq v1.6.0/go.mod h1:4vXEAYvW1fRQ2/FhZ78H73A60MHw1geSm145z2mdY1g=
github.com/lib/pq v1.7.0 h1:h93mCPfUSkaul3Ka/VG8uZdmW1uMHDGxzu0NWHuJmHY=
github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329 h1:2gxZ0XQIU/5z3Z3bUBu+FXuk2pFbkN6tcwi/pjyaDic=
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190403194419-1ea4449da983 h1:wL11wNW7dhKIcRCHSm4sHKPWz0tt4mwBsVodG7+Xyqg=
github.com/mailru/easyjson v0.0.0-20190403194419-1ea4449da983/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM=
github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
github.com/mattn/go-isatty v0.0.4/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 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-oci8 v0.0.0-20181115070430-6eefff3c767c/go.mod h1:/M9VLO+lUPmxvoOK2PfWRZ8mTtB4q1Hy9lEGijv9Nr8=
github.com/mattn/go-oci8 v0.0.0-20181130072307-052f5d97b9b6 h1:gheNi9lnffYyVyqQzJqY7lo+M3bCDVw5fLU/jSuCMhc=
github.com/mattn/go-oci8 v0.0.0-20181130072307-052f5d97b9b6/go.mod h1:/M9VLO+lUPmxvoOK2PfWRZ8mTtB4q1Hy9lEGijv9Nr8=
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
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.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-oci8 v0.0.0-20191108001511-cbd8d5bc1da0/go.mod h1:/M9VLO+lUPmxvoOK2PfWRZ8mTtB4q1Hy9lEGijv9Nr8=
github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54=
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
github.com/mattn/go-sqlite3 v2.0.2+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
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/mediocre-go-lib v0.0.0-20181029021733-cb65787f37ed/go.mod h1:dSsfyI2zABAdhcbvkXqgxOxrCsbYeHCPgrZkku60dSg=
github.com/mediocregopher/radix/v3 v3.3.0/go.mod h1:EmfVyvspXz1uZEyPBMyGK+kjWiKQGvsUt6O3Pj+LDCQ=
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/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
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=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
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 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/olekukonko/tablewriter v0.0.1 h1:b3iUnf1v+ppJiOfNX4yxxqfWKMQPZR5yoh8urCTFX88=
github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nats-io/nats.go v1.8.1/go.mod h1:BrFz9vVn0fU3AcH9Vn4Kd7W0NpJ651tD5omQ3M8LwxM=
github.com/nats-io/nkeys v0.0.2/go.mod h1:dab7URMsZm6Z/jp9Z5UGa87Uutgc2mVpXLC4B7TDb/4=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8=
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU=
github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg=
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1 h1:K0jcRCwNQM3vFGh1ppMtDh/+7ApJrjldlX8fA0jDTLQ=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.9.0 h1:R1uwffexN6Pr340GtYRIdZmAiN4J+iw6WG4wog1DUXg=
github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
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 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.4.0 h1:u3Z1r+oOXJIkxqw34zVhyPgjBsm6X2wn21NWs/HfSeg=
github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo=
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=
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
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/prometheus/client_golang v0.9.2 h1:awm861/B8OKDd2I/6o1dy3ra4BamzKhYOiGItCeZ740=
github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/pquerna/otp v1.2.0 h1:/A3+Jn+cagqayeR3iHs/L62m5ue7710D35zl1zJ1kok=
github.com/pquerna/otp v1.2.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829 h1:D+CiwcpGTW6pL6bv6KI3KbyEyCKyS+1JWS2h8PNDnGA=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.7.0 h1:wCi7urQOGBsYcQROHqpUUX4ct84xp40t9R9JX0FuA/U=
github.com/prometheus/client_golang v1.7.0/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275 h1:PnBWHBf+6L0jOqq0gIVUe6Yk0/QMZ640k6NvkxcBf+8=
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a h1:9a8MnZMP0X2nLJdBg+pBmGgkJlSaKC2KaQmTCk1XDtE=
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24 h1:pntxY8Ary0t43dCZ5dqY4YTJCObLY1kIXl0uzMv+7DE=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f h1:BVwpUVJDADN2ufcGik7W992pyps0wZ888b/y9GXcLTU=
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.2.0 h1:kUZDBDTdBVBYBj5Tmh2NZLlF60mfjA27rM34b+cVwNU=
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.1 h1:K0MGApIoQvMw27RTdJkPbr3JZ7DNbtxQNyi5STVM6Kw=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lNawc=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1 h1:/K3IL0Z1quvmJ7X0A1AwNEK7CRkVK3YwfOU/QAL4WGg=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.2 h1:6LJUbpNm42llc4HRCuvApCSWB/WfhuNo9K98Q9sNGfs=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
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/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-20191009025716-f1972eb1d1f5/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 h1:bUGsEnyNbVPw06Bs80sCeARAlK8lhwqGyi6UT8ymuGk=
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
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/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd h1:ug7PpSOB5RBPK1Kg6qskGBoP3Vnj/aNYFTznWvlkGo0=
github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
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/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/afero v1.3.0 h1:Ysnmjh1Di8EaWaBv40CYR4IdaIsBc5996Gh1oZzCBKk=
github.com/spf13/afero v1.3.0/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8=
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M=
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.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM=
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
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=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/swaggo/gin-swagger v1.1.0/go.mod h1:FQlm07YuT1glfN3hQiO11UQ2m39vOCZ/aa3WWr5E+XU=
github.com/swaggo/swag v1.4.0/go.mod h1:hog2WgeMOrQ/LvQ+o1YGTeT+vWVrbi0SiIslBtxKTyM=
github.com/swaggo/swag v1.5.0 h1:haK8VG3hj+v/c8hQ4f3U+oYpkdI/26m9LAUTXHOv+2U=
github.com/swaggo/swag v1.5.0/go.mod h1:+xZrnu5Ut3GcUkKAJm9spnOooIS1WB1cUOkLNPrvrE0=
github.com/ugorji/go v1.1.2/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.0 h1:jlIyCplCJFULU/01vCkhKuTyc3OorI3bJFuw6obfgho=
github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/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/files v0.0.0-20190704085106-630677cd5c14/go.mod h1:gxQT6pBGRuIGunNf/+tSOB5OHvguWi8Tbt82WOkf35E=
github.com/swaggo/gin-swagger v1.2.0/go.mod h1:qlH2+W7zXGZkczuL+r2nEBR2JTT+/lX05Nn6vPhc7OI=
github.com/swaggo/swag v1.5.1/go.mod h1:1Bl9F/ZBpVWh22nY0zmYyASPO1lI/zIwRDrpZU+tv8Y=
github.com/swaggo/swag v1.6.3 h1:N+uVPGP4H2hXoss2pt5dctoSUPKKRInr6qcTMOm0usI=
github.com/swaggo/swag v1.6.3/go.mod h1:wcc83tB4Mb2aNiL/HP4MFeQdpHUrca+Rp/DRNgWAUio=
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go v1.1.5-pre/go.mod h1:FwP/aQVg39TXzItUBMwnWp9T9gPQnXw4Poh4/oBQZ/0=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v0.0.0-20181022190402-e5e69e061d4f/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/ugorji/go/codec v0.0.0-20181209151446-772ced7fd4c2/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/ugorji/go/codec v0.0.0-20190320090025-2dc34c0b8780/go.mod h1:iT03XoTwV7xq/+UGwKO3UbC1nNNlopQiY61beSdrtOA=
github.com/ugorji/go/codec v1.1.5-pre/go.mod h1:tULtS6Gy1AE1yCENaw4Vb//HLH5njI2tfCQDUqRd8fI=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/ulule/limiter/v3 v3.5.0 h1:QRAebbswjlezHIfiSQgM8+jMxaz/zsrxGRuiUJ43MHo=
github.com/ulule/limiter/v3 v3.5.0/go.mod h1:TgOUQZKZ2KHjemqrC8UHUbKPqpTmSY43/2wbQ7YN1h8=
github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo=
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
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.9.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w=
github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8=
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
github.com/valyala/fasttemplate v1.1.0 h1:RZqt0yGBsps8NGvLSGW804QQqCUYYLsaOjTVHy1Ocw4=
github.com/valyala/fasttemplate v1.1.0/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
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=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI=
github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg=
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM=
github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ=
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190506204251-e1dfcc566284 h1:rlLehGeYg6jfoyz/eDqDU1iRXLKfR42nnNh57ytKEWo=
golang.org/x/crypto v0.0.0-20190506204251-e1dfcc566284/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422 h1:QzoH/1pFpZguR8NrRHLcO6jKqfv2zpuSqZLgdm7ZmjI=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4 h1:ydJNl0ENAG67pFbB+9tfhiL2pYqLhfoaZFw/cjLhY4A=
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200117160349-530e935923ad/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 h1:vEg9joUBmeBcK9iSJftGNf3coIG4HqZElCPehJsfAYM=
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
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=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/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=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181005035420-146acd28ed58 h1:otZG8yDCO4LVps5+9bxOeNiCvgmOyt96J3roHTYs7oE=
golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc h1:a3CU5tJYVj92DY2LaA1kUkrsqD5/3mLDhx2NcNqyW+0=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190322120337-addf6b3196f6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c h1:uOCk1iQW6Vc18bnC13MfzScl+wdKBmM9Y9kU7Z83/lw=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190607181551-461777fb6f67/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190611141213-3f473d35a33a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/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-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=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200602114024-627f9648deb9 h1:pNX+40auqi2JqRfOP1akLGtYcn15TUbkhwuCO3foqqM=
golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
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 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ=
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=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b h1:ag/x1USPSsqHud38I9BAC88qdNLDHHtQ4mlgQIZPPNA=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190609082536-301114b31cce/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb h1:fgwFCsaw9buMuxNd6+DQfAuSFqbNiQZpcgJQAgJsK6k=
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1 h1:ogLJMz+qpzav7lGMh10LMvAkM/fAoGlaiiHYiFYdm80=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
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=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
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/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190110015856-aa033095749b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190322203728-c1a832b0ad89/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c h1:97SnQk1GYRXJgvwZ8fadnxDOWfKvkNQHH3CtZntPSrM=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606050223-4d9ae51c2468/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190608022120-eacb66d2a7c3/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190611222205-d73e1c7e250b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628034336-212fb13d595e h1:ZlQjfVdpDxeqxRfmO30CdqWWzTvgRCj0MxaUVfxEG1k=
golang.org/x/tools v0.0.0-20190628034336-212fb13d595e/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
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-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-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7 h1:EBZoQjiKKPaLbPrbpssUfuHtwM6KV/vb4U85g/cigFY=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200410194907-79a7a3126eef h1:RHORRhs540cYZYrzgU2CPUyykkwZM78hGdzocOo9P8A=
golang.org/x/tools v0.0.0-20200410194907-79a7a3126eef/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.3.0 h1:FBSsiFRMz3LBeXIomRnVzrQwSDj4ibvcRexLG0LZGQk=
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.0 h1:Tfd7cKwKbFRsI8RMAD3oqqw7JPFRrvFlOsfbgVkjOOw=
google.golang.org/appengine v1.6.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
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=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
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=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0 h1:qdOKuR/EIArgaWNjetjgTzgVTAZ+S/WXVrq9HW9zimw=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
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=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/d4l3k/messagediff.v1 v1.2.1 h1:70AthpjunwzUiarMHyED52mj9UwtAnE89l1Gmrt3EU0=
gopkg.in/d4l3k/messagediff.v1 v1.2.1/go.mod h1:EUzikiKadqXWcD1AzJLagx0j/BeeWGtn++04Xniyg44=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
gopkg.in/stretchr/testify.v1 v1.2.2 h1:yhQC6Uy5CqibAIlk1wlusa/MJ3iAN49/BsR/dCCKz3M=
gopkg.in/stretchr/testify.v1 v1.2.2/go.mod h1:QI5V/q6UbPmuhtm10CaFZxED9NreB8PnFYN9JcR6TxU=
gopkg.in/testfixtures.v2 v2.5.3 h1:P8gDACSLJGxutzBqbzvfiXYgmQ2s00LIr4uAvWBCPAg=
gopkg.in/testfixtures.v2 v2.5.3/go.mod h1:rGPtsOtPcZhs7AsHYf1WmufW1hEsM6DXdLrYz60nrQQ=
gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo=
gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q=
gopkg.in/jcmturner/goidentity.v3 v3.0.0/go.mod h1:oG2kH0IvSYNIu80dVAyu/yoefjq1mNfM5bm88whjWx4=
gopkg.in/jcmturner/gokrb5.v7 v7.5.0/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM=
gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8=
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a h1:LJwr7TCTghdatWv40WobzlKXc9c4s8oGa7QKJUtHhWA=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
src.techknowlogick.com/xgo v0.0.0-20190507142556-a5b29ecb0ff4 h1:DZKMg4qnT7UIyB5ZaC6ZqltF2K5KhA1oQ2PdxOLZ3jg=
src.techknowlogick.com/xgo v0.0.0-20190507142556-a5b29ecb0ff4/go.mod h1:Ood88figJtEukTnU20P1IrXhyAkbOIGi4YzmeHVtGH0=
src.techknowlogick.com/xormigrate v0.0.0-20190321151057-24497c23c09c h1:fTwL7EZ3ouk3xeiPiRBYEjSPWTREb9T57bjzpRBNOpQ=
src.techknowlogick.com/xormigrate v0.0.0-20190321151057-24497c23c09c/go.mod h1:B2NutmcRaDDw4EGe7DoCwyWCELA8W+KxXPhLtgqFUaU=
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
src.techknowlogick.com/xgo v0.0.0-20200602060627-a09175ea9056 h1:9XYMVs36yGHbSNs+m7kHg5lQnTOie2bMLM1c8LtAoXs=
src.techknowlogick.com/xgo v0.0.0-20200602060627-a09175ea9056/go.mod h1:31CE1YKtDOrKTk9PSnjTpe6YbO6W/0LTYZ1VskL09oU=
src.techknowlogick.com/xormigrate v1.2.1 h1:HMtGqV5QN5zvYU244jzFCJ27rAljkMn/jsgp+H4S6WA=
src.techknowlogick.com/xormigrate v1.2.1/go.mod h1:PhU3iInlbFSev3M4ZnmAeWy9ZHUVDbz3U1X9Sg9S8xQ=
src.techknowlogick.com/xormigrate v1.3.0 h1:cCOEk8CjhRqxMtE8X8aV0qd6b2njwU1c3++ZuyaTsA0=
src.techknowlogick.com/xormigrate v1.3.0/go.mod h1:lmB01Zzm8lkvGE+qvlZoaIl39c8iEvHtYMtSFiHg250=
xorm.io/builder v0.3.7 h1:2pETdKRK+2QG4mLX4oODHEhn5Z8j1m8sXa7jfu+/SZI=
xorm.io/builder v0.3.7/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
xorm.io/core v0.7.3 h1:W8ws1PlrnkS1CZU1YWaYLMQcQilwAmQXU0BJDJon+H0=
xorm.io/core v0.7.3/go.mod h1:jJfd0UAEzZ4t87nbQYtVjmqpIODugN6PD2D9E+dJvdM=
xorm.io/xorm v1.0.1 h1:/lITxpJtkZauNpdzj+L9CN/3OQxZaABrbergMcJu+Cw=
xorm.io/xorm v1.0.1/go.mod h1:o4vnEsQ5V2F1/WK6w4XTwmiWJeGj82tqjAnHe44wVHY=
xorm.io/xorm v1.0.2 h1:kZlCh9rqd1AzGwWitcrEEqHE1h1eaZE/ujU5/2tWEtg=
xorm.io/xorm v1.0.2/go.mod h1:o4vnEsQ5V2F1/WK6w4XTwmiWJeGj82tqjAnHe44wVHY=

24
main.go
View File

@ -1,18 +1,18 @@
// Vikunja is a todo-list application to facilitate your life.
// Copyright 2018 Vikunja and contributors. All rights reserved.
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// 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 General Public License for more details.
// 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package main

View File

@ -1,23 +1,24 @@
// Vikunja is a todo-list application to facilitate your life.
// Copyright 2018 Vikunja and contributors. All rights reserved.
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// 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 General Public License for more details.
// 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package caldav
import (
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/timeutil"
"code.vikunja.io/api/pkg/user"
"code.vikunja.io/api/pkg/utils"
"fmt"
"strconv"
@ -34,37 +35,37 @@ type Event struct {
UID string
Alarms []Alarm
TimestampUnix int64
StartUnix int64
EndUnix int64
Timestamp timeutil.TimeStamp
Start timeutil.TimeStamp
End timeutil.TimeStamp
}
// Todo holds a single VTODO
type Todo struct {
// Required
TimestampUnix int64
UID string
Timestamp timeutil.TimeStamp
UID string
// Optional
Summary string
Description string
CompletedUnix int64
Organizer *models.User
Priority int64 // 0-9, 1 is highest
RelatedToUID string
Summary string
Description string
Completed timeutil.TimeStamp
Organizer *user.User
Priority int64 // 0-9, 1 is highest
RelatedToUID string
StartUnix int64
EndUnix int64
DueDateUnix int64
Duration time.Duration
Start timeutil.TimeStamp
End timeutil.TimeStamp
DueDate timeutil.TimeStamp
Duration time.Duration
CreatedUnix int64
UpdatedUnix int64 // last-mod
Created timeutil.TimeStamp
Updated timeutil.TimeStamp // last-mod
}
// Alarm holds infos about an alarm from a caldav event
type Alarm struct {
TimeUnix int64
Time timeutil.TimeStamp
Description string
}
@ -86,7 +87,7 @@ PRODID:-//` + config.ProdID + `//EN`
for _, e := range events {
if e.UID == "" {
e.UID = makeCalDavTimeFromUnixTime(e.TimestampUnix) + utils.Sha256(e.Summary)
e.UID = makeCalDavTimeFromTimeStamp(e.Timestamp) + utils.Sha256(e.Summary)
}
caldavevents += `
@ -94,9 +95,9 @@ BEGIN:VEVENT
UID:` + e.UID + `
SUMMARY:` + e.Summary + `
DESCRIPTION:` + e.Description + `
DTSTAMP:` + makeCalDavTimeFromUnixTime(e.TimestampUnix) + `
DTSTART:` + makeCalDavTimeFromUnixTime(e.StartUnix) + `
DTEND:` + makeCalDavTimeFromUnixTime(e.EndUnix)
DTSTAMP:` + makeCalDavTimeFromTimeStamp(e.Timestamp) + `
DTSTART:` + makeCalDavTimeFromTimeStamp(e.Start) + `
DTEND:` + makeCalDavTimeFromTimeStamp(e.End)
for _, a := range e.Alarms {
if a.Description == "" {
@ -105,7 +106,7 @@ DTEND:` + makeCalDavTimeFromUnixTime(e.EndUnix)
caldavevents += `
BEGIN:VALARM
TRIGGER:` + calcAlarmDateFromReminder(e.StartUnix, a.TimeUnix) + `
TRIGGER:` + calcAlarmDateFromReminder(e.Start, a.Time) + `
ACTION:DISPLAY
DESCRIPTION:` + a.Description + `
END:VALARM`
@ -131,30 +132,30 @@ PRODID:-//` + config.ProdID + `//EN`
for _, t := range todos {
if t.UID == "" {
t.UID = makeCalDavTimeFromUnixTime(t.TimestampUnix) + utils.Sha256(t.Summary)
t.UID = makeCalDavTimeFromTimeStamp(t.Timestamp) + utils.Sha256(t.Summary)
}
caldavtodos += `
BEGIN:VTODO
UID:` + t.UID + `
DTSTAMP:` + makeCalDavTimeFromUnixTime(t.TimestampUnix) + `
DTSTAMP:` + makeCalDavTimeFromTimeStamp(t.Timestamp) + `
SUMMARY:` + t.Summary
if t.StartUnix != 0 {
if t.Start != 0 {
caldavtodos += `
DTSTART: ` + makeCalDavTimeFromUnixTime(t.StartUnix)
DTSTART: ` + makeCalDavTimeFromTimeStamp(t.Start)
}
if t.EndUnix != 0 {
if t.End != 0 {
caldavtodos += `
DTEND: ` + makeCalDavTimeFromUnixTime(t.EndUnix)
DTEND: ` + makeCalDavTimeFromTimeStamp(t.End)
}
if t.Description != "" {
caldavtodos += `
DESCRIPTION:` + t.Description
}
if t.CompletedUnix != 0 {
if t.Completed != 0 {
caldavtodos += `
COMPLETED: ` + makeCalDavTimeFromUnixTime(t.CompletedUnix)
COMPLETED: ` + makeCalDavTimeFromTimeStamp(t.Completed)
}
if t.Organizer != nil {
caldavtodos += `
@ -166,14 +167,14 @@ ORGANIZER;CN=:` + t.Organizer.Username
RELATED-TO:` + t.RelatedToUID
}
if t.DueDateUnix != 0 {
if t.DueDate != 0 {
caldavtodos += `
DUE:` + makeCalDavTimeFromUnixTime(t.DueDateUnix)
DUE:` + makeCalDavTimeFromTimeStamp(t.DueDate)
}
if t.CreatedUnix != 0 {
if t.Created != 0 {
caldavtodos += `
CREATED:` + makeCalDavTimeFromUnixTime(t.CreatedUnix)
CREATED:` + makeCalDavTimeFromTimeStamp(t.Created)
}
if t.Duration != 0 {
@ -187,7 +188,7 @@ PRIORITY:` + strconv.Itoa(int(t.Priority))
}
caldavtodos += `
LAST-MODIFIED:` + makeCalDavTimeFromUnixTime(t.UpdatedUnix)
LAST-MODIFIED:` + makeCalDavTimeFromTimeStamp(t.Updated)
caldavtodos += `
END:VTODO`
@ -199,13 +200,12 @@ END:VCALENDAR` // Need a line break
return
}
func makeCalDavTimeFromUnixTime(unixtime int64) (caldavtime string) {
func makeCalDavTimeFromTimeStamp(ts timeutil.TimeStamp) (caldavtime string) {
tz, _ := time.LoadLocation("UTC")
tm := time.Unix(unixtime, 0).In(tz)
return tm.Format(DateFormat)
return ts.ToTime().In(tz).Format(DateFormat)
}
func calcAlarmDateFromReminder(eventStartUnix, reminderUnix int64) (alarmTime string) {
func calcAlarmDateFromReminder(eventStartUnix, reminderUnix timeutil.TimeStamp) (alarmTime string) {
if eventStartUnix > reminderUnix {
alarmTime += `-`
}

View File

@ -1,18 +1,18 @@
// Vikunja is a todo-list application to facilitate your life.
// Copyright 2018 Vikunja and contributors. All rights reserved.
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// 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 General Public License for more details.
// 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package caldav
@ -37,26 +37,26 @@ func TestParseEvents(t *testing.T) {
},
events: []*Event{
{
Summary: "Event #1",
Description: "Lorem Ipsum",
UID: "randommduid",
TimestampUnix: 1543626724,
StartUnix: 1543626724,
EndUnix: 1543627824,
Summary: "Event #1",
Description: "Lorem Ipsum",
UID: "randommduid",
Timestamp: 1543626724,
Start: 1543626724,
End: 1543627824,
},
{
Summary: "Event #2",
UID: "randommduidd",
TimestampUnix: 1543726724,
StartUnix: 1543726724,
EndUnix: 1543738724,
Summary: "Event #2",
UID: "randommduidd",
Timestamp: 1543726724,
Start: 1543726724,
End: 1543738724,
},
{
Summary: "Event #3 with empty uid",
UID: "20181202T0600242aaef4a81d770c1e775e26bc5abebc87f1d3d7bffaa83",
TimestampUnix: 1543726824,
StartUnix: 1543726824,
EndUnix: 1543727000,
Summary: "Event #3 with empty uid",
UID: "20181202T0600242aaef4a81d770c1e775e26bc5abebc87f1d3d7bffaa83",
Timestamp: 1543726824,
Start: 1543726824,
End: 1543727000,
},
},
},
@ -101,47 +101,47 @@ END:VCALENDAR`,
},
events: []*Event{
{
Summary: "Event #1",
Description: "Lorem Ipsum",
UID: "randommduid",
TimestampUnix: 1543626724,
StartUnix: 1543626724,
EndUnix: 1543627824,
Summary: "Event #1",
Description: "Lorem Ipsum",
UID: "randommduid",
Timestamp: 1543626724,
Start: 1543626724,
End: 1543627824,
Alarms: []Alarm{
{TimeUnix: 1543626524},
{TimeUnix: 1543626224},
{TimeUnix: 1543626024},
{Time: 1543626524},
{Time: 1543626224},
{Time: 1543626024},
},
},
{
Summary: "Event #2",
UID: "randommduidd",
TimestampUnix: 1543726724,
StartUnix: 1543726724,
EndUnix: 1543738724,
Summary: "Event #2",
UID: "randommduidd",
Timestamp: 1543726724,
Start: 1543726724,
End: 1543738724,
Alarms: []Alarm{
{TimeUnix: 1543626524},
{TimeUnix: 1543626224},
{TimeUnix: 1543626024},
{Time: 1543626524},
{Time: 1543626224},
{Time: 1543626024},
},
},
{
Summary: "Event #3 with empty uid",
TimestampUnix: 1543726824,
StartUnix: 1543726824,
EndUnix: 1543727000,
Summary: "Event #3 with empty uid",
Timestamp: 1543726824,
Start: 1543726824,
End: 1543727000,
Alarms: []Alarm{
{TimeUnix: 1543626524},
{TimeUnix: 1543626224},
{TimeUnix: 1543626024},
{TimeUnix: 1543826824},
{Time: 1543626524},
{Time: 1543626224},
{Time: 1543626024},
{Time: 1543826824},
},
},
{
Summary: "Event #4 without any",
TimestampUnix: 1543726824,
StartUnix: 1543726824,
EndUnix: 1543727000,
Summary: "Event #4 without any",
Timestamp: 1543726824,
Start: 1543726824,
End: 1543727000,
},
},
},

View File

@ -1,40 +1,27 @@
// Vikunja is a todo-list application to facilitate your life.
// Copyright 2019 Vikunja and contributors. All rights reserved.
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// 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 General Public License for more details.
// 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package cmd
import (
"code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/mail"
"code.vikunja.io/api/pkg/migration"
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/red"
"fmt"
"github.com/spf13/cobra"
"os"
)
// Version sets the version to be printed to the user. Gets overwritten by "make release" or "make build" with last git commit or tag.
var Version = "0.1"
func init() {
cobra.OnInitialize(initialize)
}
var rootCmd = &cobra.Command{
Use: "vikunja",
Short: "Vikunja is the to-do app to organize your life.",
@ -47,7 +34,8 @@ alpine areas of the Andes and a relative of the llama.
Vikunja is a self-hosted To-Do list application with a web app and mobile apps for all platforms. It is licensed under the GPLv3.
Find more info at vikunja.io.`,
Run: webCmd.Run,
PreRun: webCmd.PreRun,
Run: webCmd.Run,
}
// Execute starts the application
@ -57,27 +45,3 @@ func Execute() {
os.Exit(1)
}
}
// Initializes all kinds of things in the right order
func initialize() {
// Init the config
config.InitConfig()
// Init redis
red.InitRedis()
// Set logger
log.InitLogger()
// Run the migrations
migration.Migrate(nil)
// Set Engine
err := models.SetEngine()
if err != nil {
log.Log.Fatal(err.Error())
}
// Start the mail daemon
mail.StartMailDaemon()
}

43
pkg/cmd/dump.go Normal file
View File

@ -0,0 +1,43 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package cmd
import (
"code.vikunja.io/api/pkg/initialize"
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/modules/dump"
"github.com/spf13/cobra"
"time"
)
func init() {
rootCmd.AddCommand(dumpCmd)
}
var dumpCmd = &cobra.Command{
Use: "dump",
Short: "Dump all vikunja data into a zip file. Includes config, files and db.",
PreRun: func(cmd *cobra.Command, args []string) {
initialize.FullInit()
},
Run: func(cmd *cobra.Command, args []string) {
filename := "vikunja-dump_" + time.Now().Format("2006-01-02_15-03-05") + ".zip"
if err := dump.Dump(filename); err != nil {
log.Critical(err.Error())
}
},
}

View File

@ -1,22 +1,23 @@
// Vikunja is a todo-list application to facilitate your life.
// Copyright 2019 Vikunja and contributors. All rights reserved.
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// 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 General Public License for more details.
// 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package cmd
import (
"code.vikunja.io/api/pkg/initialize"
"code.vikunja.io/api/pkg/migration"
"github.com/spf13/cobra"
)
@ -24,7 +25,7 @@ import (
func init() {
migrateCmd.AddCommand(migrateListCmd)
migrationRollbackCmd.Flags().StringVarP(&rollbackUntilFlag, "name", "n", "", "The id of the migration you want to roll back until.")
migrationRollbackCmd.MarkFlagRequired("name")
_ = migrationRollbackCmd.MarkFlagRequired("name")
migrateCmd.AddCommand(migrationRollbackCmd)
rootCmd.AddCommand(migrateCmd)
}
@ -35,6 +36,9 @@ func init() {
var migrateCmd = &cobra.Command{
Use: "migrate",
Short: "Run all database migrations which didn't already run.",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
initialize.LightInit()
},
Run: func(cmd *cobra.Command, args []string) {
migration.Migrate(nil)
},

42
pkg/cmd/restore.go Normal file
View File

@ -0,0 +1,42 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package cmd
import (
"code.vikunja.io/api/pkg/initialize"
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/modules/dump"
"github.com/spf13/cobra"
)
func init() {
rootCmd.AddCommand(restoreCmd)
}
var restoreCmd = &cobra.Command{
Use: "restore [filename]",
Short: "Restores all vikunja data from a vikunja dump.",
Args: cobra.ExactArgs(1),
PreRun: func(cmd *cobra.Command, args []string) {
initialize.FullInit()
},
Run: func(cmd *cobra.Command, args []string) {
if err := dump.Restore(args[0]); err != nil {
log.Critical(err.Error())
}
},
}

49
pkg/cmd/testmail.go Normal file
View File

@ -0,0 +1,49 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package cmd
import (
"code.vikunja.io/api/pkg/initialize"
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/mail"
"github.com/spf13/cobra"
)
func init() {
rootCmd.AddCommand(testmailCmd)
}
var testmailCmd = &cobra.Command{
Use: "testmail [email]",
Short: "Send a test mail using the configured smtp connection",
Args: cobra.ExactArgs(1),
PreRun: func(cmd *cobra.Command, args []string) {
initialize.LightInit()
// Start the mail daemon
mail.StartMailDaemon()
},
Run: func(cmd *cobra.Command, args []string) {
log.Info("Sending testmail...")
email := args[0]
if err := mail.SendTestMail(email); err != nil {
log.Errorf("Error sending test mail: %s", err.Error())
return
}
log.Info("Testmail successfully sent.")
},
}

View File

@ -1,22 +1,23 @@
// Vikunja is a todo-list application to facilitate your life.
// Copyright 2019 Vikunja and contributors. All rights reserved.
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// 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 General Public License for more details.
// 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package cmd
import (
"code.vikunja.io/api/pkg/version"
"fmt"
"github.com/spf13/cobra"
)
@ -29,6 +30,6 @@ var versionCmd = &cobra.Command{
Use: "version",
Short: "Print the version number of Vikunja",
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Vikunja api version " + Version)
fmt.Printf("Vikunja api version %s\n", version.Version)
},
}

View File

@ -1,29 +1,30 @@
// Vikunja is a todo-list application to facilitate your life.
// Copyright 2019 Vikunja and contributors. All rights reserved.
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// 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 General Public License for more details.
// 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package cmd
import (
"code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/initialize"
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/routes"
"code.vikunja.io/api/pkg/swagger"
"code.vikunja.io/api/pkg/version"
"context"
"fmt"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"os"
"os/signal"
"time"
@ -36,20 +37,23 @@ func init() {
var webCmd = &cobra.Command{
Use: "web",
Short: "Starts the rest api web server",
PreRun: func(cmd *cobra.Command, args []string) {
initialize.FullInit()
},
Run: func(cmd *cobra.Command, args []string) {
// Version notification
fmt.Printf("Vikunja version %s\n", Version)
log.Infof("Vikunja version %s", version.Version)
// Additional swagger information
swagger.SwaggerInfo.Version = Version
swagger.SwaggerInfo.Version = version.Version
// Start the webserver
e := routes.NewEcho()
routes.RegisterRoutes(e)
// Start server
go func() {
if err := e.Start(viper.GetString("service.interface")); err != nil {
if err := e.Start(config.ServiceInterface.GetString()); err != nil {
e.Logger.Info("shutting down...")
}
}()
@ -61,7 +65,7 @@ var webCmd = &cobra.Command{
<-quit
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
log.Log.Infof("Shutting down...")
log.Infof("Shutting down...")
if err := e.Shutdown(ctx); err != nil {
e.Logger.Fatal(err)
}

View File

@ -1,93 +1,268 @@
// Vikunja is a todo-list application to facilitate your life.
// Copyright 2018 Vikunja and contributors. All rights reserved.
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// 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 General Public License for more details.
// 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package config
import (
"code.vikunja.io/api/pkg/log"
"crypto/rand"
"fmt"
"github.com/spf13/viper"
"log"
"os"
"path"
"path/filepath"
"strings"
"time"
"github.com/spf13/viper"
)
// InitConfig initializes the config, sets defaults etc.
func InitConfig() {
// Key is used as a config key
type Key string
// Set defaults
// These constants hold all config value keys
const (
// #nosec
ServiceJWTSecret Key = `service.JWTSecret`
ServiceInterface Key = `service.interface`
ServiceFrontendurl Key = `service.frontendurl`
ServiceEnableCaldav Key = `service.enablecaldav`
ServiceRootpath Key = `service.rootpath`
ServiceMaxItemsPerPage Key = `service.maxitemsperpage`
ServiceEnableMetrics Key = `service.enablemetrics`
ServiceMotd Key = `service.motd`
ServiceEnableLinkSharing Key = `service.enablelinksharing`
ServiceEnableRegistration Key = `service.enableregistration`
ServiceEnableTaskAttachments Key = `service.enabletaskattachments`
ServiceTimeZone Key = `service.timezone`
ServiceEnableTaskComments Key = `service.enabletaskcomments`
ServiceEnableTotp Key = `service.enabletotp`
ServiceSentryDsn Key = `service.sentrydsn`
DatabaseType Key = `database.type`
DatabaseHost Key = `database.host`
DatabaseUser Key = `database.user`
DatabasePassword Key = `database.password`
DatabaseDatabase Key = `database.database`
DatabasePath Key = `database.path`
DatabaseMaxOpenConnections Key = `database.maxopenconnections`
DatabaseMaxIdleConnections Key = `database.maxidleconnections`
DatabaseMaxConnectionLifetime Key = `database.maxconnectionlifetime`
DatabaseSslMode Key = `database.sslmode`
CacheEnabled Key = `cache.enabled`
CacheType Key = `cache.type`
CacheMaxElementSize Key = `cache.maxelementsize`
MailerEnabled Key = `mailer.enabled`
MailerHost Key = `mailer.host`
MailerPort Key = `mailer.port`
MailerUsername Key = `mailer.username`
MailerPassword Key = `mailer.password`
MailerSkipTLSVerify Key = `mailer.skiptlsverify`
MailerFromEmail Key = `mailer.fromemail`
MailerQueuelength Key = `mailer.queuelength`
MailerQueueTimeout Key = `mailer.queuetimeout`
RedisEnabled Key = `redis.enabled`
RedisHost Key = `redis.host`
RedisPassword Key = `redis.password`
RedisDB Key = `redis.db`
LogEnabled Key = `log.enabled`
LogStandard Key = `log.standard`
LogLevel Key = `log.level`
LogDatabase Key = `log.database`
LogDatabaseLevel Key = `log.databaselevel`
LogHTTP Key = `log.http`
LogEcho Key = `log.echo`
LogPath Key = `log.path`
RateLimitEnabled Key = `ratelimit.enabled`
RateLimitKind Key = `ratelimit.kind`
RateLimitPeriod Key = `ratelimit.period`
RateLimitLimit Key = `ratelimit.limit`
RateLimitStore Key = `ratelimit.store`
FilesBasePath Key = `files.basepath`
FilesMaxSize Key = `files.maxsize`
MigrationWunderlistEnable Key = `migration.wunderlist.enable`
MigrationWunderlistClientID Key = `migration.wunderlist.clientid`
MigrationWunderlistClientSecret Key = `migration.wunderlist.clientsecret`
MigrationWunderlistRedirectURL Key = `migration.wunderlist.redirecturl`
MigrationTodoistEnable Key = `migration.todoist.enable`
MigrationTodoistClientID Key = `migration.todoist.clientid`
MigrationTodoistClientSecret Key = `migration.todoist.clientsecret`
MigrationTodoistRedirectURL Key = `migration.todoist.redirecturl`
CorsEnable Key = `cors.enable`
CorsOrigins Key = `cors.origins`
CorsMaxAge Key = `cors.maxage`
AvatarProvider Key = `avatar.provider`
AvatarGravaterExpiration Key = `avatar.gravatarexpiration`
BackgroundsEnabled Key = `backgrounds.enabled`
BackgroundsUploadEnabled Key = `backgrounds.providers.upload.enabled`
BackgroundsUnsplashEnabled Key = `backgrounds.providers.unsplash.enabled`
BackgroundsUnsplashAccessToken Key = `backgrounds.providers.unsplash.accesstoken`
BackgroundsUnsplashApplicationID Key = `backgrounds.providers.unsplash.applicationid`
)
// GetString returns a string config value
func (k Key) GetString() string {
return viper.GetString(string(k))
}
// GetBool returns a bool config value
func (k Key) GetBool() bool {
return viper.GetBool(string(k))
}
// GetInt returns an int config value
func (k Key) GetInt() int {
return viper.GetInt(string(k))
}
// GetInt64 returns an int64 config value
func (k Key) GetInt64() int64 {
return viper.GetInt64(string(k))
}
// GetDuration returns a duration config value
func (k Key) GetDuration() time.Duration {
return viper.GetDuration(string(k))
}
// GetStringSlice returns a string slice from a config option
func (k Key) GetStringSlice() []string {
return viper.GetStringSlice(string(k))
}
// Set sets a value
func (k Key) Set(i interface{}) {
viper.Set(string(k), i)
}
// sets the default config value
func (k Key) setDefault(i interface{}) {
viper.SetDefault(string(k), i)
}
// InitDefaultConfig sets default config values
// This is an extra function so we can call it when initializing tests without initializing the full config
func InitDefaultConfig() {
// Service config
random, err := random(32)
if err != nil {
log.Log.Fatal(err.Error())
log.Fatal(err.Error())
}
// Service
viper.SetDefault("service.JWTSecret", random)
viper.SetDefault("service.interface", ":3456")
viper.SetDefault("service.frontendurl", "")
viper.SetDefault("service.enablecaldav", true)
ServiceJWTSecret.setDefault(random)
ServiceInterface.setDefault(":3456")
ServiceFrontendurl.setDefault("")
ServiceEnableCaldav.setDefault(true)
ex, err := os.Executable()
if err != nil {
panic(err)
}
exPath := filepath.Dir(ex)
viper.SetDefault("service.rootpath", exPath)
viper.SetDefault("service.pagecount", 50)
viper.SetDefault("service.enablemetrics", false)
ServiceRootpath.setDefault(exPath)
ServiceMaxItemsPerPage.setDefault(50)
ServiceEnableMetrics.setDefault(false)
ServiceMotd.setDefault("")
ServiceEnableLinkSharing.setDefault(true)
ServiceEnableRegistration.setDefault(true)
ServiceEnableTaskAttachments.setDefault(true)
ServiceTimeZone.setDefault("GMT")
ServiceEnableTaskComments.setDefault(true)
ServiceEnableTotp.setDefault(true)
// Database
viper.SetDefault("database.type", "sqlite")
viper.SetDefault("database.host", "localhost")
viper.SetDefault("database.user", "vikunja")
viper.SetDefault("database.password", "")
viper.SetDefault("database.database", "vikunja")
viper.SetDefault("database.path", "./vikunja.db")
viper.SetDefault("database.maxopenconnections", 100)
viper.SetDefault("database.maxidleconnections", 50)
viper.SetDefault("database.maxconnectionlifetime", 10000)
DatabaseType.setDefault("sqlite")
DatabaseHost.setDefault("localhost")
DatabaseUser.setDefault("vikunja")
DatabasePassword.setDefault("")
DatabaseDatabase.setDefault("vikunja")
DatabasePath.setDefault("./vikunja.db")
DatabaseMaxOpenConnections.setDefault(100)
DatabaseMaxIdleConnections.setDefault(50)
DatabaseMaxConnectionLifetime.setDefault(10000)
DatabaseSslMode.setDefault("disable")
// Cacher
viper.SetDefault("cache.enabled", false)
viper.SetDefault("cache.type", "memory")
viper.SetDefault("cache.maxelementsize", 1000)
CacheEnabled.setDefault(false)
CacheType.setDefault("memory")
CacheMaxElementSize.setDefault(1000)
// Mailer
viper.SetDefault("mailer.enabled", false)
viper.SetDefault("mailer.host", "")
viper.SetDefault("mailer.port", "587")
viper.SetDefault("mailer.user", "user")
viper.SetDefault("mailer.password", "")
viper.SetDefault("mailer.skiptlsverify", false)
viper.SetDefault("mailer.fromemail", "mail@vikunja")
viper.SetDefault("mailer.queuelength", 100)
viper.SetDefault("mailer.queuetimeout", 30)
MailerEnabled.setDefault(false)
MailerHost.setDefault("")
MailerPort.setDefault("587")
MailerUsername.setDefault("user")
MailerPassword.setDefault("")
MailerSkipTLSVerify.setDefault(false)
MailerFromEmail.setDefault("mail@vikunja")
MailerQueuelength.setDefault(100)
MailerQueueTimeout.setDefault(30)
// Redis
viper.SetDefault("redis.enabled", false)
viper.SetDefault("redis.host", "localhost:6379")
viper.SetDefault("redis.password", "")
viper.SetDefault("redis.db", 0)
RedisEnabled.setDefault(false)
RedisHost.setDefault("localhost:6379")
RedisPassword.setDefault("")
RedisDB.setDefault(0)
// Logger
viper.SetDefault("log.enabled", true)
viper.SetDefault("log.errors", "stdout")
viper.SetDefault("log.standard", "stdout")
viper.SetDefault("log.database", "off")
viper.SetDefault("log.http", "stdout")
viper.SetDefault("log.echo", "off")
viper.SetDefault("log.path", viper.GetString("service.rootpath")+"/logs")
LogEnabled.setDefault(true)
LogStandard.setDefault("stdout")
LogLevel.setDefault("INFO")
LogDatabase.setDefault("off")
LogDatabaseLevel.setDefault("WARNING")
LogHTTP.setDefault("stdout")
LogEcho.setDefault("off")
LogPath.setDefault(ServiceRootpath.GetString() + "/logs")
// Rate Limit
RateLimitEnabled.setDefault(false)
RateLimitKind.setDefault("user")
RateLimitLimit.setDefault(100)
RateLimitPeriod.setDefault(60)
RateLimitStore.setDefault("memory")
// Files
FilesBasePath.setDefault("files")
FilesMaxSize.setDefault("20MB")
// Cors
CorsEnable.setDefault(true)
CorsOrigins.setDefault([]string{"*"})
CorsMaxAge.setDefault(0)
// Migration
MigrationWunderlistEnable.setDefault(false)
MigrationTodoistEnable.setDefault(false)
// Avatar
AvatarProvider.setDefault("gravatar")
AvatarGravaterExpiration.setDefault(3600)
// List Backgrounds
BackgroundsEnabled.setDefault(false)
BackgroundsUploadEnabled.setDefault(false)
BackgroundsUnsplashEnabled.setDefault(false)
}
// InitConfig initializes the config, sets defaults etc.
func InitConfig() {
// Set defaults
InitDefaultConfig()
// Init checking for environment variables
viper.SetEnvPrefix("vikunja")
@ -95,16 +270,26 @@ func InitConfig() {
viper.AutomaticEnv()
// Load the config file
viper.AddConfigPath(viper.GetString("service.rootpath"))
viper.AddConfigPath(ServiceRootpath.GetString())
viper.AddConfigPath("/etc/vikunja/")
viper.AddConfigPath("~/.config/vikunja")
homeDir, err := os.UserHomeDir()
if err != nil {
log.Printf("No home directory found, not using config from ~/.config/vikunja/. Error was: %s\n", err.Error())
} else {
viper.AddConfigPath(path.Join(homeDir, ".config", "vikunja"))
}
viper.AddConfigPath(".")
viper.SetConfigName("config")
err = viper.ReadInConfig()
if err != nil {
log.Log.Info(err)
log.Log.Info("Using defaults.")
log.Println(err.Error())
log.Println("Using default config.")
return
}
log.Printf("Using config file: %s", viper.ConfigFileUsed())
}
func random(length int) (string, error) {

View File

@ -1,48 +1,66 @@
// Vikunja is a todo-list application to facilitate your life.
// Copyright 2019 Vikunja and contributors. All rights reserved.
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// 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 General Public License for more details.
// 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package db
import (
"code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/log"
"encoding/gob"
"fmt"
"github.com/go-xorm/core"
"github.com/go-xorm/xorm"
"github.com/spf13/viper"
xrc "gitea.com/xorm/xorm-redis-cache"
"net/url"
"strconv"
"strings"
"time"
"xorm.io/core"
"xorm.io/xorm"
"xorm.io/xorm/caches"
_ "github.com/go-sql-driver/mysql" // Because.
_ "github.com/lib/pq" // Because.
_ "github.com/mattn/go-sqlite3" // Because.
)
// We only want one instance of the engine, so we can reate it once and reuse it
var x *xorm.Engine
// CreateDBEngine initializes a db engine from the config
func CreateDBEngine() (engine *xorm.Engine, err error) {
if x != nil {
return x, nil
}
// If the database type is not set, this likely means we need to initialize the config first
if viper.GetString("database.type") == "" {
if config.DatabaseType.GetString() == "" {
config.InitConfig()
}
// Use Mysql if set
if viper.GetString("database.type") == "mysql" {
if config.DatabaseType.GetString() == "mysql" {
engine, err = initMysqlEngine()
if err != nil {
return
}
} else if config.DatabaseType.GetString() == "postgres" {
engine, err = initPostgresEngine()
if err != nil {
return
}
} else {
// Otherwise use sqlite
engine, err = initSqliteEngine()
@ -52,26 +70,89 @@ func CreateDBEngine() (engine *xorm.Engine, err error) {
}
engine.SetMapper(core.GonicMapper{})
engine.ShowSQL(viper.GetString("log.database") != "off")
engine.SetLogger(xorm.NewSimpleLogger(log.GetLogWriter("database")))
logger := log.NewXormLogger("")
engine.SetLogger(logger)
// Cache
// We have to initialize the cache here to avoid import cycles
if config.CacheEnabled.GetBool() {
switch config.CacheType.GetString() {
case "memory":
cacher := caches.NewLRUCacher(caches.NewMemoryStore(), config.CacheMaxElementSize.GetInt())
engine.SetDefaultCacher(cacher)
case "redis":
cacher := xrc.NewRedisCacher(config.RedisEnabled.GetString(), config.RedisPassword.GetString(), xrc.DEFAULT_EXPIRATION, engine.Logger())
engine.SetDefaultCacher(cacher)
default:
log.Info("Did not find a valid cache type. Caching disabled. Please refer to the docs for poosible cache types.")
}
}
x = engine
return
}
// RegisterTableStructsForCache registers tables in gob encoding for redis cache
func RegisterTableStructsForCache(val interface{}) {
gob.Register(val)
}
func initMysqlEngine() (engine *xorm.Engine, err error) {
// We're using utf8mb here instead of just utf8 because we want to use non-BMP characters.
// See https://stackoverflow.com/a/30074553/10924593 for more info.
connStr := fmt.Sprintf(
"%s:%s@tcp(%s)/%s?charset=utf8&parseTime=true",
viper.GetString("database.user"),
viper.GetString("database.password"),
viper.GetString("database.host"),
viper.GetString("database.database"))
"%s:%s@tcp(%s)/%s?charset=utf8mb4&parseTime=true",
config.DatabaseUser.GetString(),
config.DatabasePassword.GetString(),
config.DatabaseHost.GetString(),
config.DatabaseDatabase.GetString())
engine, err = xorm.NewEngine("mysql", connStr)
if err != nil {
return
}
engine.SetMaxOpenConns(viper.GetInt("database.maxopenconnections"))
engine.SetMaxIdleConns(viper.GetInt("database.maxidleconnections"))
max, err := time.ParseDuration(strconv.Itoa(viper.GetInt("database.maxconnectionlifetime")) + `ms`)
engine.SetMaxOpenConns(config.DatabaseMaxOpenConnections.GetInt())
engine.SetMaxIdleConns(config.DatabaseMaxIdleConnections.GetInt())
max, err := time.ParseDuration(strconv.Itoa(config.DatabaseMaxConnectionLifetime.GetInt()) + `ms`)
if err != nil {
return
}
engine.SetConnMaxLifetime(max)
return
}
// parsePostgreSQLHostPort parses given input in various forms defined in
// https://www.postgresql.org/docs/current/static/libpq-connect.html#LIBPQ-CONNSTRING
// and returns proper host and port number.
func parsePostgreSQLHostPort(info string) (string, string) {
host, port := "127.0.0.1", "5432"
if strings.Contains(info, ":") && !strings.HasSuffix(info, "]") {
idx := strings.LastIndex(info, ":")
host = info[:idx]
port = info[idx+1:]
} else if len(info) > 0 {
host = info
}
return host, port
}
func initPostgresEngine() (engine *xorm.Engine, err error) {
host, port := parsePostgreSQLHostPort(config.DatabaseHost.GetString())
connStr := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=%s",
host,
port,
url.PathEscape(config.DatabaseUser.GetString()),
url.PathEscape(config.DatabasePassword.GetString()),
config.DatabaseDatabase.GetString(),
config.DatabaseSslMode.GetString(),
)
engine, err = xorm.NewEngine("postgres", connStr)
if err != nil {
return
}
engine.SetMaxOpenConns(config.DatabaseMaxOpenConnections.GetInt())
engine.SetMaxIdleConns(config.DatabaseMaxIdleConnections.GetInt())
max, err := time.ParseDuration(strconv.Itoa(config.DatabaseMaxConnectionLifetime.GetInt()) + `ms`)
if err != nil {
return
}
@ -80,10 +161,27 @@ func initMysqlEngine() (engine *xorm.Engine, err error) {
}
func initSqliteEngine() (engine *xorm.Engine, err error) {
path := viper.GetString("database.path")
path := config.DatabasePath.GetString()
if path == "" {
path = "./db.db"
}
return xorm.NewEngine("sqlite3", path)
}
// WipeEverything wipes all tables and their data. Use with caution...
func WipeEverything() error {
tables, err := x.DBMetas()
if err != nil {
return err
}
for _, t := range tables {
if err := x.DropTables(t.Name); err != nil {
return err
}
}
return nil
}

54
pkg/db/dump.go Normal file
View File

@ -0,0 +1,54 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package db
import "encoding/json"
// Dump dumps all database tables
func Dump() (data map[string][]byte, err error) {
tables, err := x.DBMetas()
if err != nil {
return
}
data = make(map[string][]byte, len(tables))
for _, table := range tables {
entries := []map[string]interface{}{}
err := x.Table(table.Name).Find(&entries)
if err != nil {
return nil, err
}
data[table.Name], err = json.Marshal(entries)
if err != nil {
return nil, err
}
}
return
}
// Restore restores a table with all its entries
func Restore(table string, contents []map[string]interface{}) (err error) {
for _, content := range contents {
if _, err := x.Table(table).Insert(content); err != nil {
return err
}
}
return
}

207
pkg/db/fixtures/buckets.yml Normal file
View File

@ -0,0 +1,207 @@
- id: 1
title: testbucket1
list_id: 1
created_by_id: 1
created: 1587244432
updated: 1587244432
- id: 2
title: testbucket2
list_id: 1
created_by_id: 1
created: 1587244432
updated: 1587244432
- id: 3
title: testbucket3
list_id: 1
created_by_id: 1
created: 1587244432
updated: 1587244432
- id: 4
title: testbucket4 - other list
list_id: 2
created_by_id: 1
created: 1587244432
updated: 1587244432
# The following are not or only partly owned by user 1
- id: 5
title: testbucket5
list_id: 20
created_by_id: 1
created: 1587244432
updated: 1587244432
- id: 6
title: testbucket6
list_id: 6
created_by_id: 1
created: 1587244432
updated: 1587244432
- id: 7
title: testbucket7
list_id: 7
created_by_id: 1
created: 1587244432
updated: 1587244432
- id: 8
title: testbucket8
list_id: 8
created_by_id: 1
created: 1587244432
updated: 1587244432
- id: 9
title: testbucket9
list_id: 9
created_by_id: 1
created: 1587244432
updated: 1587244432
- id: 10
title: testbucket10
list_id: 10
created_by_id: 1
created: 1587244432
updated: 1587244432
- id: 11
title: testbucket11
list_id: 11
created_by_id: 1
created: 1587244432
updated: 1587244432
- id: 12
title: testbucket13
list_id: 12
created_by_id: 1
created: 1587244432
updated: 1587244432
- id: 13
title: testbucket13
list_id: 13
created_by_id: 1
created: 1587244432
updated: 1587244432
- id: 14
title: testbucket14
list_id: 14
created_by_id: 1
created: 1587244432
updated: 1587244432
- id: 15
title: testbucket15
list_id: 15
created_by_id: 1
created: 1587244432
updated: 1587244432
- id: 16
title: testbucket16
list_id: 16
created_by_id: 1
created: 1587244432
updated: 1587244432
- id: 17
title: testbucket17
list_id: 17
created_by_id: 1
created: 1587244432
updated: 1587244432
- id: 18
title: testbucket18
list_id: 5
created_by_id: 1
created: 1587244432
updated: 1587244432
- id: 19
title: testbucket19
list_id: 21
created_by_id: 1
created: 1587244432
updated: 1587244432
- id: 20
title: testbucket20
list_id: 22
created_by_id: 1
created: 1587244432
updated: 1587244432
- id: 21
title: testbucket21
list_id: 3
created_by_id: 1
created: 1587244432
updated: 1587244432
# Duplicate buckets to make deletion of one of them possible
- id: 22
title: testbucket22
list_id: 6
created_by_id: 1
created: 1587244432
updated: 1587244432
- id: 23
title: testbucket23
list_id: 7
created_by_id: 1
created: 1587244432
updated: 1587244432
- id: 24
title: testbucket24
list_id: 8
created_by_id: 1
created: 1587244432
updated: 1587244432
- id: 25
title: testbucket25
list_id: 9
created_by_id: 1
created: 1587244432
updated: 1587244432
- id: 26
title: testbucket26
list_id: 10
created_by_id: 1
created: 1587244432
updated: 1587244432
- id: 27
title: testbucket27
list_id: 11
created_by_id: 1
created: 1587244432
updated: 1587244432
- id: 28
title: testbucket28
list_id: 12
created_by_id: 1
created: 1587244432
updated: 1587244432
- id: 29
title: testbucket29
list_id: 13
created_by_id: 1
created: 1587244432
updated: 1587244432
- id: 30
title: testbucket30
list_id: 14
created_by_id: 1
created: 1587244432
updated: 1587244432
- id: 31
title: testbucket31
list_id: 15
created_by_id: 1
created: 1587244432
updated: 1587244432
- id: 32
title: testbucket32
list_id: 16
created_by_id: 1
created: 1587244432
updated: 1587244432
- id: 33
title: testbucket33
list_id: 17
created_by_id: 1
created: 1587244432
updated: 1587244432
# This bucket is the last one in its list
- id: 34
title: testbucket34
list_id: 18
created_by_id: 1
created: 1587244432
updated: 1587244432

View File

@ -0,0 +1,5 @@
- id: 1
name: test
size: 100
created_unix: 1570998791
created_by_id: 1

View File

@ -0,0 +1,16 @@
- id: 1
task_id: 1
label_id: 4
created: 0
- id: 2
task_id: 2
label_id: 4
created: 0
- id: 3
task_id: 35
label_id: 4
created: 0
- id: 4
task_id: 36
label_id: 4
created: 0

View File

@ -0,0 +1,24 @@
- id: 1
hash: test
list_id: 1
right: 0
sharing_type: 1
shared_by_id: 1
created: 0
updated: 0
- id: 2
hash: test2
list_id: 2
right: 1
sharing_type: 1
shared_by_id: 1
created: 0
updated: 0
- id: 3
hash: test3
list_id: 3
right: 2
sharing_type: 1
shared_by_id: 1
created: 0
updated: 0

View File

@ -2,6 +2,7 @@
id: 1
title: Test1
description: Lorem Ipsum
identifier: test1
owner_id: 1
namespace_id: 1
updated: 0
@ -10,6 +11,7 @@
id: 2
title: Test2
description: Lorem Ipsum
identifier: test2
owner_id: 3
namespace_id: 1
updated: 0
@ -18,6 +20,7 @@
id: 3
title: Test3
description: Lorem Ipsum
identifier: test3
owner_id: 3
namespace_id: 2
updated: 0
@ -26,6 +29,7 @@
id: 4
title: Test4
description: Lorem Ipsum
identifier: test4
owner_id: 3
namespace_id: 3
updated: 0
@ -34,6 +38,7 @@
id: 5
title: Test5
description: Lorem Ipsum
identifier: test5
owner_id: 5
namespace_id: 5
updated: 0
@ -42,6 +47,7 @@
id: 6
title: Test6
description: Lorem Ipsum
identifier: test6
owner_id: 6
namespace_id: 6
updated: 0
@ -50,6 +56,7 @@
id: 7
title: Test7
description: Lorem Ipsum
identifier: test7
owner_id: 6
namespace_id: 6
updated: 0
@ -58,6 +65,7 @@
id: 8
title: Test8
description: Lorem Ipsum
identifier: test8
owner_id: 6
namespace_id: 6
updated: 0
@ -66,6 +74,7 @@
id: 9
title: Test9
description: Lorem Ipsum
identifier: test9
owner_id: 6
namespace_id: 6
updated: 0
@ -74,6 +83,7 @@
id: 10
title: Test10
description: Lorem Ipsum
identifier: test10
owner_id: 6
namespace_id: 6
updated: 0
@ -82,6 +92,7 @@
id: 11
title: Test11
description: Lorem Ipsum
identifier: test11
owner_id: 6
namespace_id: 6
updated: 0
@ -90,6 +101,7 @@
id: 12
title: Test12
description: Lorem Ipsum
identifier: test12
owner_id: 6
namespace_id: 7
updated: 0
@ -98,6 +110,7 @@
id: 13
title: Test13
description: Lorem Ipsum
identifier: test13
owner_id: 6
namespace_id: 8
updated: 0
@ -106,6 +119,7 @@
id: 14
title: Test14
description: Lorem Ipsum
identifier: test14
owner_id: 6
namespace_id: 9
updated: 0
@ -114,6 +128,7 @@
id: 15
title: Test15
description: Lorem Ipsum
identifier: test15
owner_id: 6
namespace_id: 10
updated: 0
@ -122,6 +137,7 @@
id: 16
title: Test16
description: Lorem Ipsum
identifier: test16
owner_id: 6
namespace_id: 11
updated: 0
@ -130,7 +146,56 @@
id: 17
title: Test17
description: Lorem Ipsum
identifier: test17
owner_id: 6
namespace_id: 12
updated: 0
created: 0
# This list is owned by user 7, and several other users have access to it via different methods.
# It is used to test the listUsers method.
-
id: 18
title: Test18
description: Lorem Ipsum
identifier: test18
owner_id: 7
namespace_id: 13
updated: 0
created: 0
-
id: 19
title: Test19
description: Lorem Ipsum
identifier: test19
owner_id: 7
namespace_id: 14
updated: 0
created: 0
-
id: 20
title: Test20
description: Lorem Ipsum
identifier: test20
owner_id: 13
namespace_id: 15
updated: 0
created: 0
-
id: 21
title: Test21 archived through namespace
description: Lorem Ipsum
identifier: test21
owner_id: 1
namespace_id: 16
updated: 0
created: 0
-
id: 22
title: Test22 archived individually
description: Lorem Ipsum
identifier: test22
owner_id: 1
namespace_id: 1
is_archived: 1
updated: 0
created: 0

View File

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

View File

@ -0,0 +1,16 @@
- id: 1
task_id: 30
user_id: 1
created: 0
- id: 2
task_id: 30
user_id: 2
created: 0
- id: 3
task_id: 35
user_id: 2
created: 0
- id: 4
task_id: 36
user_id: 2
created: 0

View File

@ -0,0 +1,11 @@
- id: 1
task_id: 1
file_id: 1
created_by_id: 1
created: 0
# The file for this attachment does not exist
- id: 2
task_id: 1
file_id: 9999
created_by_id: 1
created: 0

View File

@ -0,0 +1,96 @@
- id: 1
comment: Lorem Ipsum Dolor Sit Amet
author_id: 1
task_id: 1
created: 1582135626
updated: 1582135626
- id: 2
comment: comment 2
author_id: 5
task_id: 14
created: 1582135626
updated: 1582135626
- id: 3
comment: comment 3
author_id: 5
task_id: 15
created: 1582135626
updated: 1582135626
- id: 4
comment: comment 4
author_id: 6
task_id: 16
created: 1582135626
updated: 1582135626
- id: 5
comment: comment 5
author_id: 6
task_id: 17
created: 1582135626
updated: 1582135626
- id: 6
comment: comment 6
author_id: 6
task_id: 18
created: 1582135626
updated: 1582135626
- id: 7
comment: comment 7
author_id: 6
task_id: 19
created: 1582135626
updated: 1582135626
- id: 8
comment: comment 8
author_id: 6
task_id: 20
created: 1582135626
updated: 1582135626
- id: 9
comment: comment 9
author_id: 6
task_id: 21
created: 1582135626
updated: 1582135626
- id: 10
comment: comment 10
author_id: 6
task_id: 22
created: 1582135626
updated: 1582135626
- id: 11
comment: comment 11
author_id: 6
task_id: 23
created: 1582135626
updated: 1582135626
- id: 12
comment: comment 12
author_id: 6
task_id: 24
created: 1582135626
updated: 1582135626
- id: 13
comment: comment 13
author_id: 6
task_id: 25
created: 1582135626
updated: 1582135626
- id: 14
comment: comment 14
author_id: 6
task_id: 26
created: 1582135626
updated: 1582135626
- id: 15
comment: comment 15
author_id: 1
task_id: 35
created: 1582135626
updated: 1582135626
- id: 16
comment: comment 16
author_id: 1
task_id: 36
created: 1582135626
updated: 1582135626

View File

@ -0,0 +1,36 @@
- id: 1
task_id: 1
other_task_id: 29
relation_kind: 'subtask'
created_by_id: 1
created: 0
- id: 2
task_id: 29
other_task_id: 1
relation_kind: 'parenttask'
created_by_id: 1
created: 0
- id: 3
task_id: 35
other_task_id: 1
relation_kind: 'related'
created_by_id: 1
created: 0
- id: 4
task_id: 35
other_task_id: 1
relation_kind: 'related'
created_by_id: 1
created: 0
- id: 5
task_id: 36
other_task_id: 1
relation_kind: 'related'
created_by_id: 1
created: 0
- id: 6
task_id: 36
other_task_id: 1
relation_kind: 'related'
created_by_id: 1
created: 0

View File

@ -1,201 +1,339 @@
- id: 1
text: 'task #1'
title: 'task #1'
description: 'Lorem Ipsum'
done: false
created_by_id: 1
list_id: 1
index: 1
created: 1543626724
updated: 1543626724
bucket_id: 1
- id: 2
text: 'task #2 done'
title: 'task #2 done'
done: true
created_by_id: 1
list_id: 1
index: 2
created: 1543626724
updated: 1543626724
bucket_id: 1
- id: 3
text: 'task #3 high prio'
title: 'task #3 high prio'
done: false
created_by_id: 1
list_id: 1
index: 3
created: 1543626724
updated: 1543626724
priority: 100
bucket_id: 2
- id: 4
text: 'task #4 low prio'
title: 'task #4 low prio'
done: false
created_by_id: 1
list_id: 1
index: 4
created: 1543626724
updated: 1543626724
priority: 1
bucket_id: 2
- id: 5
text: 'task #5 higher due date'
title: 'task #5 higher due date'
done: false
created_by_id: 1
list_id: 1
index: 5
created: 1543626724
updated: 1543626724
due_date_unix: 1543636724
bucket_id: 2
- id: 6
text: 'task #6 lower due date'
title: 'task #6 lower due date'
done: false
created_by_id: 1
list_id: 1
index: 6
created: 1543626724
updated: 1543626724
due_date_unix: 1543616724
bucket_id: 3
- id: 7
text: 'task #7 with start date'
title: 'task #7 with start date'
done: false
created_by_id: 1
list_id: 1
index: 7
created: 1543626724
updated: 1543626724
start_date_unix: 1544600000
bucket_id: 3
- id: 8
text: 'task #8 with end date'
title: 'task #8 with end date'
done: false
created_by_id: 1
list_id: 1
index: 8
created: 1543626724
updated: 1543626724
end_date_unix: 1544700000
bucket_id: 3
- id: 9
text: 'task #9 with start and end date'
title: 'task #9 with start and end date'
done: false
created_by_id: 1
list_id: 1
index: 9
created: 1543626724
updated: 1543626724
start_date_unix: 1544600000
end_date_unix: 1544700000
bucket_id: 1
- id: 10
text: 'task #10 basic'
title: 'task #10 basic'
done: false
created_by_id: 1
list_id: 1
index: 10
bucket_id: 1
created: 1543626724
updated: 1543626724
- id: 11
text: 'task #11 basic'
title: 'task #11 basic'
done: false
created_by_id: 1
list_id: 1
index: 11
bucket_id: 1
created: 1543626724
updated: 1543626724
- id: 12
text: 'task #12 basic'
title: 'task #12 basic'
done: false
created_by_id: 1
list_id: 1
index: 12
bucket_id: 1
created: 1543626724
updated: 1543626724
- id: 13
text: 'task #13 basic other list'
title: 'task #13 basic other list'
done: false
created_by_id: 1
list_id: 2
index: 1
bucket_id: 4
created: 1543626724
updated: 1543626724
- id: 14
text: 'task #14 basic other list'
title: 'task #14 basic other list'
done: false
created_by_id: 5
list_id: 5
index: 1
bucket_id: 18
created: 1543626724
updated: 1543626724
- id: 15
text: 'task #15'
title: 'task #15'
done: false
created_by_id: 6
list_id: 6
index: 1
bucket_id: 6
created: 1543626724
updated: 1543626724
- id: 16
text: 'task #16'
title: 'task #16'
done: false
created_by_id: 6
list_id: 7
index: 1
bucket_id: 7
created: 1543626724
updated: 1543626724
- id: 17
text: 'task #17'
title: 'task #17'
done: false
created_by_id: 6
list_id: 8
index: 1
bucket_id: 8
created: 1543626724
updated: 1543626724
- id: 18
text: 'task #18'
title: 'task #18'
done: false
created_by_id: 6
list_id: 9
index: 1
bucket_id: 9
created: 1543626724
updated: 1543626724
- id: 19
text: 'task #19'
title: 'task #19'
done: false
created_by_id: 6
list_id: 10
index: 1
bucket_id: 10
created: 1543626724
updated: 1543626724
- id: 20
text: 'task #20'
title: 'task #20'
done: false
created_by_id: 6
list_id: 11
index: 1
bucket_id: 11
created: 1543626724
updated: 1543626724
- id: 21
text: 'task #21'
title: 'task #21'
done: false
created_by_id: 6
list_id: 12
index: 1
bucket_id: 12
created: 1543626724
updated: 1543626724
- id: 22
text: 'task #22'
title: 'task #22'
done: false
created_by_id: 6
list_id: 13
index: 1
bucket_id: 13
created: 1543626724
updated: 1543626724
- id: 23
text: 'task #23'
title: 'task #23'
done: false
created_by_id: 6
list_id: 14
index: 1
bucket_id: 14
created: 1543626724
updated: 1543626724
- id: 24
text: 'task #24'
title: 'task #24'
done: false
created_by_id: 6
list_id: 15
index: 1
bucket_id: 15
created: 1543626724
updated: 1543626724
- id: 25
text: 'task #25'
title: 'task #25'
done: false
created_by_id: 6
list_id: 16
index: 1
bucket_id: 16
created: 1543626724
updated: 1543626724
- id: 26
text: 'task #26'
title: 'task #26'
done: false
created_by_id: 6
list_id: 17
index: 1
bucket_id: 17
created: 1543626724
updated: 1543626724
- id: 27
text: 'task #27 with reminders'
title: 'task #27 with reminders'
done: false
created_by_id: 1
list_id: 1
index: 12
bucket_id: 1
created: 1543626724
updated: 1543626724
- id: 28
text: 'task #28 with repeat after'
title: 'task #28 with repeat after'
done: false
created_by_id: 1
repeat_after: 3600
list_id: 1
index: 13
bucket_id: 1
created: 1543626724
updated: 1543626724
- id: 29
text: 'task #29 with parent task (1)'
title: 'task #29 with parent task (1)'
done: false
created_by_id: 1
parent_task_id: 1
list_id: 1
index: 14
bucket_id: 1
created: 1543626724
updated: 1543626724
- id: 30
text: 'task #30 with assignees'
title: 'task #30 with assignees'
done: false
created_by_id: 1
list_id: 1
index: 15
bucket_id: 1
created: 1543626724
updated: 1543626724
- id: 31
text: 'task #31 with color'
title: 'task #31 with color'
done: false
created_by_id: 1
list_id: 1
index: 16
hex_color: f0f0f0
bucket_id: 1
created: 1543626724
updated: 1543626724
- id: 32
title: 'task #32'
done: false
created_by_id: 1
list_id: 3
index: 1
bucket_id: 21
created: 1543626724
updated: 1543626724
- id: 33
title: 'task #33 with percent done'
done: false
created_by_id: 1
list_id: 1
index: 17
percent_done: 0.5
bucket_id: 1
created: 1543626724
updated: 1543626724
# This task is forbidden for user1
- id: 34
title: 'task #34'
done: false
created_by_id: 13
list_id: 20
index: 20
bucket_id: 5
created: 1543626724
updated: 1543626724
- id: 35
title: 'task #35'
done: false
created_by_id: 1
list_id: 21
index: 1
bucket_id: 19
created: 1543626724
updated: 1543626724
- id: 36
title: 'task #36'
done: false
created_by_id: 1
list_id: 22
index: 1
bucket_id: 20
created: 1543626724
updated: 1543626724

View File

@ -4,7 +4,6 @@
right: 0
updated: 0
created: 0
# This team has read only access on list 6
- id: 2
team_id: 2
@ -12,7 +11,6 @@
right: 0
updated: 0
created: 0
# This team has write access on list 7
- id: 3
team_id: 3
@ -20,11 +18,31 @@
right: 1
updated: 0
created: 0
# This team has admin access on list 8
- id: 4
team_id: 4
list_id: 8
right: 2
updated: 0
created: 0
# Readonly acces on list 19
- id: 5
team_id: 8
list_id: 19
right: 2
updated: 0
created: 0
# Write acces on list 19
- id: 6
team_id: 9
list_id: 19
right: 1
updated: 0
created: 0
# Admin acces on list 19
- id: 7
team_id: 10
list_id: 19
right: 2
updated: 0
created: 0

View File

@ -31,3 +31,27 @@
team_id: 7
user_id: 1
created: 0
-
team_id: 8
user_id: 1
created: 0
-
team_id: 9
user_id: 2
created: 0
-
team_id: 10
user_id: 3
created: 0
-
team_id: 11
user_id: 8
created: 0
-
team_id: 12
user_id: 9
created: 0
-
team_id: 13
user_id: 10
created: 0

View File

@ -32,3 +32,21 @@
right: 2
updated: 0
created: 0
- id: 6
team_id: 11
namespace_id: 14
right: 0
updated: 0
created: 0
- id: 7
team_id: 12
namespace_id: 14
right: 1
updated: 0
created: 0
- id: 8
team_id: 13
namespace_id: 14
right: 2
updated: 0
created: 0

View File

@ -19,4 +19,22 @@
created_by_id: 1
- id: 7
name: testteam4_admin_on_namespace9
created_by_id: 1
created_by_id: 1
- id: 8
name: testteam8
created_by_id: 7
- id: 9
name: testteam9
created_by_id: 7
- id: 10
name: testteam10
created_by_id: 7
- id: 11
name: testteam11
created_by_id: 7
- id: 12
name: testteam12
created_by_id: 7
- id: 13
name: testteam13
created_by_id: 7

View File

@ -46,3 +46,52 @@
is_active: true
updated: 0
created: 0
- id: 7
username: 'user7'
password: '$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.' # 1234
email: 'user7@example.com'
is_active: true
updated: 0
created: 0
- id: 8
username: 'user8'
password: '$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.' # 1234
email: 'user8@example.com'
is_active: true
updated: 0
created: 0
- id: 9
username: 'user9'
password: '$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.' # 1234
email: 'user9@example.com'
is_active: true
updated: 0
created: 0
- id: 10
username: 'user10'
password: '$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.' # 1234
email: 'user10@example.com'
is_active: true
updated: 0
created: 0
- id: 11
username: 'user11'
password: '$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.' # 1234
email: 'user11@example.com'
is_active: true
updated: 0
created: 0
- id: 12
username: 'user12'
password: '$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.' # 1234
email: 'user12@example.com'
is_active: true
updated: 0
created: 0
- id: 13
username: 'user13'
password: '$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.' # 1234
email: 'user14@example.com'
is_active: true
updated: 0
created: 0

View File

@ -4,31 +4,45 @@
right: 0
updated: 0
created: 0
- id: 2
user_id: 2
list_id: 3
right: 0
updated: 0
created: 0
- id: 3
user_id: 1
list_id: 9
right: 0
updated: 0
created: 0
- id: 4
user_id: 1
list_id: 10
right: 1
updated: 0
created: 0
- id: 5
user_id: 1
list_id: 11
right: 2
updated: 0
created: 0
- id: 6
user_id: 4
list_id: 19
right: 0
updated: 0
created: 0
- id: 7
user_id: 5
list_id: 19
right: 1
updated: 0
created: 0
- id: 8
user_id: 6
list_id: 19
right: 2
updated: 0
created: 0

View File

@ -32,3 +32,21 @@
right: 2
updated: 0
created: 0
- id: 6
user_id: 11
namespace_id: 14
right: 0
updated: 0
created: 0
- id: 7
user_id: 12
namespace_id: 14
right: 1
updated: 0
created: 0
- id: 8
user_id: 13
namespace_id: 14
right: 2
updated: 0
created: 0

70
pkg/db/test.go Normal file
View File

@ -0,0 +1,70 @@
// Copyright2018-2020 Vikunja and contriubtors. All rights reserved.
//
// This file is part of Vikunja.
//
// Vikunja is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Vikunja 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Vikunja. If not, see <https://www.gnu.org/licenses/>.
package db
import (
"code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/log"
"os"
"xorm.io/core"
"xorm.io/xorm"
)
// CreateTestEngine creates an instance of the db engine which lives in memory
func CreateTestEngine() (engine *xorm.Engine, err error) {
if x != nil {
return x, nil
}
if os.Getenv("VIKUNJA_TESTS_USE_CONFIG") == "1" {
config.InitConfig()
engine, err = CreateDBEngine()
if err != nil {
return nil, err
}
} else {
engine, err = xorm.NewEngine("sqlite3", "file::memory:?cache=shared")
if err != nil {
return nil, err
}
}
engine.SetMapper(core.GonicMapper{})
logger := log.NewXormLogger("DEBUG")
logger.ShowSQL(os.Getenv("UNIT_TESTS_VERBOSE") == "1")
engine.SetLogger(logger)
x = engine
return
}
// InitTestFixtures populates the db with all fixtures from the fixtures folder
func InitTestFixtures(tablenames ...string) (err error) {
// Create all fixtures
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"))
// Sync fixtures
err = InitFixtures(tablenames...)
if err != nil {
log.Fatal(err)
}
return nil
}

115
pkg/db/test_fixtures.go Normal file
View File

@ -0,0 +1,115 @@
// Copyright 2018-2020 Vikunja and contriubtors. All rights reserved.
//
// This file is part of Vikunja.
//
// Vikunja is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Vikunja 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Vikunja. If not, see <https://www.gnu.org/licenses/>.
package db
import (
"code.vikunja.io/api/pkg/config"
"fmt"
"github.com/go-testfixtures/testfixtures/v3"
"github.com/stretchr/testify/assert"
"path/filepath"
"testing"
"xorm.io/xorm/schemas"
)
var fixtures *testfixtures.Loader
// InitFixtures initialize test fixtures for a test database
func InitFixtures(tablenames ...string) (err error) {
var testfiles func(loader *testfixtures.Loader) error
dir := filepath.Join(config.ServiceRootpath.GetString(), "pkg", "db", "fixtures")
// If fixture table names are specified, load them
// Otherwise, load all fixtures
if len(tablenames) > 0 {
for i, name := range tablenames {
tablenames[i] = filepath.Join(dir, name+".yml")
}
testfiles = testfixtures.Files(tablenames...)
} else {
testfiles = testfixtures.Directory(dir)
}
loaderOptions := []func(loader *testfixtures.Loader) error{
testfixtures.Database(x.DB().DB),
testfixtures.Dialect(config.DatabaseType.GetString()),
testfixtures.DangerousSkipTestDatabaseCheck(),
testfiles,
}
if config.DatabaseType.GetString() == "postgres" {
loaderOptions = append(loaderOptions, testfixtures.SkipResetSequences())
}
fixtures, err = testfixtures.New(loaderOptions...)
if err != nil {
return err
}
return err
}
// LoadFixtures load fixtures for a test database
func LoadFixtures() error {
err := fixtures.Load()
if err != nil {
return err
}
// Copied from https://github.com/go-gitea/gitea/blob/master/models/test_fixtures.go#L39
// Now if we're running postgres we need to tell it to update the sequences
if x.Dialect().URI().DBType == schemas.POSTGRES {
results, err := x.QueryString(`SELECT 'SELECT SETVAL(' ||
quote_literal(quote_ident(PGT.schemaname) || '.' || quote_ident(S.relname)) ||
', COALESCE(MAX(' ||quote_ident(C.attname)|| '), 1) ) FROM ' ||
quote_ident(PGT.schemaname)|| '.'||quote_ident(T.relname)|| ';'
FROM pg_class AS S,
pg_depend AS D,
pg_class AS T,
pg_attribute AS C,
pg_tables AS PGT
WHERE S.relkind = 'S'
AND S.oid = D.objid
AND D.refobjid = T.oid
AND D.refobjid = C.attrelid
AND D.refobjsubid = C.attnum
AND T.relname = PGT.tablename
ORDER BY S.relname;`)
if err != nil {
fmt.Printf("Failed to generate sequence update: %v\n", err)
return err
}
for _, r := range results {
for _, value := range r {
_, err = x.Exec(value)
if err != nil {
fmt.Printf("Failed to update sequence: %s Error: %v\n", value, err)
return err
}
}
}
}
return err
}
// LoadAndAssertFixtures loads all fixtures defined before and asserts they are correctly loaded
func LoadAndAssertFixtures(t *testing.T) {
err := LoadFixtures()
assert.NoError(t, err)
}

49
pkg/files/db.go Normal file
View File

@ -0,0 +1,49 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package files
import (
"code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/log"
"xorm.io/xorm"
)
var x *xorm.Engine
// SetEngine sets the xorm.Engine
func SetEngine() (err error) {
x, err = db.CreateDBEngine()
if err != nil {
log.Criticalf("Could not connect to db: %v", err.Error())
return
}
// Cache
if config.CacheEnabled.GetBool() && config.CacheType.GetString() == "redis" {
db.RegisterTableStructsForCache(GetTables())
}
return nil
}
// GetTables returns all structs which are also a table.
func GetTables() []interface{} {
return []interface{}{
&File{},
}
}

41
pkg/files/dump.go Normal file
View File

@ -0,0 +1,41 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package files
import (
"io"
)
// Dump dumps all saved files
// This only includes the raw files, no db entries.
func Dump() (allFiles map[int64]io.ReadCloser, err error) {
files := []*File{}
err = x.Find(&files)
if err != nil {
return
}
allFiles = make(map[int64]io.ReadCloser, len(files))
for _, file := range files {
if err := file.LoadFileByID(); err != nil {
return nil, err
}
allFiles[file.ID] = file.File
}
return
}

69
pkg/files/error.go Normal file
View File

@ -0,0 +1,69 @@
// Copyright 2018-2020 Vikunja and contriubtors. All rights reserved.
//
// This file is part of Vikunja.
//
// Vikunja is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Vikunja 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Vikunja. If not, see <https://www.gnu.org/licenses/>.
package files
import "fmt"
// ErrFileDoesNotExist defines an error where a file does not exist in the db
type ErrFileDoesNotExist struct {
FileID int64
}
// Error is the error implementation of ErrFileDoesNotExist
func (err ErrFileDoesNotExist) Error() string {
return fmt.Sprintf("file %d does not exist", err.FileID)
}
//IsErrFileDoesNotExist checks if an error is ErrFileDoesNotExist
func IsErrFileDoesNotExist(err error) bool {
_, ok := err.(ErrFileDoesNotExist)
return ok
}
// ErrFileIsTooLarge defines an error where a file is larger than the configured limit
type ErrFileIsTooLarge struct {
Size uint64
}
// Error is the error implementation of ErrFileIsTooLarge
func (err ErrFileIsTooLarge) Error() string {
return fmt.Sprintf("file is too large [Size: %d]", err.Size)
}
//IsErrFileIsTooLarge checks if an error is ErrFileIsTooLarge
func IsErrFileIsTooLarge(err error) bool {
_, ok := err.(ErrFileIsTooLarge)
return ok
}
// ErrFileIsNotUnsplashFile defines an error where a file is not downloaded from unsplash.
// Used in cases whenever unsplash information about a file is requested, but the file was not downloaded from unsplash.
type ErrFileIsNotUnsplashFile struct {
FileID int64
}
// Error is the error implementation of ErrFileIsNotUnsplashFile
func (err ErrFileIsNotUnsplashFile) Error() string {
return fmt.Sprintf("file was not downloaded from unsplash [FileID: %d]", err.FileID)
}
//IsErrFileIsNotUnsplashFile checks if an error is ErrFileIsNotUnsplashFile
func IsErrFileIsNotUnsplashFile(err error) bool {
_, ok := err.(ErrFileIsNotUnsplashFile)
return ok
}

84
pkg/files/filehandling.go Normal file
View File

@ -0,0 +1,84 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package files
import (
"code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/log"
"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
"os"
"testing"
)
// This file handles storing and retrieving a file for different backends
var fs afero.Fs
var afs *afero.Afero
// InitFileHandler creates a new file handler for the file backend we want to use
func InitFileHandler() {
fs = afero.NewOsFs()
afs = &afero.Afero{Fs: fs}
}
// InitTestFileHandler initializes a new memory file system for testing
func InitTestFileHandler() {
fs = afero.NewMemMapFs()
afs = &afero.Afero{Fs: fs}
}
func initFixtures(t *testing.T) {
// DB fixtures
db.LoadAndAssertFixtures(t)
// File fixtures
InitTestFileFixtures(t)
}
//InitTestFileFixtures initializes file fixtures
func InitTestFileFixtures(t *testing.T) {
// Init fixture files
filename := config.FilesBasePath.GetString() + "/1"
err := afero.WriteFile(afs, filename, []byte("testfile1"), 0644)
assert.NoError(t, err)
}
// InitTests handles the actual bootstrapping of the test env
func InitTests() {
var err error
x, err = db.CreateTestEngine()
if err != nil {
log.Fatal(err)
}
err = x.Sync2(GetTables()...)
if err != nil {
log.Fatal(err)
}
err = db.InitTestFixtures("files")
if err != nil {
log.Fatal(err)
}
InitTestFileHandler()
}
// FileStat stats a file. This is an exported function to be able to test this from outide of the package
func FileStat(filename string) (os.FileInfo, error) {
return afs.Stat(filename)
}

120
pkg/files/files.go Normal file
View File

@ -0,0 +1,120 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package files
import (
"code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/timeutil"
"code.vikunja.io/web"
"github.com/c2h5oh/datasize"
"github.com/spf13/afero"
"io"
"strconv"
"time"
)
// File holds all information about a file
type File struct {
ID int64 `xorm:"int(11) autoincr not null unique pk" json:"id"`
Name string `xorm:"text not null" json:"name"`
Mime string `xorm:"text null" json:"mime"`
Size uint64 `xorm:"int(11) not null" json:"size"`
Created time.Time `xorm:"-" json:"created"`
CreatedUnix timeutil.TimeStamp `xorm:"created" json:"-"`
CreatedByID int64 `xorm:"int(11) not null" json:"-"`
File afero.File `xorm:"-" json:"-"`
// This ReadCloser is only used for migration purposes. Use with care!
// There is currentlc no better way of doing this.
FileContent []byte `xorm:"-" json:"-"`
}
// TableName is the table name for the files table
func (File) TableName() string {
return "files"
}
func (f *File) getFileName() string {
return config.FilesBasePath.GetString() + "/" + strconv.FormatInt(f.ID, 10)
}
// LoadFileByID returns a file by its ID
func (f *File) LoadFileByID() (err error) {
f.File, err = afs.Open(f.getFileName())
return
}
// LoadFileMetaByID loads everything about a file without loading the actual file
func (f *File) LoadFileMetaByID() (err error) {
exists, err := x.Where("id = ?", f.ID).Get(f)
if !exists {
return ErrFileDoesNotExist{FileID: f.ID}
}
f.Created = f.CreatedUnix.ToTime()
return
}
// Create creates a new file from an FileHeader
func Create(f io.ReadCloser, realname string, realsize uint64, a web.Auth) (file *File, err error) {
// Get and parse the configured file size
var maxSize datasize.ByteSize
err = maxSize.UnmarshalText([]byte(config.FilesMaxSize.GetString()))
if err != nil {
return nil, err
}
if realsize > maxSize.Bytes() {
return nil, ErrFileIsTooLarge{Size: realsize}
}
// We first insert the file into the db to get it's ID
file = &File{
Name: realname,
Size: realsize,
CreatedByID: a.GetID(),
}
_, err = x.Insert(file)
if err != nil {
return
}
// Save the file to storage with its new ID as path
err = file.Save(f)
return
}
// Delete removes a file from the DB and the file system
func (f *File) Delete() (err error) {
deleted, err := x.Where("id = ?", f.ID).Delete(f)
if err != nil {
return err
}
if deleted == 0 {
return ErrFileDoesNotExist{FileID: f.ID}
}
err = afs.Remove(f.getFileName())
return
}
// Save saves a file to storage
func (f *File) Save(fcontent io.ReadCloser) error {
return afs.WriteReader(f.getFileName(), fcontent)
}

131
pkg/files/files_test.go Normal file
View File

@ -0,0 +1,131 @@
// Copyright 2018-2020 Vikunja and contriubtors. All rights reserved.
//
// This file is part of Vikunja.
//
// Vikunja is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Vikunja 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Vikunja. If not, see <https://www.gnu.org/licenses/>.
package files
import (
"github.com/stretchr/testify/assert"
"io"
"os"
"testing"
)
type testfile struct {
content []byte
done bool
}
func (t *testfile) Read(p []byte) (n int, err error) {
if t.done {
return 0, io.EOF
}
copy(p, t.content)
t.done = true
return len(p), nil
}
func (t *testfile) Close() error {
return nil
}
type testauth struct {
id int64
}
func (a *testauth) GetID() int64 {
return a.id
}
func TestCreate(t *testing.T) {
t.Run("Normal", func(t *testing.T) {
initFixtures(t)
tf := &testfile{
content: []byte("testfile"),
}
ta := &testauth{id: 1}
createdFile, err := Create(tf, "testfile", 100, ta)
assert.NoError(t, err)
// Check the file was created correctly
file := &File{ID: createdFile.ID}
err = file.LoadFileMetaByID()
assert.NoError(t, err)
assert.Equal(t, int64(1), file.CreatedByID)
assert.Equal(t, "testfile", file.Name)
assert.Equal(t, uint64(100), file.Size)
})
t.Run("Too Large", func(t *testing.T) {
initFixtures(t)
tf := &testfile{
content: []byte("testfile"),
}
ta := &testauth{id: 1}
_, err := Create(tf, "testfile", 99999999999, ta)
assert.Error(t, err)
assert.True(t, IsErrFileIsTooLarge(err))
})
}
func TestFile_Delete(t *testing.T) {
t.Run("Normal", func(t *testing.T) {
initFixtures(t)
f := &File{ID: 1}
err := f.Delete()
assert.NoError(t, err)
})
t.Run("Nonexisting", func(t *testing.T) {
initFixtures(t)
f := &File{ID: 9999}
err := f.Delete()
assert.Error(t, err)
assert.True(t, IsErrFileDoesNotExist(err))
})
}
func TestFile_LoadFileByID(t *testing.T) {
t.Run("Normal", func(t *testing.T) {
initFixtures(t)
f := &File{ID: 1}
err := f.LoadFileByID()
assert.NoError(t, err)
})
t.Run("Nonexisting", func(t *testing.T) {
initFixtures(t)
f := &File{ID: 9999}
err := f.LoadFileByID()
assert.Error(t, err)
assert.True(t, os.IsNotExist(err))
})
}
func TestFile_LoadFileMetaByID(t *testing.T) {
t.Run("Normal", func(t *testing.T) {
initFixtures(t)
f := &File{ID: 1}
err := f.LoadFileMetaByID()
assert.NoError(t, err)
assert.Equal(t, "test", f.Name)
})
t.Run("Nonexisting", func(t *testing.T) {
initFixtures(t)
f := &File{ID: 9999}
err := f.LoadFileMetaByID()
assert.Error(t, err)
assert.True(t, IsErrFileDoesNotExist(err))
})
}

29
pkg/files/main_test.go Normal file
View File

@ -0,0 +1,29 @@
// Copyright 2018-2020 Vikunja and contriubtors. All rights reserved.
//
// This file is part of Vikunja.
//
// Vikunja is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Vikunja 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Vikunja. If not, see <https://www.gnu.org/licenses/>.
package files
import (
"os"
"testing"
)
// TestMain is the main test function used to bootstrap the test env
func TestMain(m *testing.M) {
InitTests()
os.Exit(m.Run())
}

79
pkg/initialize/init.go Normal file
View File

@ -0,0 +1,79 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package initialize
import (
"code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/files"
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/mail"
"code.vikunja.io/api/pkg/migration"
"code.vikunja.io/api/pkg/models"
migrator "code.vikunja.io/api/pkg/modules/migration"
"code.vikunja.io/api/pkg/red"
"code.vikunja.io/api/pkg/user"
)
// LightInit will only fullInit config, redis, logger but no db connection.
func LightInit() {
// Init the config
config.InitConfig()
// Init redis
red.InitRedis()
// Set logger
log.InitLogger()
}
// InitEngines intializes all db connections
func InitEngines() {
err := models.SetEngine()
if err != nil {
log.Fatal(err.Error())
}
err = user.InitDB()
if err != nil {
log.Fatal(err.Error())
}
err = files.SetEngine()
if err != nil {
log.Fatal(err.Error())
}
err = migrator.InitDB()
if err != nil {
log.Fatal(err.Error())
}
}
// FullInit initializes all kinds of things in the right order
func FullInit() {
LightInit()
// Run the migrations
migration.Migrate(nil)
// Set Engine
InitEngines()
// Initialize the files handler
files.InitFileHandler()
// Start the mail daemon
mail.StartMailDaemon()
}

View File

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

View File

@ -0,0 +1,238 @@
// Copyright 2020 Vikunja and contriubtors. All rights reserved.
//
// This file is part of Vikunja.
//
// Vikunja is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Vikunja 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Vikunja. If not, see <https://www.gnu.org/licenses/>.
package integrations
import (
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/web/handler"
"github.com/stretchr/testify/assert"
"net/url"
"testing"
)
// This tests the following behaviour:
// 1. A namespace should not be editable if it is archived.
// 1. With the exception being to un-archive it.
// 2. A list which belongs to an archived namespace cannot be edited.
// 3. An archived list should not be editable.
// 1. Except for un-archiving it.
// 4. It is not possible to un-archive a list individually if its namespace is archived.
// 5. Creating new lists on an archived namespace should not work.
// 6. Creating new tasks on an archived list should not work.
// 7. Creating new tasks on a list who's namespace is archived should not work.
// 8. Editing tasks on an archived list should not work.
// 9. Editing tasks on a list who's namespace is archived should not work.
// 10. Archived namespaces should not appear in the list with all namespaces.
// 11. Archived lists should not appear in the list with all lists.
// 12. Lists who's namespace is archived should not appear in the list with all lists.
//
// All of this is tested through integration tests because it's not yet clear if this will be implemented directly
// or with some kind of middleware.
//
// Maybe the inheritance of lists from namespaces could be solved with some kind of is_archived_inherited flag -
// that way I'd only need to implement the checking on a list level and update the flag for all lists once the
// namespace is archived. The archived flag would then be used to not accedentially unarchive lists which were
// already individually archived when the namespace was archived.
// Should still test it all though.
//
// Namespace 16 is archived
// List 21 belongs to namespace 16
// List 22 is archived individually
func TestArchived(t *testing.T) {
testListHandler := webHandlerTest{
user: &testuser1,
strFunc: func() handler.CObject {
return &models.List{}
},
t: t,
}
testNamespaceHandler := webHandlerTest{
user: &testuser1,
strFunc: func() handler.CObject {
return &models.Namespace{}
},
t: t,
}
testTaskHandler := webHandlerTest{
user: &testuser1,
strFunc: func() handler.CObject {
return &models.Task{}
},
t: t,
}
testLabelHandler := webHandlerTest{
user: &testuser1,
strFunc: func() handler.CObject {
return &models.LabelTask{}
},
t: t,
}
testAssigneeHandler := webHandlerTest{
user: &testuser1,
strFunc: func() handler.CObject {
return &models.TaskAssginee{}
},
t: t,
}
testRelationHandler := webHandlerTest{
user: &testuser1,
strFunc: func() handler.CObject {
return &models.TaskRelation{}
},
t: t,
}
testCommentHandler := webHandlerTest{
user: &testuser1,
strFunc: func() handler.CObject {
return &models.TaskComment{}
},
t: t,
}
t.Run("namespace", func(t *testing.T) {
t.Run("not editable", func(t *testing.T) {
_, err := testNamespaceHandler.testUpdateWithUser(nil, map[string]string{"namespace": "16"}, `{"title":"TestIpsum","is_archived":true}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeNamespaceIsArchived)
})
t.Run("unarchivable", func(t *testing.T) {
rec, err := testNamespaceHandler.testUpdateWithUser(nil, map[string]string{"namespace": "16"}, `{"title":"TestIpsum","is_archived":false}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"is_archived":false`)
})
t.Run("no new lists", func(t *testing.T) {
_, err := testListHandler.testCreateWithUser(nil, map[string]string{"namespace": "16"}, `{"title":"Lorem"}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeNamespaceIsArchived)
})
t.Run("should not appear in the list", func(t *testing.T) {
rec, err := testNamespaceHandler.testReadAllWithUser(nil, nil)
assert.NoError(t, err)
assert.NotContains(t, rec.Body.String(), `"title":"Archived testnamespace16"`)
})
t.Run("should appear in the list if explicitly requested", func(t *testing.T) {
rec, err := testNamespaceHandler.testReadAllWithUser(url.Values{"is_archived": []string{"true"}}, nil)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Archived testnamespace16"`)
})
})
t.Run("list", func(t *testing.T) {
taskTests := func(taskID string, errCode int, t *testing.T) {
t.Run("task", func(t *testing.T) {
t.Run("edit task", func(t *testing.T) {
_, err := testTaskHandler.testUpdateWithUser(nil, map[string]string{"listtask": taskID}, `{"title":"TestIpsum"}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, errCode)
})
t.Run("delete", func(t *testing.T) {
_, err := testTaskHandler.testDeleteWithUser(nil, map[string]string{"listtask": taskID})
assert.Error(t, err)
assertHandlerErrorCode(t, err, errCode)
})
t.Run("add new labels", func(t *testing.T) {
_, err := testLabelHandler.testCreateWithUser(nil, map[string]string{"listtask": taskID}, `{"label_id":1}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, errCode)
})
t.Run("remove lables", func(t *testing.T) {
_, err := testLabelHandler.testDeleteWithUser(nil, map[string]string{"listtask": taskID, "label": "4"})
assert.Error(t, err)
assertHandlerErrorCode(t, err, errCode)
})
t.Run("add assignees", func(t *testing.T) {
_, err := testAssigneeHandler.testCreateWithUser(nil, map[string]string{"listtask": taskID}, `{"user_id":3}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, errCode)
})
t.Run("remove assignees", func(t *testing.T) {
_, err := testAssigneeHandler.testDeleteWithUser(nil, map[string]string{"listtask": taskID, "user": "2"})
assert.Error(t, err)
assertHandlerErrorCode(t, err, errCode)
})
t.Run("add relation", func(t *testing.T) {
_, err := testRelationHandler.testCreateWithUser(nil, map[string]string{"task": taskID}, `{"other_task_id":1,"relation_kind":"related"}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, errCode)
})
t.Run("remove relation", func(t *testing.T) {
_, err := testRelationHandler.testDeleteWithUser(nil, map[string]string{"task": taskID}, `{"other_task_id":2,"relation_kind":"related"}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, errCode)
})
t.Run("add comment", func(t *testing.T) {
_, err := testCommentHandler.testCreateWithUser(nil, map[string]string{"task": taskID}, `{"comment":"Lorem"}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, errCode)
})
t.Run("remove comment", func(t *testing.T) {
var commentID = "15"
if taskID == "36" {
commentID = "16"
}
_, err := testCommentHandler.testDeleteWithUser(nil, map[string]string{"task": taskID, "commentid": commentID})
assert.Error(t, err)
assertHandlerErrorCode(t, err, errCode)
})
})
}
// The list belongs to an archived namespace
t.Run("archived namespace", func(t *testing.T) {
t.Run("not editable", func(t *testing.T) {
_, err := testListHandler.testUpdateWithUser(nil, map[string]string{"list": "21"}, `{"title":"TestIpsum","is_archived":true}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeNamespaceIsArchived)
})
t.Run("no new tasks", func(t *testing.T) {
_, err := testTaskHandler.testCreateWithUser(nil, map[string]string{"list": "21"}, `{"title":"Lorem"}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeNamespaceIsArchived)
})
t.Run("not unarchivable", func(t *testing.T) {
_, err := testListHandler.testUpdateWithUser(nil, map[string]string{"list": "21"}, `{"title":"LoremIpsum","is_archived":false}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeNamespaceIsArchived)
})
taskTests("35", models.ErrCodeNamespaceIsArchived, t)
})
// The list itself is archived
t.Run("archived individually", func(t *testing.T) {
t.Run("not editable", func(t *testing.T) {
_, err := testListHandler.testUpdateWithUser(nil, map[string]string{"list": "22"}, `{"title":"TestIpsum","is_archived":true}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeListIsArchived)
})
t.Run("no new tasks", func(t *testing.T) {
_, err := testTaskHandler.testCreateWithUser(nil, map[string]string{"list": "22"}, `{"title":"Lorem"}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeListIsArchived)
})
t.Run("unarchivable", func(t *testing.T) {
rec, err := testListHandler.testUpdateWithUser(nil, map[string]string{"list": "22"}, `{"title":"LoremIpsum","is_archived":false}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"is_archived":false`)
})
taskTests("36", models.ErrCodeListIsArchived, t)
})
})
}

View File

@ -1,69 +1,72 @@
// Vikunja is a todo-list application to facilitate your life.
// Copyright 2019 Vikunja and contributors. All rights reserved.
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// 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 General Public License for more details.
// 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package integrations
import (
"code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/files"
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/routes"
v1 "code.vikunja.io/api/pkg/routes/api/v1"
"code.vikunja.io/api/pkg/user"
"code.vikunja.io/web"
"code.vikunja.io/web/handler"
"github.com/dgrijalva/jwt-go"
"github.com/labstack/echo/v4"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
"net/http"
"net/http/httptest"
"net/url"
"os"
"strings"
"testing"
)
// These are the test users, the same way they are in the test database
var (
testuser1 = models.User{
testuser1 = user.User{
ID: 1,
Username: "user1",
Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.",
Email: "user1@example.com",
IsActive: true,
}
testuser2 = models.User{
testuser2 = user.User{
ID: 2,
Username: "user2",
Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.",
Email: "user2@example.com",
}
testuser3 = models.User{
testuser3 = user.User{
ID: 3,
Username: "user3",
Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.",
Email: "user3@example.com",
PasswordResetToken: "passwordresettesttoken",
}
testuser4 = models.User{
testuser4 = user.User{
ID: 4,
Username: "user4",
Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.",
Email: "user4@example.com",
EmailConfirmToken: "tiepiQueed8ahc7zeeFe1eveiy4Ein8osooxegiephauph2Ael",
}
testuser5 = models.User{
testuser5 = user.User{
ID: 4,
Username: "user5",
Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.",
@ -74,10 +77,15 @@ var (
)
func setupTestEnv() (e *echo.Echo, err error) {
config.InitConfig()
models.SetupTests(viper.GetString("service.rootpath"))
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()
err = models.LoadFixtures()
err = db.LoadFixtures()
if err != nil {
return
}
@ -108,20 +116,32 @@ func newTestRequest(t *testing.T, method string, handler func(ctx echo.Context)
return
}
func addTokenToContext(t *testing.T, user *models.User, c echo.Context) {
func addUserTokenToContext(t *testing.T, user *user.User, c echo.Context) {
// Get the token as a string
token, err := v1.CreateNewJWTTokenForUser(user)
token, err := v1.NewUserJWTAuthtoken(user)
assert.NoError(t, err)
// We send the string token through the parsing function to get a valid jwt.Token
tken, err := jwt.Parse(token, func(t *jwt.Token) (interface{}, error) {
return []byte(viper.GetString("service.JWTSecret")), nil
return []byte(config.ServiceJWTSecret.GetString()), nil
})
assert.NoError(t, err)
c.Set("user", tken)
}
func newTestRequestWithUser(t *testing.T, method string, handler echo.HandlerFunc, user *models.User, payload string, queryParams url.Values, urlParams map[string]string) (rec *httptest.ResponseRecorder, err error) {
c, rec := bootstrapTestRequest(t, method, payload, queryParams)
func addLinkShareTokenToContext(t *testing.T, share *models.LinkSharing, c echo.Context) {
// Get the token as a string
token, err := v1.NewLinkShareJWTAuthtoken(share)
assert.NoError(t, err)
// We send the string token through the parsing function to get a valid jwt.Token
tken, err := jwt.Parse(token, func(t *jwt.Token) (interface{}, error) {
return []byte(config.ServiceJWTSecret.GetString()), nil
})
assert.NoError(t, err)
c.Set("user", tken)
}
func testRequestSetup(t *testing.T, method string, payload string, queryParams url.Values, urlParams map[string]string) (rec *httptest.ResponseRecorder, c echo.Context) {
c, rec = bootstrapTestRequest(t, method, payload, queryParams)
var paramNames []string
var paramValues []string
@ -131,8 +151,19 @@ func newTestRequestWithUser(t *testing.T, method string, handler echo.HandlerFun
}
c.SetParamNames(paramNames...)
c.SetParamValues(paramValues...)
return
}
addTokenToContext(t, user, c)
func newTestRequestWithUser(t *testing.T, method string, handler echo.HandlerFunc, user *user.User, payload string, queryParams url.Values, urlParams map[string]string) (rec *httptest.ResponseRecorder, err error) {
rec, c := testRequestSetup(t, method, payload, queryParams, urlParams)
addUserTokenToContext(t, user, c)
err = handler(c)
return
}
func newTestRequestWithLinkShare(t *testing.T, method string, handler echo.HandlerFunc, share *models.LinkSharing, payload string, queryParams url.Values, urlParams map[string]string) (rec *httptest.ResponseRecorder, err error) {
rec, c := testRequestSetup(t, method, payload, queryParams, urlParams)
addLinkShareTokenToContext(t, share, c)
err = handler(c)
return
}
@ -156,9 +187,10 @@ func assertHandlerErrorCode(t *testing.T, err error, expectedErrorCode int) {
}
type webHandlerTest struct {
user *models.User
strFunc func() handler.CObject
t *testing.T
user *user.User
linkShare *models.LinkSharing
strFunc func() handler.CObject
t *testing.T
}
func (h *webHandlerTest) getHandler() handler.WebHandler {
@ -169,27 +201,56 @@ func (h *webHandlerTest) getHandler() handler.WebHandler {
}
}
func (h *webHandlerTest) testReadAll(queryParams url.Values, urlParams map[string]string) (rec *httptest.ResponseRecorder, err error) {
func (h *webHandlerTest) testReadAllWithUser(queryParams url.Values, urlParams map[string]string) (rec *httptest.ResponseRecorder, err error) {
hndl := h.getHandler()
return newTestRequestWithUser(h.t, http.MethodGet, hndl.ReadAllWeb, h.user, "", queryParams, urlParams)
}
func (h *webHandlerTest) testReadOne(queryParams url.Values, urlParams map[string]string) (rec *httptest.ResponseRecorder, err error) {
func (h *webHandlerTest) testReadOneWithUser(queryParams url.Values, urlParams map[string]string) (rec *httptest.ResponseRecorder, err error) {
hndl := h.getHandler()
return newTestRequestWithUser(h.t, http.MethodGet, hndl.ReadOneWeb, h.user, "", queryParams, urlParams)
}
func (h *webHandlerTest) testCreate(queryParams url.Values, urlParams map[string]string, payload string) (rec *httptest.ResponseRecorder, err error) {
func (h *webHandlerTest) testCreateWithUser(queryParams url.Values, urlParams map[string]string, payload string) (rec *httptest.ResponseRecorder, err error) {
hndl := h.getHandler()
return newTestRequestWithUser(h.t, http.MethodPut, hndl.CreateWeb, h.user, payload, queryParams, urlParams)
}
func (h *webHandlerTest) testUpdate(queryParams url.Values, urlParams map[string]string, payload string) (rec *httptest.ResponseRecorder, err error) {
func (h *webHandlerTest) testUpdateWithUser(queryParams url.Values, urlParams map[string]string, payload string) (rec *httptest.ResponseRecorder, err error) {
hndl := h.getHandler()
return newTestRequestWithUser(h.t, http.MethodPost, hndl.UpdateWeb, h.user, payload, queryParams, urlParams)
}
func (h *webHandlerTest) testDelete(queryParams url.Values, urlParams map[string]string) (rec *httptest.ResponseRecorder, err error) {
func (h *webHandlerTest) testDeleteWithUser(queryParams url.Values, urlParams map[string]string, payload ...string) (rec *httptest.ResponseRecorder, err error) {
pl := ""
if len(payload) > 0 {
pl = payload[0]
}
hndl := h.getHandler()
return newTestRequestWithUser(h.t, http.MethodDelete, hndl.DeleteWeb, h.user, "", queryParams, urlParams)
return newTestRequestWithUser(h.t, http.MethodDelete, hndl.DeleteWeb, h.user, pl, queryParams, urlParams)
}
func (h *webHandlerTest) testReadAllWithLinkShare(queryParams url.Values, urlParams map[string]string) (rec *httptest.ResponseRecorder, err error) {
hndl := h.getHandler()
return newTestRequestWithLinkShare(h.t, http.MethodGet, hndl.ReadAllWeb, h.linkShare, "", queryParams, urlParams)
}
func (h *webHandlerTest) testReadOneWithLinkShare(queryParams url.Values, urlParams map[string]string) (rec *httptest.ResponseRecorder, err error) {
hndl := h.getHandler()
return newTestRequestWithLinkShare(h.t, http.MethodGet, hndl.ReadOneWeb, h.linkShare, "", queryParams, urlParams)
}
func (h *webHandlerTest) testCreateWithLinkShare(queryParams url.Values, urlParams map[string]string, payload string) (rec *httptest.ResponseRecorder, err error) {
hndl := h.getHandler()
return newTestRequestWithLinkShare(h.t, http.MethodPut, hndl.CreateWeb, h.linkShare, payload, queryParams, urlParams)
}
func (h *webHandlerTest) testUpdateWithLinkShare(queryParams url.Values, urlParams map[string]string, payload string) (rec *httptest.ResponseRecorder, err error) {
hndl := h.getHandler()
return newTestRequestWithLinkShare(h.t, http.MethodPost, hndl.UpdateWeb, h.linkShare, payload, queryParams, urlParams)
}
func (h *webHandlerTest) testDeleteWithLinkShare(queryParams url.Values, urlParams map[string]string) (rec *httptest.ResponseRecorder, err error) {
hndl := h.getHandler()
return newTestRequestWithLinkShare(h.t, http.MethodDelete, hndl.DeleteWeb, h.linkShare, "", queryParams, urlParams)
}

View File

@ -0,0 +1,300 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package integrations
import (
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/web/handler"
"github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert"
"testing"
)
func TestBucket(t *testing.T) {
testHandler := webHandlerTest{
user: &testuser1,
strFunc: func() handler.CObject {
return &models.Bucket{}
},
t: t,
}
t.Run("ReadAll", func(t *testing.T) {
t.Run("Normal", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(nil, map[string]string{"list": "1"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `testbucket1`)
assert.Contains(t, rec.Body.String(), `testbucket2`)
assert.Contains(t, rec.Body.String(), `testbucket3`)
assert.NotContains(t, rec.Body.String(), `testbucket4`) // Different List
})
})
t.Run("Update", func(t *testing.T) {
t.Run("Normal", func(t *testing.T) {
// Check the list was loaded successfully afterwards, see testReadOneWithUser
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"bucket": "1"}, `{"title":"TestLoremIpsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
})
t.Run("Nonexisting Bucket", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"bucket": "9999"}, `{"title":"TestLoremIpsum"}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeBucketDoesNotExist)
})
t.Run("Empty title", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"bucket": "1"}, `{"title":""}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message.(models.ValidationHTTPError).InvalidFields, "title: non zero value required")
})
t.Run("Rights check", func(t *testing.T) {
t.Run("Forbidden", func(t *testing.T) {
// Owned by user13
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"bucket": "5"}, `{"title":"TestLoremIpsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Team readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"bucket": "6"}, `{"title":"TestLoremIpsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Team write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"bucket": "7"}, `{"title":"TestLoremIpsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
})
t.Run("Shared Via Team admin", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"bucket": "8"}, `{"title":"TestLoremIpsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
})
t.Run("Shared Via User readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"bucket": "9"}, `{"title":"TestLoremIpsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via User write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"bucket": "10"}, `{"title":"TestLoremIpsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
})
t.Run("Shared Via User admin", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"bucket": "11"}, `{"title":"TestLoremIpsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
})
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"bucket": "12"}, `{"title":"TestLoremIpsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"bucket": "13"}, `{"title":"TestLoremIpsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
})
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"bucket": "14"}, `{"title":"TestLoremIpsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
})
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"bucket": "15"}, `{"title":"TestLoremIpsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"bucket": "16"}, `{"title":"TestLoremIpsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
})
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"bucket": "17"}, `{"title":"TestLoremIpsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
})
})
})
t.Run("Delete", func(t *testing.T) {
t.Run("Normal", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "1", "bucket": "1"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
})
t.Run("Nonexisting", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"bucket": "999"})
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeBucketDoesNotExist)
})
t.Run("Rights check", func(t *testing.T) {
t.Run("Forbidden", func(t *testing.T) {
// Owned by user13
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "20", "bucket": "5"})
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Team readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "6", "bucket": "6"})
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Team write", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "7", "bucket": "7"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
})
t.Run("Shared Via Team admin", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "8", "bucket": "8"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
})
t.Run("Shared Via User readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "9", "bucket": "9"})
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via User write", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "10", "bucket": "10"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
})
t.Run("Shared Via User admin", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "11", "bucket": "11"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
})
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "12", "bucket": "12"})
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "13", "bucket": "13"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
})
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "14", "bucket": "14"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
})
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "15", "bucket": "15"})
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "16", "bucket": "16"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
})
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "17", "bucket": "17"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
})
})
})
t.Run("Create", func(t *testing.T) {
t.Run("Normal", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "1"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
t.Run("Nonexisting", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "9999"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeListDoesNotExist)
})
t.Run("Rights check", func(t *testing.T) {
t.Run("Forbidden", func(t *testing.T) {
// Owned by user13
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "20"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Team readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "6"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Team write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "7"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
t.Run("Shared Via Team admin", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "8"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
t.Run("Shared Via User readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "9"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via User write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "10"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
t.Run("Shared Via User admin", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "11"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "12"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "13"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "14"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "15"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "16"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "17"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
})
})
}

File diff suppressed because it is too large Load Diff

View File

@ -1,18 +1,18 @@
// Vikunja is a todo-list application to facilitate your life.
// Copyright 2019 Vikunja and contributors. All rights reserved.
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// 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 General Public License for more details.
// 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package integrations
@ -35,16 +35,18 @@ func TestList(t *testing.T) {
}
t.Run("ReadAll", func(t *testing.T) {
t.Run("Normal", func(t *testing.T) {
rec, err := testHandler.testReadAll(nil, nil)
rec, err := testHandler.testReadAllWithUser(nil, nil)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `Test1`)
assert.NotContains(t, rec.Body.String(), `Test2`)
assert.NotContains(t, rec.Body.String(), `Test2"`)
assert.Contains(t, rec.Body.String(), `Test3`) // Shared directly via users_list
assert.Contains(t, rec.Body.String(), `Test4`) // Shared via namespace
assert.NotContains(t, rec.Body.String(), `Test5`)
assert.NotContains(t, rec.Body.String(), `Test21`) // Archived through namespace
assert.NotContains(t, rec.Body.String(), `Test22`) // Archived directly
})
t.Run("Search", func(t *testing.T) {
rec, err := testHandler.testReadAll(url.Values{"s": []string{"Test1"}}, nil)
rec, err := testHandler.testReadAllWithUser(url.Values{"s": []string{"Test1"}}, nil)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `Test1`)
assert.NotContains(t, rec.Body.String(), `Test2`)
@ -52,89 +54,100 @@ func TestList(t *testing.T) {
assert.NotContains(t, rec.Body.String(), `Test4`)
assert.NotContains(t, rec.Body.String(), `Test5`)
})
t.Run("Normal with archived lists", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"is_archived": []string{"true"}}, nil)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `Test1`)
assert.NotContains(t, rec.Body.String(), `Test2"`)
assert.Contains(t, rec.Body.String(), `Test3`) // Shared directly via users_list
assert.Contains(t, rec.Body.String(), `Test4`) // Shared via namespace
assert.NotContains(t, rec.Body.String(), `Test5`)
assert.Contains(t, rec.Body.String(), `Test21`) // Archived through namespace
assert.Contains(t, rec.Body.String(), `Test22`) // Archived directly
})
})
t.Run("ReadOne", func(t *testing.T) {
t.Run("Normal", func(t *testing.T) {
rec, err := testHandler.testReadOne(nil, map[string]string{"list": "1"})
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "1"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Test1"`)
assert.NotContains(t, rec.Body.String(), `"title":"Test2"`)
assert.Contains(t, rec.Body.String(), `"owner":{"id":1,"username":"user1",`)
assert.NotContains(t, rec.Body.String(), `"owner":{"id":2,"username":"user2",`)
assert.Contains(t, rec.Body.String(), `"tasks":[{"id":1,"text":"task #1",`)
assert.NotContains(t, rec.Body.String(), `"tasks":`)
})
t.Run("Nonexisting", func(t *testing.T) {
_, err := testHandler.testReadOne(nil, map[string]string{"list": "9999"})
_, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "9999"})
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeListDoesNotExist)
})
t.Run("Rights check", func(t *testing.T) {
t.Run("Forbidden", func(t *testing.T) {
// Owned by user3
_, err := testHandler.testReadOne(nil, map[string]string{"list": "2"})
// Owned by user13
_, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "20"})
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `You don't have the right to see this`)
})
t.Run("Shared Via Team readonly", func(t *testing.T) {
rec, err := testHandler.testReadOne(nil, map[string]string{"list": "6"})
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "6"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Test6"`)
})
t.Run("Shared Via Team write", func(t *testing.T) {
rec, err := testHandler.testReadOne(nil, map[string]string{"list": "7"})
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "7"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Test7"`)
})
t.Run("Shared Via Team admin", func(t *testing.T) {
rec, err := testHandler.testReadOne(nil, map[string]string{"list": "8"})
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "8"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Test8"`)
})
t.Run("Shared Via User readonly", func(t *testing.T) {
rec, err := testHandler.testReadOne(nil, map[string]string{"list": "9"})
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "9"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Test9"`)
})
t.Run("Shared Via User write", func(t *testing.T) {
rec, err := testHandler.testReadOne(nil, map[string]string{"list": "10"})
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "10"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Test10"`)
})
t.Run("Shared Via User admin", func(t *testing.T) {
rec, err := testHandler.testReadOne(nil, map[string]string{"list": "11"})
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "11"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Test11"`)
})
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
rec, err := testHandler.testReadOne(nil, map[string]string{"list": "12"})
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "12"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Test12"`)
})
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
rec, err := testHandler.testReadOne(nil, map[string]string{"list": "13"})
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "13"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Test13"`)
})
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
rec, err := testHandler.testReadOne(nil, map[string]string{"list": "14"})
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "14"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Test14"`)
})
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
rec, err := testHandler.testReadOne(nil, map[string]string{"list": "15"})
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "15"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Test15"`)
})
t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
rec, err := testHandler.testReadOne(nil, map[string]string{"list": "16"})
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "16"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Test16"`)
})
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
rec, err := testHandler.testReadOne(nil, map[string]string{"list": "17"})
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "17"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Test17"`)
})
@ -142,106 +155,101 @@ func TestList(t *testing.T) {
})
t.Run("Update", func(t *testing.T) {
t.Run("Normal", func(t *testing.T) {
// Check the list was loaded successfully afterwards, see testReadOne
rec, err := testHandler.testUpdate(nil, map[string]string{"list": "1"}, `{"title":"TestLoremIpsum"}`)
// Check the list was loaded successfully afterwards, see testReadOneWithUser
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "1"}, `{"title":"TestLoremIpsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
// The description should not be updated but returned correctly
assert.Contains(t, rec.Body.String(), `description":"Lorem Ipsum`)
})
t.Run("Nonexisting", func(t *testing.T) {
_, err := testHandler.testUpdate(nil, map[string]string{"list": "9999"}, `{"title":"TestLoremIpsum"}`)
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "9999"}, `{"title":"TestLoremIpsum"}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeListDoesNotExist)
})
t.Run("Normal with updating the description", func(t *testing.T) {
rec, err := testHandler.testUpdate(nil, map[string]string{"list": "1"}, `{"title":"TestLoremIpsum","description":"Lorem Ipsum dolor sit amet"}`)
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "1"}, `{"title":"TestLoremIpsum","description":"Lorem Ipsum dolor sit amet"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
assert.Contains(t, rec.Body.String(), `"description":"Lorem Ipsum dolor sit amet`)
})
t.Run("Empty title", func(t *testing.T) {
_, err := testHandler.testUpdate(nil, map[string]string{"list": "1"}, `{"title":""}`)
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "1"}, `{"title":""}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message.(models.ValidationHTTPError).InvalidFields, "title: non zero value required")
})
t.Run("Almost empty title", func(t *testing.T) {
_, err := testHandler.testUpdate(nil, map[string]string{"list": "1"}, `{"title":"nn"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message.(models.ValidationHTTPError).InvalidFields[0], "does not validate as runelength(3|250)")
})
t.Run("Title too long", func(t *testing.T) {
_, err := testHandler.testUpdate(nil, map[string]string{"list": "1"}, `{"title":"Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea taki"}`)
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "1"}, `{"title":"Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea taki"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message.(models.ValidationHTTPError).InvalidFields[0], "does not validate as runelength(3|250)")
assert.Contains(t, err.(*echo.HTTPError).Message.(models.ValidationHTTPError).InvalidFields[0], "does not validate as runelength(1|250)")
})
t.Run("Rights check", func(t *testing.T) {
t.Run("Forbidden", func(t *testing.T) {
// Owned by user3
_, err := testHandler.testUpdate(nil, map[string]string{"list": "2"}, `{"title":"TestLoremIpsum"}`)
// Owned by user13
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "20"}, `{"title":"TestLoremIpsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Team readonly", func(t *testing.T) {
_, err := testHandler.testUpdate(nil, map[string]string{"list": "6"}, `{"title":"TestLoremIpsum"}`)
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "6"}, `{"title":"TestLoremIpsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Team write", func(t *testing.T) {
rec, err := testHandler.testUpdate(nil, map[string]string{"list": "7"}, `{"title":"TestLoremIpsum"}`)
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "7"}, `{"title":"TestLoremIpsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
})
t.Run("Shared Via Team admin", func(t *testing.T) {
rec, err := testHandler.testUpdate(nil, map[string]string{"list": "8"}, `{"title":"TestLoremIpsum"}`)
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "8"}, `{"title":"TestLoremIpsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
})
t.Run("Shared Via User readonly", func(t *testing.T) {
_, err := testHandler.testUpdate(nil, map[string]string{"list": "9"}, `{"title":"TestLoremIpsum"}`)
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "9"}, `{"title":"TestLoremIpsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via User write", func(t *testing.T) {
rec, err := testHandler.testUpdate(nil, map[string]string{"list": "10"}, `{"title":"TestLoremIpsum"}`)
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "10"}, `{"title":"TestLoremIpsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
})
t.Run("Shared Via User admin", func(t *testing.T) {
rec, err := testHandler.testUpdate(nil, map[string]string{"list": "11"}, `{"title":"TestLoremIpsum"}`)
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "11"}, `{"title":"TestLoremIpsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
})
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
_, err := testHandler.testUpdate(nil, map[string]string{"list": "12"}, `{"title":"TestLoremIpsum"}`)
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "12"}, `{"title":"TestLoremIpsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
rec, err := testHandler.testUpdate(nil, map[string]string{"list": "13"}, `{"title":"TestLoremIpsum"}`)
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "13"}, `{"title":"TestLoremIpsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
})
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
rec, err := testHandler.testUpdate(nil, map[string]string{"list": "14"}, `{"title":"TestLoremIpsum"}`)
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "14"}, `{"title":"TestLoremIpsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
})
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
_, err := testHandler.testUpdate(nil, map[string]string{"list": "15"}, `{"title":"TestLoremIpsum"}`)
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "15"}, `{"title":"TestLoremIpsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
rec, err := testHandler.testUpdate(nil, map[string]string{"list": "16"}, `{"title":"TestLoremIpsum"}`)
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "16"}, `{"title":"TestLoremIpsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
})
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
rec, err := testHandler.testUpdate(nil, map[string]string{"list": "17"}, `{"title":"TestLoremIpsum"}`)
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "17"}, `{"title":"TestLoremIpsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
})
@ -249,82 +257,82 @@ func TestList(t *testing.T) {
})
t.Run("Delete", func(t *testing.T) {
t.Run("Normal", func(t *testing.T) {
rec, err := testHandler.testDelete(nil, map[string]string{"list": "1"})
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "1"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
})
t.Run("Nonexisting", func(t *testing.T) {
_, err := testHandler.testDelete(nil, map[string]string{"list": "999"})
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "999"})
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeListDoesNotExist)
})
t.Run("Rights check", func(t *testing.T) {
t.Run("Forbidden", func(t *testing.T) {
// Owned by user3
_, err := testHandler.testDelete(nil, map[string]string{"list": "2"})
// Owned by user13
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "20"})
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Team readonly", func(t *testing.T) {
_, err := testHandler.testDelete(nil, map[string]string{"list": "6"})
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "6"})
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Team write", func(t *testing.T) {
_, err := testHandler.testDelete(nil, map[string]string{"list": "7"})
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "7"})
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Team admin", func(t *testing.T) {
rec, err := testHandler.testDelete(nil, map[string]string{"list": "8"})
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "8"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
})
t.Run("Shared Via User readonly", func(t *testing.T) {
_, err := testHandler.testDelete(nil, map[string]string{"list": "9"})
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "9"})
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via User write", func(t *testing.T) {
_, err := testHandler.testDelete(nil, map[string]string{"list": "10"})
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "10"})
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via User admin", func(t *testing.T) {
rec, err := testHandler.testDelete(nil, map[string]string{"list": "11"})
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "11"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
})
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
_, err := testHandler.testDelete(nil, map[string]string{"list": "12"})
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "12"})
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
_, err := testHandler.testDelete(nil, map[string]string{"list": "13"})
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "13"})
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
rec, err := testHandler.testDelete(nil, map[string]string{"list": "14"})
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "14"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
})
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
_, err := testHandler.testDelete(nil, map[string]string{"list": "15"})
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "15"})
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
_, err := testHandler.testDelete(nil, map[string]string{"list": "16"})
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "16"})
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
rec, err := testHandler.testDelete(nil, map[string]string{"list": "17"})
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "17"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
})
@ -332,93 +340,88 @@ func TestList(t *testing.T) {
})
t.Run("Create", func(t *testing.T) {
t.Run("Normal", func(t *testing.T) {
// Check the list was loaded successfully after update, see testReadOne
rec, err := testHandler.testCreate(nil, map[string]string{"namespace": "1"}, `{"title":"Lorem"}`)
// Check the list was loaded successfully after update, see testReadOneWithUser
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"namespace": "1"}, `{"title":"Lorem"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem"`)
assert.Contains(t, rec.Body.String(), `"description":""`)
assert.Contains(t, rec.Body.String(), `"owner":{"id":1`)
assert.Contains(t, rec.Body.String(), `"tasks":null`)
assert.NotContains(t, rec.Body.String(), `"tasks":`)
})
t.Run("Normal with description", func(t *testing.T) {
rec, err := testHandler.testCreate(nil, map[string]string{"namespace": "1"}, `{"title":"Lorem","description":"Lorem Ipsum"}`)
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"namespace": "1"}, `{"title":"Lorem","description":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem"`)
assert.Contains(t, rec.Body.String(), `"description":"Lorem Ipsum"`)
assert.Contains(t, rec.Body.String(), `"owner":{"id":1`)
assert.Contains(t, rec.Body.String(), `"tasks":null`)
assert.NotContains(t, rec.Body.String(), `"tasks":`)
})
t.Run("Nonexisting Namespace", func(t *testing.T) {
_, err := testHandler.testCreate(nil, map[string]string{"namespace": "999999"}, `{"title":"Lorem"}`)
_, err := testHandler.testCreateWithUser(nil, map[string]string{"namespace": "999999"}, `{"title":"Lorem"}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeNamespaceDoesNotExist)
})
t.Run("Empty title", func(t *testing.T) {
_, err := testHandler.testCreate(nil, map[string]string{"namespace": "1"}, `{"title":""}`)
_, err := testHandler.testCreateWithUser(nil, map[string]string{"namespace": "1"}, `{"title":""}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message.(models.ValidationHTTPError).InvalidFields, "title: non zero value required")
})
t.Run("Almost empty title", func(t *testing.T) {
_, err := testHandler.testCreate(nil, map[string]string{"namespace": "1"}, `{"title":"nn"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message.(models.ValidationHTTPError).InvalidFields[0], "does not validate as runelength(3|250)")
})
t.Run("Title too long", func(t *testing.T) {
_, err := testHandler.testCreate(nil, map[string]string{"namespace": "1"}, `{"title":"Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea taki"}`)
_, err := testHandler.testCreateWithUser(nil, map[string]string{"namespace": "1"}, `{"title":"Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea taki"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message.(models.ValidationHTTPError).InvalidFields[0], "does not validate as runelength(3|250)")
assert.Contains(t, err.(*echo.HTTPError).Message.(models.ValidationHTTPError).InvalidFields[0], "does not validate as runelength(1|250)")
})
t.Run("Rights check", func(t *testing.T) {
t.Run("Forbidden", func(t *testing.T) {
// Owned by user3
_, err := testHandler.testCreate(nil, map[string]string{"namespace": "3"}, `{"title":"Lorem"}`)
// Owned by user13
_, err := testHandler.testCreateWithUser(nil, map[string]string{"namespace": "15"}, `{"title":"Lorem"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
_, err := testHandler.testCreate(nil, map[string]string{"namespace": "7"}, `{"title":"Lorem"}`)
_, err := testHandler.testCreateWithUser(nil, map[string]string{"namespace": "7"}, `{"title":"Lorem"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
rec, err := testHandler.testCreate(nil, map[string]string{"namespace": "8"}, `{"title":"Lorem"}`)
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"namespace": "8"}, `{"title":"Lorem"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem"`)
assert.Contains(t, rec.Body.String(), `"description":""`)
assert.Contains(t, rec.Body.String(), `"owner":{"id":1`)
assert.Contains(t, rec.Body.String(), `"tasks":null`)
assert.NotContains(t, rec.Body.String(), `"tasks":`)
})
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
rec, err := testHandler.testCreate(nil, map[string]string{"namespace": "9"}, `{"title":"Lorem"}`)
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"namespace": "9"}, `{"title":"Lorem"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem"`)
assert.Contains(t, rec.Body.String(), `"description":""`)
assert.Contains(t, rec.Body.String(), `"owner":{"id":1`)
assert.Contains(t, rec.Body.String(), `"tasks":null`)
assert.NotContains(t, rec.Body.String(), `"tasks":`)
})
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
_, err := testHandler.testCreate(nil, map[string]string{"namespace": "10"}, `{"title":"Lorem"}`)
_, err := testHandler.testCreateWithUser(nil, map[string]string{"namespace": "10"}, `{"title":"Lorem"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
rec, err := testHandler.testCreate(nil, map[string]string{"namespace": "11"}, `{"title":"Lorem"}`)
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"namespace": "11"}, `{"title":"Lorem"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem"`)
assert.Contains(t, rec.Body.String(), `"description":""`)
assert.Contains(t, rec.Body.String(), `"owner":{"id":1`)
assert.Contains(t, rec.Body.String(), `"tasks":null`)
assert.NotContains(t, rec.Body.String(), `"tasks":`)
})
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
rec, err := testHandler.testCreate(nil, map[string]string{"namespace": "12"}, `{"title":"Lorem"}`)
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"namespace": "12"}, `{"title":"Lorem"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem"`)
assert.Contains(t, rec.Body.String(), `"description":""`)
assert.Contains(t, rec.Body.String(), `"owner":{"id":1`)
assert.Contains(t, rec.Body.String(), `"tasks":null`)
assert.NotContains(t, rec.Body.String(), `"tasks":`)
})
})
})

View File

@ -1,24 +1,24 @@
// Vikunja is a todo-list application to facilitate your life.
// Copyright 2019 Vikunja and contributors. All rights reserved.
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// 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 General Public License for more details.
// 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package integrations
import (
"code.vikunja.io/api/pkg/models"
apiv1 "code.vikunja.io/api/pkg/routes/api/v1"
"code.vikunja.io/api/pkg/user"
"github.com/stretchr/testify/assert"
"net/http"
"testing"
@ -36,7 +36,7 @@ func TestLogin(t *testing.T) {
t.Run("Empty payload", func(t *testing.T) {
_, err := newTestRequest(t, http.MethodPost, apiv1.Login, `{}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeNoUsernamePassword)
assertHandlerErrorCode(t, err, user.ErrCodeNoUsernamePassword)
})
t.Run("Not existing user", func(t *testing.T) {
_, err := newTestRequest(t, http.MethodPost, apiv1.Login, `{
@ -44,7 +44,7 @@ func TestLogin(t *testing.T) {
"password": "1234"
}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeWrongUsernameOrPassword)
assertHandlerErrorCode(t, err, user.ErrCodeWrongUsernameOrPassword)
})
t.Run("Wrong password", func(t *testing.T) {
_, err := newTestRequest(t, http.MethodPost, apiv1.Login, `{
@ -52,7 +52,7 @@ func TestLogin(t *testing.T) {
"password": "wrong"
}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeWrongUsernameOrPassword)
assertHandlerErrorCode(t, err, user.ErrCodeWrongUsernameOrPassword)
})
t.Run("user with unconfirmed email", func(t *testing.T) {
_, err := newTestRequest(t, http.MethodPost, apiv1.Login, `{
@ -60,6 +60,6 @@ func TestLogin(t *testing.T) {
"password": "1234"
}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeEmailNotConfirmed)
assertHandlerErrorCode(t, err, user.ErrCodeEmailNotConfirmed)
})
}

View File

@ -1,24 +1,24 @@
// Vikunja is a todo-list application to facilitate your life.
// Copyright 2019 Vikunja and contributors. All rights reserved.
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// 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 General Public License for more details.
// 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package integrations
import (
"code.vikunja.io/api/pkg/models"
apiv1 "code.vikunja.io/api/pkg/routes/api/v1"
"code.vikunja.io/api/pkg/user"
"github.com/stretchr/testify/assert"
"net/http"
"testing"
@ -37,7 +37,7 @@ func TestRegister(t *testing.T) {
t.Run("Empty payload", func(t *testing.T) {
_, err := newTestRequest(t, http.MethodPost, apiv1.RegisterUser, `{}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeNoUsernamePassword)
assertHandlerErrorCode(t, err, user.ErrCodeNoUsernamePassword)
})
t.Run("Empty username", func(t *testing.T) {
_, err := newTestRequest(t, http.MethodPost, apiv1.RegisterUser, `{
@ -46,7 +46,7 @@ func TestRegister(t *testing.T) {
"email": "email@example.com"
}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeNoUsernamePassword)
assertHandlerErrorCode(t, err, user.ErrCodeNoUsernamePassword)
})
t.Run("Empty password", func(t *testing.T) {
_, err := newTestRequest(t, http.MethodPost, apiv1.RegisterUser, `{
@ -55,7 +55,7 @@ func TestRegister(t *testing.T) {
"email": "email@example.com"
}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeNoUsernamePassword)
assertHandlerErrorCode(t, err, user.ErrCodeNoUsernamePassword)
})
t.Run("Empty email", func(t *testing.T) {
_, err := newTestRequest(t, http.MethodPost, apiv1.RegisterUser, `{
@ -64,7 +64,7 @@ func TestRegister(t *testing.T) {
"email": ""
}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeNoUsernamePassword)
assertHandlerErrorCode(t, err, user.ErrCodeNoUsernamePassword)
})
t.Run("Already existing username", func(t *testing.T) {
_, err := newTestRequest(t, http.MethodPost, apiv1.RegisterUser, `{
@ -73,7 +73,7 @@ func TestRegister(t *testing.T) {
"email": "email@example.com"
}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrorCodeUsernameExists)
assertHandlerErrorCode(t, err, user.ErrorCodeUsernameExists)
})
t.Run("Already existing email", func(t *testing.T) {
_, err := newTestRequest(t, http.MethodPost, apiv1.RegisterUser, `{
@ -82,6 +82,6 @@ func TestRegister(t *testing.T) {
"email": "user1@example.com"
}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrorCodeUserEmailExists)
assertHandlerErrorCode(t, err, user.ErrorCodeUserEmailExists)
})
}

View File

@ -0,0 +1,414 @@
// Copyright 2018-2020 Vikunja and contriubtors. All rights reserved.
//
// This file is part of Vikunja.
//
// Vikunja is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Vikunja 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Vikunja. If not, see <https://www.gnu.org/licenses/>.
package integrations
import (
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/web/handler"
"github.com/stretchr/testify/assert"
"net/url"
"testing"
)
func TestTaskCollection(t *testing.T) {
testHandler := webHandlerTest{
user: &testuser1,
strFunc: func() handler.CObject {
return &models.TaskCollection{}
},
t: t,
}
t.Run("ReadAll on list", func(t *testing.T) {
urlParams := map[string]string{"list": "1"}
t.Run("Normal", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(nil, urlParams)
assert.NoError(t, err)
// Not using assert.Equal to avoid having the tests break every time we add new fixtures
assert.Contains(t, rec.Body.String(), `task #1`)
assert.Contains(t, rec.Body.String(), `task #2`)
assert.Contains(t, rec.Body.String(), `task #3`)
assert.Contains(t, rec.Body.String(), `task #4`)
assert.Contains(t, rec.Body.String(), `task #5`)
assert.Contains(t, rec.Body.String(), `task #6`)
assert.Contains(t, rec.Body.String(), `task #7`)
assert.Contains(t, rec.Body.String(), `task #8`)
assert.Contains(t, rec.Body.String(), `task #9`)
assert.Contains(t, rec.Body.String(), `task #10`)
assert.Contains(t, rec.Body.String(), `task #11`)
assert.Contains(t, rec.Body.String(), `task #12`)
assert.NotContains(t, rec.Body.String(), `task #13`)
assert.NotContains(t, rec.Body.String(), `task #14`)
assert.NotContains(t, rec.Body.String(), `task #15`) // Shared via team readonly
assert.NotContains(t, rec.Body.String(), `task #16`) // Shared via team write
assert.NotContains(t, rec.Body.String(), `task #17`) // Shared via team admin
assert.NotContains(t, rec.Body.String(), `task #18`) // Shared via user readonly
assert.NotContains(t, rec.Body.String(), `task #19`) // Shared via user write
assert.NotContains(t, rec.Body.String(), `task #20`) // Shared via user admin
assert.NotContains(t, rec.Body.String(), `task #21`) // Shared via namespace team readonly
assert.NotContains(t, rec.Body.String(), `task #22`) // Shared via namespace team write
assert.NotContains(t, rec.Body.String(), `task #23`) // Shared via namespace team admin
assert.NotContains(t, rec.Body.String(), `task #24`) // Shared via namespace user readonly
assert.NotContains(t, rec.Body.String(), `task #25`) // Shared via namespace user write
assert.NotContains(t, rec.Body.String(), `task #26`) // Shared via namespace user admin
assert.Contains(t, rec.Body.String(), `task #27`)
assert.Contains(t, rec.Body.String(), `task #28`)
assert.NotContains(t, rec.Body.String(), `task #32`)
})
t.Run("Search", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"s": []string{"task #6"}}, urlParams)
assert.NoError(t, err)
assert.NotContains(t, rec.Body.String(), `task #1`)
assert.NotContains(t, rec.Body.String(), `task #2`)
assert.NotContains(t, rec.Body.String(), `task #3`)
assert.NotContains(t, rec.Body.String(), `task #4`)
assert.NotContains(t, rec.Body.String(), `task #5`)
assert.Contains(t, rec.Body.String(), `task #6`)
assert.NotContains(t, rec.Body.String(), `task #7`)
assert.NotContains(t, rec.Body.String(), `task #8`)
assert.NotContains(t, rec.Body.String(), `task #9`)
assert.NotContains(t, rec.Body.String(), `task #10`)
assert.NotContains(t, rec.Body.String(), `task #11`)
assert.NotContains(t, rec.Body.String(), `task #12`)
assert.NotContains(t, rec.Body.String(), `task #13`)
assert.NotContains(t, rec.Body.String(), `task #14`)
})
t.Run("Search case insensitive", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"s": []string{"tASk #6"}}, urlParams)
assert.NoError(t, err)
assert.NotContains(t, rec.Body.String(), `task #1`)
assert.NotContains(t, rec.Body.String(), `task #2`)
assert.NotContains(t, rec.Body.String(), `task #3`)
assert.NotContains(t, rec.Body.String(), `task #4`)
assert.NotContains(t, rec.Body.String(), `task #5`)
assert.Contains(t, rec.Body.String(), `task #6`)
assert.NotContains(t, rec.Body.String(), `task #7`)
assert.NotContains(t, rec.Body.String(), `task #8`)
assert.NotContains(t, rec.Body.String(), `task #9`)
assert.NotContains(t, rec.Body.String(), `task #10`)
assert.NotContains(t, rec.Body.String(), `task #11`)
assert.NotContains(t, rec.Body.String(), `task #12`)
assert.NotContains(t, rec.Body.String(), `task #13`)
assert.NotContains(t, rec.Body.String(), `task #14`)
})
t.Run("Sort Order", func(t *testing.T) {
// TODO: Add more cases
// should equal priority asc
t.Run("by priority", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}}, urlParams)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":null,"due_date":null,"reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_from_current_date":false,"priority":0,"start_date":null,"end_date":null,"assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"created_by":{"id":1,"username":"user1","created":null,"updated":null}},{"id":4,"title":"task #4 low prio","description":"","done":false,"done_at":null,"due_date":null,"reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_from_current_date":false,"priority":1,"start_date":null,"end_date":null,"assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-4","index":4,"related_tasks":{},"attachments":null,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"created_by":{"id":1,"username":"user1","created":null,"updated":null}},{"id":3,"title":"task #3 high prio","description":"","done":false,"done_at":null,"due_date":null,"reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_from_current_date":false,"priority":100,"start_date":null,"end_date":null,"assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"created_by":{"id":1,"username":"user1","created":null,"updated":null}}]`)
})
t.Run("by priority desc", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"desc"}}, urlParams)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":3,"title":"task #3 high prio","description":"","done":false,"done_at":null,"due_date":null,"reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_from_current_date":false,"priority":100,"start_date":null,"end_date":null,"assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"created_by":{"id":1,"username":"user1","created":null,"updated":null}},{"id":4,"title":"task #4 low prio","description":"","done":false,"done_at":null,"due_date":null,"reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_from_current_date":false,"priority":1`)
})
t.Run("by priority asc", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"asc"}}, urlParams)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":null,"due_date":null,"reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_from_current_date":false,"priority":0,"start_date":null,"end_date":null,"assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"created_by":{"id":1,"username":"user1","created":null,"updated":null}},{"id":4,"title":"task #4 low prio","description":"","done":false,"done_at":null,"due_date":null,"reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_from_current_date":false,"priority":1,"start_date":null,"end_date":null,"assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-4","index":4,"related_tasks":{},"attachments":null,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"created_by":{"id":1,"username":"user1","created":null,"updated":null}},{"id":3,"title":"task #3 high prio","description":"","done":false,"done_at":null,"due_date":null,"reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_from_current_date":false,"priority":100,"start_date":null,"end_date":null,"assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"created_by":{"id":1,"username":"user1","created":null,"updated":null}}]`)
})
// should equal duedate asc
t.Run("by due_date", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date_unix"}}, urlParams)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":null,"due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_from_current_date":false,"priority":0,"start_date":null,"end_date":null,"assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"created_by":{"id":1,"username":"user1","created":null,"updated":null}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":null,"due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_from_current_date":false,"priority":0,"start_date":null,"end_date":null,"assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"created_by":{"id":1,"username":"user1","created":null,"updated":null}}]`)
})
t.Run("by duedate desc", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date_unix"}, "order_by": []string{"desc"}}, urlParams)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":null,"due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_from_current_date":false,"priority":0,"start_date":null,"end_date":null,"assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"created_by":{"id":1,"username":"user1","created":null,"updated":null}},{"id":6,"title":"task #6 lower due date`)
})
// Due date without unix suffix
t.Run("by duedate asc without _unix suffix", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"asc"}}, urlParams)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":null,"due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_from_current_date":false,"priority":0,"start_date":null,"end_date":null,"assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"created_by":{"id":1,"username":"user1","created":null,"updated":null}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":null,"due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_from_current_date":false,"priority":0,"start_date":null,"end_date":null,"assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"created_by":{"id":1,"username":"user1","created":null,"updated":null}}]`)
})
t.Run("by due_date without _unix suffix", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}}, urlParams)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":null,"due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_from_current_date":false,"priority":0,"start_date":null,"end_date":null,"assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"created_by":{"id":1,"username":"user1","created":null,"updated":null}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":null,"due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_from_current_date":false,"priority":0,"start_date":null,"end_date":null,"assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"created_by":{"id":1,"username":"user1","created":null,"updated":null}}]`)
})
t.Run("by duedate desc without _unix suffix", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"desc"}}, urlParams)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":null,"due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_from_current_date":false,"priority":0,"start_date":null,"end_date":null,"assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"created_by":{"id":1,"username":"user1","created":null,"updated":null}},{"id":6,"title":"task #6 lower due date`)
})
t.Run("by duedate asc", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date_unix"}, "order_by": []string{"asc"}}, urlParams)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":null,"due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_from_current_date":false,"priority":0,"start_date":null,"end_date":null,"assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"created_by":{"id":1,"username":"user1","created":null,"updated":null}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":null,"due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_from_current_date":false,"priority":0,"start_date":null,"end_date":null,"assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"created_by":{"id":1,"username":"user1","created":null,"updated":null}}]`)
})
t.Run("invalid sort parameter", func(t *testing.T) {
_, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"loremipsum"}}, urlParams)
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeInvalidTaskField)
})
t.Run("invalid sort order", func(t *testing.T) {
_, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"id"}, "order_by": []string{"loremipsum"}}, urlParams)
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeInvalidSortOrder)
})
t.Run("invalid parameter", func(t *testing.T) {
// Invalid parameter should not sort at all
rec, err := testHandler.testReadAllWithUser(url.Values{"sort": []string{"loremipsum"}}, urlParams)
assert.NoError(t, err)
assert.NotContains(t, rec.Body.String(), `[{"id":3,"title":"task #3 high prio","description":"","done":false,"due_date":0,"reminder_dates":null,"repeat_after":0,"repeat_from_current_date":false,"priority":100,"start_date":0,"end_date":0,"assignees":null,"labels":null,"hex_color":"","created":1543626724,"updated":1543626724,"created_by":{"id":0,"username":"","email":"","created":0,"updated":0}},{"id":4,"title":"task #4 low prio","description":"","done":false,"due_date":0,"reminder_dates":null,"repeat_after":0,"repeat_from_current_date":false,"priority":1`)
assert.NotContains(t, rec.Body.String(), `{"id":4,"title":"task #4 low prio","description":"","done":false,"due_date":0,"reminder_dates":null,"repeat_after":0,"repeat_from_current_date":false,"priority":1,"start_date":0,"end_date":0,"assignees":null,"labels":null,"hex_color":"","created":1543626724,"updated":1543626724,"created_by":{"id":0,"username":"","email":"","created":0,"updated":0}},{"id":3,"title":"task #3 high prio","description":"","done":false,"due_date":0,"reminder_dates":null,"repeat_after":0,"repeat_from_current_date":false,"priority":100,"start_date":0,"end_date":0,"assignees":null,"labels":null,"created":1543626724,"updated":1543626724,"created_by":{"id":0,"username":"","email":"","created":0,"updated":0}}]`)
assert.NotContains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"due_date":1543636724,"reminder_dates":null,"repeat_after":0,"repeat_from_current_date":false,"priority":0,"start_date":0,"end_date":0,"assignees":null,"labels":null,"hex_color":"","created":1543626724,"updated":1543626724,"created_by":{"id":0,"username":"","email":"","created":0,"updated":0}},{"id":6,"title":"task #6 lower due date"`)
assert.NotContains(t, rec.Body.String(), `{"id":6,"title":"task #6 lower due date","description":"","done":false,"due_date":1543616724,"reminder_dates":null,"repeat_after":0,"repeat_from_current_date":false,"priority":0,"start_date":0,"end_date":0,"assignees":null,"labels":null,"hex_color":"","created":1543626724,"updated":1543626724,"created_by":{"id":0,"username":"","email":"","created":0,"updated":0}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"due_date":1543636724,"reminder_dates":null,"repeat_after":0,"repeat_from_current_date":false,"priority":0,"start_date":0,"end_date":0,"assignees":null,"labels":null,"created":1543626724,"updated":1543626724,"created_by":{"id":0,"username":"","email":"","created":0,"updated":0}}]`)
})
})
t.Run("Date range", func(t *testing.T) {
t.Run("start and end date", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(
url.Values{
"filter_by": []string{"start_date", "end_date", "due_date"},
"filter_value": []string{"1544500000", "1544700001", "1543500000"},
"filter_comparator": []string{"greater", "less", "greater"},
},
urlParams,
)
assert.NoError(t, err)
assert.NotContains(t, rec.Body.String(), `task #1`)
assert.NotContains(t, rec.Body.String(), `task #2`)
assert.NotContains(t, rec.Body.String(), `task #3`)
assert.NotContains(t, rec.Body.String(), `task #4`)
assert.Contains(t, rec.Body.String(), `task #5`)
assert.Contains(t, rec.Body.String(), `task #6`)
assert.Contains(t, rec.Body.String(), `task #7`)
assert.Contains(t, rec.Body.String(), `task #8`)
assert.Contains(t, rec.Body.String(), `task #9`)
assert.NotContains(t, rec.Body.String(), `task #10`)
assert.NotContains(t, rec.Body.String(), `task #11`)
assert.NotContains(t, rec.Body.String(), `task #12`)
assert.NotContains(t, rec.Body.String(), `task #13`)
assert.NotContains(t, rec.Body.String(), `task #14`)
})
t.Run("start date only", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(
url.Values{
"filter_by": []string{"start_date"},
"filter_value": []string{"1540000000"},
"filter_comparator": []string{"greater"},
},
urlParams,
)
assert.NoError(t, err)
assert.NotContains(t, rec.Body.String(), `task #1`)
assert.NotContains(t, rec.Body.String(), `task #2`)
assert.NotContains(t, rec.Body.String(), `task #3`)
assert.NotContains(t, rec.Body.String(), `task #4`)
assert.NotContains(t, rec.Body.String(), `task #5`)
assert.NotContains(t, rec.Body.String(), `task #6`)
assert.Contains(t, rec.Body.String(), `task #7`)
assert.NotContains(t, rec.Body.String(), `task #8`)
assert.Contains(t, rec.Body.String(), `task #9`)
assert.NotContains(t, rec.Body.String(), `task #10`)
assert.NotContains(t, rec.Body.String(), `task #11`)
assert.NotContains(t, rec.Body.String(), `task #12`)
assert.NotContains(t, rec.Body.String(), `task #13`)
assert.NotContains(t, rec.Body.String(), `task #14`)
})
t.Run("end date only", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(
url.Values{
"filter_by": []string{"end_date"},
"filter_value": []string{"1544700001"},
"filter_comparator": []string{"greater"},
},
urlParams,
)
assert.NoError(t, err)
// If no start date but an end date is specified, this should be null
// since we don't have any tasks in the fixtures with an end date >
// the current date.
assert.Equal(t, "[]\n", rec.Body.String())
})
})
})
t.Run("ReadAll for all tasks", func(t *testing.T) {
t.Run("Normal", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(nil, nil)
assert.NoError(t, err)
// Not using assert.Equal to avoid having the tests break every time we add new fixtures
assert.Contains(t, rec.Body.String(), `task #1`)
assert.Contains(t, rec.Body.String(), `task #2`)
assert.Contains(t, rec.Body.String(), `task #3`)
assert.Contains(t, rec.Body.String(), `task #4`)
assert.Contains(t, rec.Body.String(), `task #5`)
assert.Contains(t, rec.Body.String(), `task #6`)
assert.Contains(t, rec.Body.String(), `task #7`)
assert.Contains(t, rec.Body.String(), `task #8`)
assert.Contains(t, rec.Body.String(), `task #9`)
assert.Contains(t, rec.Body.String(), `task #10`)
assert.Contains(t, rec.Body.String(), `task #11`)
assert.Contains(t, rec.Body.String(), `task #12`)
assert.NotContains(t, rec.Body.String(), `task #13`)
assert.NotContains(t, rec.Body.String(), `task #14`)
assert.NotContains(t, rec.Body.String(), `task #13`)
assert.NotContains(t, rec.Body.String(), `task #14`)
assert.Contains(t, rec.Body.String(), `task #15`) // Shared via team readonly
assert.Contains(t, rec.Body.String(), `task #16`) // Shared via team write
assert.Contains(t, rec.Body.String(), `task #17`) // Shared via team admin
assert.Contains(t, rec.Body.String(), `task #18`) // Shared via user readonly
assert.Contains(t, rec.Body.String(), `task #19`) // Shared via user write
assert.Contains(t, rec.Body.String(), `task #20`) // Shared via user admin
assert.Contains(t, rec.Body.String(), `task #21`) // Shared via namespace team readonly
assert.Contains(t, rec.Body.String(), `task #22`) // Shared via namespace team write
assert.Contains(t, rec.Body.String(), `task #23`) // Shared via namespace team admin
assert.Contains(t, rec.Body.String(), `task #24`) // Shared via namespace user readonly
assert.Contains(t, rec.Body.String(), `task #25`) // Shared via namespace user write
assert.Contains(t, rec.Body.String(), `task #26`) // Shared via namespace user admin
// TODO: Add some cases where the user has access to the list, somhow shared
})
t.Run("Search", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"s": []string{"task #6"}}, nil)
assert.NoError(t, err)
assert.NotContains(t, rec.Body.String(), `task #1`)
assert.NotContains(t, rec.Body.String(), `task #2`)
assert.NotContains(t, rec.Body.String(), `task #3`)
assert.NotContains(t, rec.Body.String(), `task #4`)
assert.NotContains(t, rec.Body.String(), `task #5`)
assert.Contains(t, rec.Body.String(), `task #6`)
assert.NotContains(t, rec.Body.String(), `task #7`)
assert.NotContains(t, rec.Body.String(), `task #8`)
assert.NotContains(t, rec.Body.String(), `task #9`)
assert.NotContains(t, rec.Body.String(), `task #10`)
assert.NotContains(t, rec.Body.String(), `task #11`)
assert.NotContains(t, rec.Body.String(), `task #12`)
assert.NotContains(t, rec.Body.String(), `task #13`)
assert.NotContains(t, rec.Body.String(), `task #14`)
})
t.Run("Sort Order", func(t *testing.T) {
// should equal priority asc
t.Run("by priority", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}}, nil)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":null,"due_date":null,"reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_from_current_date":false,"priority":0,"start_date":null,"end_date":null,"assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"created_by":{"id":1,"username":"user1","created":null,"updated":null}},{"id":4,"title":"task #4 low prio","description":"","done":false,"done_at":null,"due_date":null,"reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_from_current_date":false,"priority":1,"start_date":null,"end_date":null,"assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-4","index":4,"related_tasks":{},"attachments":null,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"created_by":{"id":1,"username":"user1","created":null,"updated":null}},{"id":3,"title":"task #3 high prio","description":"","done":false,"done_at":null,"due_date":null,"reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_from_current_date":false,"priority":100,"start_date":null,"end_date":null,"assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"created_by":{"id":1,"username":"user1","created":null,"updated":null}}]`)
})
t.Run("by priority desc", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"desc"}}, nil)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":3,"title":"task #3 high prio","description":"","done":false,"done_at":null,"due_date":null,"reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_from_current_date":false,"priority":100,"start_date":null,"end_date":null,"assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"created_by":{"id":1,"username":"user1","created":null,"updated":null}},{"id":4,"title":"task #4 low prio","description":"","done":false,"done_at":null,"due_date":null,"reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_from_current_date":false,"priority":1`)
})
t.Run("by priority asc", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"asc"}}, nil)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":null,"due_date":null,"reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_from_current_date":false,"priority":0,"start_date":null,"end_date":null,"assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"created_by":{"id":1,"username":"user1","created":null,"updated":null}},{"id":4,"title":"task #4 low prio","description":"","done":false,"done_at":null,"due_date":null,"reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_from_current_date":false,"priority":1,"start_date":null,"end_date":null,"assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-4","index":4,"related_tasks":{},"attachments":null,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"created_by":{"id":1,"username":"user1","created":null,"updated":null}},{"id":3,"title":"task #3 high prio","description":"","done":false,"done_at":null,"due_date":null,"reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_from_current_date":false,"priority":100,"start_date":null,"end_date":null,"assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"created_by":{"id":1,"username":"user1","created":null,"updated":null}}]`)
})
// should equal duedate asc
t.Run("by due_date", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date_unix"}}, nil)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":null,"due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_from_current_date":false,"priority":0,"start_date":null,"end_date":null,"assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"created_by":{"id":1,"username":"user1","created":null,"updated":null}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":null,"due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_from_current_date":false,"priority":0,"start_date":null,"end_date":null,"assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"created_by":{"id":1,"username":"user1","created":null,"updated":null}}]`)
})
t.Run("by duedate desc", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date_unix"}, "order_by": []string{"desc"}}, nil)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":null,"due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_from_current_date":false,"priority":0,"start_date":null,"end_date":null,"assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"created_by":{"id":1,"username":"user1","created":null,"updated":null}},{"id":6,"title":"task #6 lower due date`)
})
t.Run("by duedate asc", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date_unix"}, "order_by": []string{"asc"}}, nil)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":null,"due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_from_current_date":false,"priority":0,"start_date":null,"end_date":null,"assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"created_by":{"id":1,"username":"user1","created":null,"updated":null}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":null,"due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_from_current_date":false,"priority":0,"start_date":null,"end_date":null,"assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"created_by":{"id":1,"username":"user1","created":null,"updated":null}}]`)
})
t.Run("invalid parameter", func(t *testing.T) {
// Invalid parameter should not sort at all
rec, err := testHandler.testReadAllWithUser(url.Values{"sort": []string{"loremipsum"}}, nil)
assert.NoError(t, err)
assert.NotContains(t, rec.Body.String(), `[{"id":3,"title":"task #3 high prio","description":"","done":false,"due_date":0,"reminder_dates":null,"repeat_after":0,"repeat_from_current_date":false,"priority":100,"start_date":0,"end_date":0,"assignees":null,"labels":null,"hex_color":"","created":1543626724,"updated":1543626724,"created_by":{"id":0,"username":"","email":"","created":0,"updated":0}},{"id":4,"title":"task #4 low prio","description":"","done":false,"due_date":0,"reminder_dates":null,"repeat_after":0,"repeat_from_current_date":false,"priority":1`)
assert.NotContains(t, rec.Body.String(), `{"id":4,"title":"task #4 low prio","description":"","done":false,"due_date":0,"reminder_dates":null,"repeat_after":0,"repeat_from_current_date":false,"priority":1,"start_date":0,"end_date":0,"assignees":null,"labels":null,"hex_color":"","created":1543626724,"updated":1543626724,"created_by":{"id":0,"username":"","email":"","created":0,"updated":0}},{"id":3,"title":"task #3 high prio","description":"","done":false,"due_date":0,"reminder_dates":null,"repeat_after":0,"repeat_from_current_date":false,"priority":100,"start_date":0,"end_date":0,"assignees":null,"labels":null,"created":1543626724,"updated":1543626724,"created_by":{"id":0,"username":"","email":"","created":0,"updated":0}}]`)
assert.NotContains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"due_date":1543636724,"reminder_dates":null,"repeat_after":0,"repeat_from_current_date":false,"priority":0,"start_date":0,"end_date":0,"assignees":null,"labels":null,"hex_color":"","created":1543626724,"updated":1543626724,"created_by":{"id":0,"username":"","email":"","created":0,"updated":0}},{"id":6,"title":"task #6 lower due date"`)
assert.NotContains(t, rec.Body.String(), `{"id":6,"title":"task #6 lower due date","description":"","done":false,"due_date":1543616724,"reminder_dates":null,"repeat_after":0,"repeat_from_current_date":false,"priority":0,"start_date":0,"end_date":0,"assignees":null,"labels":null,"hex_color":"","created":1543626724,"updated":1543626724,"created_by":{"id":0,"username":"","email":"","created":0,"updated":0}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"due_date":1543636724,"reminder_dates":null,"repeat_after":0,"repeat_from_current_date":false,"priority":0,"start_date":0,"end_date":0,"assignees":null,"labels":null,"created":1543626724,"updated":1543626724,"created_by":{"id":0,"username":"","email":"","created":0,"updated":0}}]`)
})
})
t.Run("Date range", func(t *testing.T) {
t.Run("start and end date", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(
url.Values{
"filter_by": []string{"start_date", "end_date", "due_date"},
"filter_value": []string{"1544500000", "1544700001", "1543500000"},
"filter_comparator": []string{"greater", "less", "greater"},
},
nil,
)
assert.NoError(t, err)
assert.NotContains(t, rec.Body.String(), `task #1`)
assert.NotContains(t, rec.Body.String(), `task #2`)
assert.NotContains(t, rec.Body.String(), `task #3`)
assert.NotContains(t, rec.Body.String(), `task #4`)
assert.Contains(t, rec.Body.String(), `task #5`)
assert.Contains(t, rec.Body.String(), `task #6`)
assert.Contains(t, rec.Body.String(), `task #7`)
assert.Contains(t, rec.Body.String(), `task #8`)
assert.Contains(t, rec.Body.String(), `task #9`)
assert.NotContains(t, rec.Body.String(), `task #10`)
assert.NotContains(t, rec.Body.String(), `task #11`)
assert.NotContains(t, rec.Body.String(), `task #12`)
assert.NotContains(t, rec.Body.String(), `task #13`)
assert.NotContains(t, rec.Body.String(), `task #14`)
})
t.Run("start date only", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(
url.Values{
"filter_by": []string{"start_date"},
"filter_value": []string{"1540000000"},
"filter_comparator": []string{"greater"},
},
nil,
)
assert.NoError(t, err)
assert.NotContains(t, rec.Body.String(), `task #1`)
assert.NotContains(t, rec.Body.String(), `task #2`)
assert.NotContains(t, rec.Body.String(), `task #3`)
assert.NotContains(t, rec.Body.String(), `task #4`)
assert.NotContains(t, rec.Body.String(), `task #5`)
assert.NotContains(t, rec.Body.String(), `task #6`)
assert.Contains(t, rec.Body.String(), `task #7`)
assert.NotContains(t, rec.Body.String(), `task #8`)
assert.Contains(t, rec.Body.String(), `task #9`)
assert.NotContains(t, rec.Body.String(), `task #10`)
assert.NotContains(t, rec.Body.String(), `task #11`)
assert.NotContains(t, rec.Body.String(), `task #12`)
assert.NotContains(t, rec.Body.String(), `task #13`)
assert.NotContains(t, rec.Body.String(), `task #14`)
})
t.Run("end date only", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(
url.Values{
"filter_by": []string{"end_date"},
"filter_value": []string{"1544700001"},
"filter_comparator": []string{"greater"},
},
nil,
)
assert.NoError(t, err)
// If no start date but an end date is specified, this should be null
// since we don't have any tasks in the fixtures with an end date >
// the current date.
assert.Equal(t, "[]\n", rec.Body.String())
})
})
})
}

View File

@ -0,0 +1,284 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package integrations
import (
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/web/handler"
"github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert"
"testing"
)
func TestTaskComments(t *testing.T) {
testHandler := webHandlerTest{
user: &testuser1,
strFunc: func() handler.CObject {
return &models.TaskComment{}
},
t: t,
}
// Only run specific nested tests:
// ^TestTaskComments$/^Update$/^Update_task_items$/^Removing_Assignees_null$
t.Run("Update", func(t *testing.T) {
t.Run("Normal", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "1", "commentid": "1"}, `{"comment":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"comment":"Lorem Ipsum"`)
})
t.Run("Nonexisting", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "99999", "commentid": "9999"}, `{"comment":"Lorem Ipsum"}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeTaskDoesNotExist)
})
t.Run("Rights check", func(t *testing.T) {
t.Run("Forbidden", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "14", "commentid": "2"}, `{"comment":"Lorem Ipsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Team readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "15", "commentid": "3"}, `{"comment":"Lorem Ipsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Team write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "16", "commentid": "4"}, `{"comment":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"comment":"Lorem Ipsum"`)
})
t.Run("Shared Via Team admin", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "17", "commentid": "5"}, `{"comment":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"comment":"Lorem Ipsum"`)
})
t.Run("Shared Via User readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "18", "commentid": "6"}, `{"comment":"Lorem Ipsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via User write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "19", "commentid": "7"}, `{"comment":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"comment":"Lorem Ipsum"`)
})
t.Run("Shared Via User admin", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "20", "commentid": "8"}, `{"comment":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"comment":"Lorem Ipsum"`)
})
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "21", "commentid": "9"}, `{"comment":"Lorem Ipsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "22", "commentid": "10"}, `{"comment":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"comment":"Lorem Ipsum"`)
})
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "23", "commentid": "11"}, `{"comment":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"comment":"Lorem Ipsum"`)
})
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "24", "commentid": "12"}, `{"comment":"Lorem Ipsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "25", "commentid": "13"}, `{"comment":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"comment":"Lorem Ipsum"`)
})
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "26", "commentid": "14"}, `{"comment":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"comment":"Lorem Ipsum"`)
})
})
})
t.Run("Delete", func(t *testing.T) {
t.Run("Normal", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "1", "commentid": "1"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
})
t.Run("Nonexisting", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "99999", "commentid": "9999"})
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeTaskDoesNotExist)
})
t.Run("Rights check", func(t *testing.T) {
t.Run("Forbidden", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "14", "commentid": "2"})
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Team readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "15", "commentid": "3"})
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Team write", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "16", "commentid": "4"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
})
t.Run("Shared Via Team admin", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "17", "commentid": "5"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
})
t.Run("Shared Via User readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "18", "commentid": "6"})
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via User write", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "19", "commentid": "7"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
})
t.Run("Shared Via User admin", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "20", "commentid": "8"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
})
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "21", "commentid": "9"})
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "22", "commentid": "10"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
})
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "23", "commentid": "11"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
})
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "24", "commentid": "12"})
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "25", "commentid": "13"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
})
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "26", "commentid": "14"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
})
})
})
t.Run("Create", func(t *testing.T) {
t.Run("Normal", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "1"}, `{"comment":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"comment":"Lorem Ipsum"`)
})
t.Run("Nonexisting", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "9999"}, `{"comment":"Lorem Ipsum"}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeTaskDoesNotExist)
})
t.Run("Rights check", func(t *testing.T) {
t.Run("Forbidden", func(t *testing.T) {
// Owned by user13
_, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "34"}, `{"comment":"Lorem Ipsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Team readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "15"}, `{"comment":"Lorem Ipsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Team write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "16"}, `{"comment":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"comment":"Lorem Ipsum"`)
})
t.Run("Shared Via Team admin", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "17"}, `{"comment":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"comment":"Lorem Ipsum"`)
})
t.Run("Shared Via User readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "18"}, `{"comment":"Lorem Ipsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via User write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "19"}, `{"comment":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"comment":"Lorem Ipsum"`)
})
t.Run("Shared Via User admin", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "20"}, `{"comment":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"comment":"Lorem Ipsum"`)
})
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "21"}, `{"comment":"Lorem Ipsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "22"}, `{"comment":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"comment":"Lorem Ipsum"`)
})
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "23"}, `{"comment":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"comment":"Lorem Ipsum"`)
})
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "24"}, `{"comment":"Lorem Ipsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "25"}, `{"comment":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"comment":"Lorem Ipsum"`)
})
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "26"}, `{"comment":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"comment":"Lorem Ipsum"`)
})
})
})
}

View File

@ -1,18 +1,18 @@
// Vikunja is a todo-list application to facilitate your life.
// Copyright 2019 Vikunja and contributors. All rights reserved.
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// 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 General Public License for more details.
// 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package integrations
@ -21,468 +21,368 @@ import (
"code.vikunja.io/web/handler"
"github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert"
"net/url"
"testing"
)
func TestListTask(t *testing.T) {
func TestTask(t *testing.T) {
testHandler := webHandlerTest{
user: &testuser1,
strFunc: func() handler.CObject {
return &models.ListTask{}
return &models.Task{}
},
t: t,
}
// Only run specific nested tests:
// ^TestListTask$/^Update$/^Update_task_items$/^Removing_Assignees_null$
t.Run("ReadAll", func(t *testing.T) {
t.Run("Normal", func(t *testing.T) {
rec, err := testHandler.testReadAll(nil, nil)
assert.NoError(t, err)
// Not using assert.Equal to avoid having the tests break every time we add new fixtures
assert.Contains(t, rec.Body.String(), `task #1`)
assert.Contains(t, rec.Body.String(), `task #2`)
assert.Contains(t, rec.Body.String(), `task #3`)
assert.Contains(t, rec.Body.String(), `task #4`)
assert.Contains(t, rec.Body.String(), `task #5`)
assert.Contains(t, rec.Body.String(), `task #6`)
assert.Contains(t, rec.Body.String(), `task #7`)
assert.Contains(t, rec.Body.String(), `task #8`)
assert.Contains(t, rec.Body.String(), `task #9`)
assert.Contains(t, rec.Body.String(), `task #10`)
assert.Contains(t, rec.Body.String(), `task #11`)
assert.Contains(t, rec.Body.String(), `task #12`)
assert.NotContains(t, rec.Body.String(), `task #13`)
assert.NotContains(t, rec.Body.String(), `task #14`)
// TODO: add more tasks, since the whole point of this is to get all tasks in all lists where the user
// has at least read access
})
t.Run("Search", func(t *testing.T) {
rec, err := testHandler.testReadAll(url.Values{"s": []string{"task #6"}}, nil)
assert.NoError(t, err)
assert.NotContains(t, rec.Body.String(), `task #1`)
assert.NotContains(t, rec.Body.String(), `task #2`)
assert.NotContains(t, rec.Body.String(), `task #3`)
assert.NotContains(t, rec.Body.String(), `task #4`)
assert.NotContains(t, rec.Body.String(), `task #5`)
assert.Contains(t, rec.Body.String(), `task #6`)
assert.NotContains(t, rec.Body.String(), `task #7`)
assert.NotContains(t, rec.Body.String(), `task #8`)
assert.NotContains(t, rec.Body.String(), `task #9`)
assert.NotContains(t, rec.Body.String(), `task #10`)
assert.NotContains(t, rec.Body.String(), `task #11`)
assert.NotContains(t, rec.Body.String(), `task #12`)
assert.NotContains(t, rec.Body.String(), `task #13`)
assert.NotContains(t, rec.Body.String(), `task #14`)
})
t.Run("Sort Order", func(t *testing.T) {
// should equal priority desc
t.Run("by priority", func(t *testing.T) {
rec, err := testHandler.testReadAll(url.Values{"sort": []string{"priority"}}, nil)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":3,"text":"task #3 high prio","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"parentTaskID":0,"priority":100,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","subtasks":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","created":0,"updated":0}},{"id":4,"text":"task #4 low prio","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"parentTaskID":0,"priority":1`)
})
t.Run("by priority desc", func(t *testing.T) {
rec, err := testHandler.testReadAll(url.Values{"sort": []string{"prioritydesc"}}, nil)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":3,"text":"task #3 high prio","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"parentTaskID":0,"priority":100,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","subtasks":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","created":0,"updated":0}},{"id":4,"text":"task #4 low prio","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"parentTaskID":0,"priority":1`)
})
t.Run("by priority asc", func(t *testing.T) {
rec, err := testHandler.testReadAll(url.Values{"sort": []string{"priorityasc"}}, nil)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `{"id":31,"text":"task #31 with color","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"parentTaskID":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"f0f0f0","subtasks":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","created":0,"updated":0}},{"id":4,"text":"task #4 low prio","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"parentTaskID":0,"priority":1,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","subtasks":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","created":0,"updated":0}},{"id":3,"text":"task #3 high prio","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"parentTaskID":0,"priority":100,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","subtasks":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","created":0,"updated":0}}]`)
})
// should equal duedate desc
t.Run("by duedate", func(t *testing.T) {
rec, err := testHandler.testReadAll(url.Values{"sort": []string{"dueadate"}}, nil)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":5,"text":"task #5 higher due date","description":"","done":false,"doneAt":0,"dueDate":1543636724,"reminderDates":null,"listID":1,"repeatAfter":0,"parentTaskID":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","subtasks":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","created":0,"updated":0}},{"id":6,"text":"task #6 lower due date"`)
})
t.Run("by duedate desc", func(t *testing.T) {
rec, err := testHandler.testReadAll(url.Values{"sort": []string{"dueadatedesc"}}, nil)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":5,"text":"task #5 higher due date","description":"","done":false,"doneAt":0,"dueDate":1543636724,"reminderDates":null,"listID":1,"repeatAfter":0,"parentTaskID":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","subtasks":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","created":0,"updated":0}},{"id":6,"text":"task #6 lower due date"`)
})
t.Run("by duedate asc", func(t *testing.T) {
rec, err := testHandler.testReadAll(url.Values{"sort": []string{"duedateasc"}}, nil)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `{"id":6,"text":"task #6 lower due date","description":"","done":false,"doneAt":0,"dueDate":1543616724,"reminderDates":null,"listID":1,"repeatAfter":0,"parentTaskID":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","subtasks":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","created":0,"updated":0}},{"id":5,"text":"task #5 higher due date","description":"","done":false,"doneAt":0,"dueDate":1543636724,"reminderDates":null,"listID":1,"repeatAfter":0,"parentTaskID":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","subtasks":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","created":0,"updated":0}}]`)
})
t.Run("invalid parameter", func(t *testing.T) {
// Invalid parameter should not sort at all
rec, err := testHandler.testReadAll(url.Values{"sort": []string{"loremipsum"}}, nil)
assert.NoError(t, err)
assert.NotContains(t, rec.Body.String(), `[{"id":3,"text":"task #3 high prio","description":"","done":false,"dueDate":0,"reminderDates":null,"repeatAfter":0,"parentTaskID":0,"priority":100,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","subtasks":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":0,"username":"","email":"","created":0,"updated":0}},{"id":4,"text":"task #4 low prio","description":"","done":false,"dueDate":0,"reminderDates":null,"repeatAfter":0,"parentTaskID":0,"priority":1`)
assert.NotContains(t, rec.Body.String(), `{"id":4,"text":"task #4 low prio","description":"","done":false,"dueDate":0,"reminderDates":null,"repeatAfter":0,"parentTaskID":0,"priority":1,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","subtasks":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":0,"username":"","email":"","created":0,"updated":0}},{"id":3,"text":"task #3 high prio","description":"","done":false,"dueDate":0,"reminderDates":null,"repeatAfter":0,"parentTaskID":0,"priority":100,"startDate":0,"endDate":0,"assignees":null,"labels":null,"subtasks":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":0,"username":"","email":"","created":0,"updated":0}}]`)
assert.NotContains(t, rec.Body.String(), `[{"id":5,"text":"task #5 higher due date","description":"","done":false,"dueDate":1543636724,"reminderDates":null,"repeatAfter":0,"parentTaskID":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","subtasks":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":0,"username":"","email":"","created":0,"updated":0}},{"id":6,"text":"task #6 lower due date"`)
assert.NotContains(t, rec.Body.String(), `{"id":6,"text":"task #6 lower due date","description":"","done":false,"dueDate":1543616724,"reminderDates":null,"repeatAfter":0,"parentTaskID":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","subtasks":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":0,"username":"","email":"","created":0,"updated":0}},{"id":5,"text":"task #5 higher due date","description":"","done":false,"dueDate":1543636724,"reminderDates":null,"repeatAfter":0,"parentTaskID":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"subtasks":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":0,"username":"","email":"","created":0,"updated":0}}]`)
})
})
t.Run("Date range", func(t *testing.T) {
t.Run("start and end date", func(t *testing.T) {
rec, err := testHandler.testReadAll(url.Values{"startdate": []string{"1540000000"}, "enddate": []string{"1544700001"}}, nil)
assert.NoError(t, err)
assert.NotContains(t, rec.Body.String(), `task #1`)
assert.NotContains(t, rec.Body.String(), `task #2`)
assert.NotContains(t, rec.Body.String(), `task #3`)
assert.NotContains(t, rec.Body.String(), `task #4`)
assert.Contains(t, rec.Body.String(), `task #5`)
assert.Contains(t, rec.Body.String(), `task #6`)
assert.Contains(t, rec.Body.String(), `task #7`)
assert.Contains(t, rec.Body.String(), `task #8`)
assert.Contains(t, rec.Body.String(), `task #9`)
assert.NotContains(t, rec.Body.String(), `task #10`)
assert.NotContains(t, rec.Body.String(), `task #11`)
assert.NotContains(t, rec.Body.String(), `task #12`)
assert.NotContains(t, rec.Body.String(), `task #13`)
assert.NotContains(t, rec.Body.String(), `task #14`)
})
t.Run("start date only", func(t *testing.T) {
rec, err := testHandler.testReadAll(url.Values{"startdate": []string{"1540000000"}}, nil)
assert.NoError(t, err)
assert.NotContains(t, rec.Body.String(), `task #1`)
assert.NotContains(t, rec.Body.String(), `task #2`)
assert.NotContains(t, rec.Body.String(), `task #3`)
assert.NotContains(t, rec.Body.String(), `task #4`)
assert.Contains(t, rec.Body.String(), `task #5`)
assert.Contains(t, rec.Body.String(), `task #6`)
assert.Contains(t, rec.Body.String(), `task #7`)
assert.Contains(t, rec.Body.String(), `task #8`)
assert.Contains(t, rec.Body.String(), `task #9`)
assert.NotContains(t, rec.Body.String(), `task #10`)
assert.NotContains(t, rec.Body.String(), `task #11`)
assert.NotContains(t, rec.Body.String(), `task #12`)
assert.NotContains(t, rec.Body.String(), `task #13`)
assert.NotContains(t, rec.Body.String(), `task #14`)
})
t.Run("end date only", func(t *testing.T) {
rec, err := testHandler.testReadAll(url.Values{"enddate": []string{"1544700001"}}, nil)
assert.NoError(t, err)
// If no start date but an end date is specified, this should be null
// since we don't have any tasks in the fixtures with an end date >
// the current date.
assert.Equal(t, "null\n", rec.Body.String())
})
})
})
// ^TestTask$/^Update$/^Update_task_items$/^Removing_Assignees_null$
t.Run("Update", func(t *testing.T) {
t.Run("Update task items", func(t *testing.T) {
t.Run("Text", func(t *testing.T) {
rec, err := testHandler.testUpdate(nil, map[string]string{"listtask": "1"}, `{"text":"Lorem Ipsum"}`)
t.Run("Title", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"text":"Lorem Ipsum"`)
assert.NotContains(t, rec.Body.String(), `"text":"task #1"`)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
assert.NotContains(t, rec.Body.String(), `"title":"task #1"`)
})
t.Run("Description", func(t *testing.T) {
rec, err := testHandler.testUpdate(nil, map[string]string{"listtask": "1"}, `{"description":"Dolor sit amet"}`)
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"description":"Dolor sit amet"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"description":"Dolor sit amet"`)
assert.NotContains(t, rec.Body.String(), `"description":"Lorem Ipsum"`)
})
t.Run("Description to empty", func(t *testing.T) {
rec, err := testHandler.testUpdate(nil, map[string]string{"listtask": "1"}, `{"description":""}`)
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"description":""}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"description":""`)
assert.NotContains(t, rec.Body.String(), `"description":"Lorem Ipsum"`)
})
t.Run("Done", func(t *testing.T) {
rec, err := testHandler.testUpdate(nil, map[string]string{"listtask": "1"}, `{"done":true}`)
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"done":true}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"done":true`)
assert.NotContains(t, rec.Body.String(), `"done":false`)
})
t.Run("Undone", func(t *testing.T) {
rec, err := testHandler.testUpdate(nil, map[string]string{"listtask": "2"}, `{"done":false}`)
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "2"}, `{"done":false}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"done":false`)
assert.NotContains(t, rec.Body.String(), `"done":true`)
})
t.Run("Due date", func(t *testing.T) {
rec, err := testHandler.testUpdate(nil, map[string]string{"listtask": "1"}, `{"dueDate": 123456}`)
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"due_date": "2020-02-10T10:00:00Z"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"dueDate":123456`)
assert.NotContains(t, rec.Body.String(), `"dueDate":0`)
assert.Contains(t, rec.Body.String(), `"due_date":"2020-02-10T10:00:00Z"`)
assert.NotContains(t, rec.Body.String(), `"due_date":0`)
})
t.Run("Due date unset", func(t *testing.T) {
rec, err := testHandler.testUpdate(nil, map[string]string{"listtask": "5"}, `{"dueDate": 0}`)
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "5"}, `{"due_date": null}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"dueDate":0`)
assert.NotContains(t, rec.Body.String(), `"dueDate":1543636724`)
assert.Contains(t, rec.Body.String(), `"due_date":null`)
assert.NotContains(t, rec.Body.String(), `"due_date":"2020-02-10T10:00:00Z"`)
})
t.Run("Reminders", func(t *testing.T) {
rec, err := testHandler.testUpdate(nil, map[string]string{"listtask": "1"}, `{"reminderDates": [1555508227,1555511000]}`)
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"reminder_dates": ["2020-02-10T10:00:00Z","2020-02-11T10:00:00Z"]}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"reminderDates":[1555508227,1555511000]`)
assert.NotContains(t, rec.Body.String(), `"reminderDates": null`)
assert.Contains(t, rec.Body.String(), `"reminder_dates":["2020-02-10T10:00:00Z","2020-02-11T10:00:00Z"]`)
assert.NotContains(t, rec.Body.String(), `"reminder_dates": null`)
})
t.Run("Reminders unset to empty array", func(t *testing.T) {
rec, err := testHandler.testUpdate(nil, map[string]string{"listtask": "27"}, `{"reminderDates": []}`)
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "27"}, `{"reminder_dates": []}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"reminderDates":null`)
assert.NotContains(t, rec.Body.String(), `"reminderDates":[1543626724,1543626824]`)
assert.Contains(t, rec.Body.String(), `"reminder_dates":null`)
assert.NotContains(t, rec.Body.String(), `"reminder_dates":[1543626724,1543626824]`)
})
t.Run("Reminders unset to null", func(t *testing.T) {
rec, err := testHandler.testUpdate(nil, map[string]string{"listtask": "27"}, `{"reminderDates": null}`)
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "27"}, `{"reminder_dates": null}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"reminderDates":null`)
assert.NotContains(t, rec.Body.String(), `"reminderDates":[1543626724,1543626824]`)
assert.Contains(t, rec.Body.String(), `"reminder_dates":null`)
assert.NotContains(t, rec.Body.String(), `"reminder_dates":[1543626724,1543626824]`)
})
t.Run("Repeat after", func(t *testing.T) {
rec, err := testHandler.testUpdate(nil, map[string]string{"listtask": "1"}, `{"repeatAfter":3600}`)
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"repeat_after":3600}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"repeatAfter":3600`)
assert.NotContains(t, rec.Body.String(), `"repeatAfter":0`)
assert.Contains(t, rec.Body.String(), `"repeat_after":3600`)
assert.NotContains(t, rec.Body.String(), `"repeat_after":0`)
})
t.Run("Repeat after unset", func(t *testing.T) {
rec, err := testHandler.testUpdate(nil, map[string]string{"listtask": "28"}, `{"repeatAfter":0}`)
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "28"}, `{"repeat_after":0}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"repeatAfter":0`)
assert.NotContains(t, rec.Body.String(), `"repeatAfter":3600`)
assert.Contains(t, rec.Body.String(), `"repeat_after":0`)
assert.NotContains(t, rec.Body.String(), `"repeat_after":3600`)
})
t.Run("Repeat after update done", func(t *testing.T) {
rec, err := testHandler.testUpdate(nil, map[string]string{"listtask": "28"}, `{"done":true}`)
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "28"}, `{"done":true}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"done":false`)
assert.NotContains(t, rec.Body.String(), `"done":true`)
})
t.Run("Parent task", func(t *testing.T) {
rec, err := testHandler.testUpdate(nil, map[string]string{"listtask": "1"}, `{"parentTaskID":2}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"parentTaskID":2`)
assert.NotContains(t, rec.Body.String(), `"parentTaskID":0`)
})
t.Run("Parent task same task", func(t *testing.T) {
_, err := testHandler.testUpdate(nil, map[string]string{"listtask": "1"}, `{"parentTaskID":1}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeParentTaskCannotBeTheSame)
})
t.Run("Parent task unset", func(t *testing.T) {
rec, err := testHandler.testUpdate(nil, map[string]string{"listtask": "29"}, `{"parentTaskID":0}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"parentTaskID":0`)
assert.NotContains(t, rec.Body.String(), `"parentTaskID":1`)
})
t.Run("Assignees", func(t *testing.T) {
rec, err := testHandler.testUpdate(nil, map[string]string{"listtask": "1"}, `{"assignees":[{"id":1}]}`)
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"assignees":[{"id":1}]}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"assignees":[{"id":1`)
assert.NotContains(t, rec.Body.String(), `"assignees":[]`)
})
t.Run("Removing Assignees empty array", func(t *testing.T) {
rec, err := testHandler.testUpdate(nil, map[string]string{"listtask": "30"}, `{"assignees":[]}`)
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "30"}, `{"assignees":[]}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"assignees":null`)
assert.NotContains(t, rec.Body.String(), `"assignees":[{"id":1`)
})
t.Run("Removing Assignees null", func(t *testing.T) {
rec, err := testHandler.testUpdate(nil, map[string]string{"listtask": "30"}, `{"assignees":null}`)
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "30"}, `{"assignees":null}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"assignees":null`)
assert.NotContains(t, rec.Body.String(), `"assignees":[{"id":1`)
})
t.Run("Priority", func(t *testing.T) {
rec, err := testHandler.testUpdate(nil, map[string]string{"listtask": "1"}, `{"priority":100}`)
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"priority":100}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"priority":100`)
assert.NotContains(t, rec.Body.String(), `"priority":0`)
})
t.Run("Priority to 0", func(t *testing.T) {
rec, err := testHandler.testUpdate(nil, map[string]string{"listtask": "3"}, `{"priority":0}`)
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "3"}, `{"priority":0}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"priority":0`)
assert.NotContains(t, rec.Body.String(), `"priority":100`)
})
t.Run("Start date", func(t *testing.T) {
rec, err := testHandler.testUpdate(nil, map[string]string{"listtask": "1"}, `{"startDate":1234567}`)
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"start_date":"2020-02-10T10:00:00Z"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"startDate":1234567`)
assert.NotContains(t, rec.Body.String(), `"startDate":0`)
assert.Contains(t, rec.Body.String(), `"start_date":"2020-02-10T10:00:00Z"`)
assert.NotContains(t, rec.Body.String(), `"start_date":0`)
})
t.Run("Start date unset", func(t *testing.T) {
rec, err := testHandler.testUpdate(nil, map[string]string{"listtask": "7"}, `{"startDate":0}`)
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "7"}, `{"start_date":null}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"startDate":0`)
assert.NotContains(t, rec.Body.String(), `"startDate":1544600000`)
assert.Contains(t, rec.Body.String(), `"start_date":null`)
assert.NotContains(t, rec.Body.String(), `"start_date":"2020-02-10T10:00:00Z"`)
})
t.Run("End date", func(t *testing.T) {
rec, err := testHandler.testUpdate(nil, map[string]string{"listtask": "1"}, `{"endDate":123456}`)
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"end_date":"2020-02-10T12:00:00Z"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"endDate":123456`)
assert.NotContains(t, rec.Body.String(), `"endDate":0`)
assert.Contains(t, rec.Body.String(), `"end_date":"2020-02-10T12:00:00Z"`)
assert.NotContains(t, rec.Body.String(), `"end_date":""`)
})
t.Run("End date unset", func(t *testing.T) {
rec, err := testHandler.testUpdate(nil, map[string]string{"listtask": "8"}, `{"endDate":0}`)
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "8"}, `{"end_date":null}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"endDate":0`)
assert.NotContains(t, rec.Body.String(), `"endDate":1544700000`)
assert.Contains(t, rec.Body.String(), `"end_date":null`)
assert.NotContains(t, rec.Body.String(), `"end_date":"2020-02-10T10:00:00Z"`)
})
t.Run("Color", func(t *testing.T) {
rec, err := testHandler.testUpdate(nil, map[string]string{"listtask": "1"}, `{"hexColor":"f0f0f0"}`)
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"hex_color":"f0f0f0"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"hexColor":"f0f0f0"`)
assert.NotContains(t, rec.Body.String(), `"hexColor":""`)
assert.Contains(t, rec.Body.String(), `"hex_color":"f0f0f0"`)
assert.NotContains(t, rec.Body.String(), `"hex_color":""`)
})
t.Run("Color unset", func(t *testing.T) {
rec, err := testHandler.testUpdate(nil, map[string]string{"listtask": "31"}, `{"hexColor":""}`)
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "31"}, `{"hex_color":""}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"hexColor":""`)
assert.NotContains(t, rec.Body.String(), `"hexColor":"f0f0f0"`)
assert.Contains(t, rec.Body.String(), `"hex_color":""`)
assert.NotContains(t, rec.Body.String(), `"hex_color":"f0f0f0"`)
})
t.Run("Percent Done", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"percent_done":0.1}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"percent_done":0.1`)
assert.NotContains(t, rec.Body.String(), `"percent_done":0,`)
})
t.Run("Percent Done unset", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "33"}, `{"percent_done":0}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"percent_done":0,`)
assert.NotContains(t, rec.Body.String(), `"percent_done":0.1`)
})
})
t.Run("Nonexisting", func(t *testing.T) {
_, err := testHandler.testUpdate(nil, map[string]string{"listtask": "99999"}, `{"text":"Lorem Ipsum"}`)
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "99999"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeListTaskDoesNotExist)
assertHandlerErrorCode(t, err, models.ErrCodeTaskDoesNotExist)
})
t.Run("Rights check", func(t *testing.T) {
t.Run("Forbidden", func(t *testing.T) {
_, err := testHandler.testUpdate(nil, map[string]string{"listtask": "14"}, `{"text":"Lorem Ipsum"}`)
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "14"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Team readonly", func(t *testing.T) {
_, err := testHandler.testUpdate(nil, map[string]string{"listtask": "15"}, `{"text":"Lorem Ipsum"}`)
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "15"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Team write", func(t *testing.T) {
rec, err := testHandler.testUpdate(nil, map[string]string{"listtask": "16"}, `{"text":"Lorem Ipsum"}`)
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "16"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"text":"Lorem Ipsum"`)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
t.Run("Shared Via Team admin", func(t *testing.T) {
rec, err := testHandler.testUpdate(nil, map[string]string{"listtask": "17"}, `{"text":"Lorem Ipsum"}`)
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "17"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"text":"Lorem Ipsum"`)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
t.Run("Shared Via User readonly", func(t *testing.T) {
_, err := testHandler.testUpdate(nil, map[string]string{"listtask": "18"}, `{"text":"Lorem Ipsum"}`)
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "18"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via User write", func(t *testing.T) {
rec, err := testHandler.testUpdate(nil, map[string]string{"listtask": "19"}, `{"text":"Lorem Ipsum"}`)
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "19"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"text":"Lorem Ipsum"`)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
t.Run("Shared Via User admin", func(t *testing.T) {
rec, err := testHandler.testUpdate(nil, map[string]string{"listtask": "20"}, `{"text":"Lorem Ipsum"}`)
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "20"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"text":"Lorem Ipsum"`)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
_, err := testHandler.testUpdate(nil, map[string]string{"listtask": "21"}, `{"text":"Lorem Ipsum"}`)
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "21"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
rec, err := testHandler.testUpdate(nil, map[string]string{"listtask": "22"}, `{"text":"Lorem Ipsum"}`)
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "22"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"text":"Lorem Ipsum"`)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
rec, err := testHandler.testUpdate(nil, map[string]string{"listtask": "23"}, `{"text":"Lorem Ipsum"}`)
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "23"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"text":"Lorem Ipsum"`)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
_, err := testHandler.testUpdate(nil, map[string]string{"listtask": "24"}, `{"text":"Lorem Ipsum"}`)
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "24"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
rec, err := testHandler.testUpdate(nil, map[string]string{"listtask": "25"}, `{"text":"Lorem Ipsum"}`)
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "25"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"text":"Lorem Ipsum"`)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
rec, err := testHandler.testUpdate(nil, map[string]string{"listtask": "26"}, `{"text":"Lorem Ipsum"}`)
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "26"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"text":"Lorem Ipsum"`)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
})
t.Run("Move to other list", func(t *testing.T) {
t.Run("normal", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"list_id":7}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"list_id":7`)
assert.NotContains(t, rec.Body.String(), `"list_id":1`)
})
t.Run("Forbidden", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"list_id":20}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrorCodeGenericForbidden)
})
t.Run("Read Only", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"list_id":6}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrorCodeGenericForbidden)
})
})
t.Run("Bucket", func(t *testing.T) {
t.Run("Normal", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"bucket_id":2}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"bucket_id":2`)
assert.NotContains(t, rec.Body.String(), `"bucket_id":1`)
})
t.Run("Different List", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"bucket_id":4}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeBucketDoesNotBelongToList)
})
t.Run("Nonexisting Bucket", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"bucket_id":9999}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeBucketDoesNotExist)
})
})
})
t.Run("Delete", func(t *testing.T) {
t.Run("Normal", func(t *testing.T) {
rec, err := testHandler.testDelete(nil, map[string]string{"listtask": "1"})
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "1"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
})
t.Run("Nonexisting", func(t *testing.T) {
_, err := testHandler.testDelete(nil, map[string]string{"listtask": "99999"})
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "99999"})
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeListTaskDoesNotExist)
assertHandlerErrorCode(t, err, models.ErrCodeTaskDoesNotExist)
})
t.Run("Rights check", func(t *testing.T) {
t.Run("Forbidden", func(t *testing.T) {
_, err := testHandler.testDelete(nil, map[string]string{"listtask": "14"})
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "14"})
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Team readonly", func(t *testing.T) {
_, err := testHandler.testDelete(nil, map[string]string{"listtask": "15"})
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "15"})
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Team write", func(t *testing.T) {
rec, err := testHandler.testDelete(nil, map[string]string{"listtask": "16"})
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "16"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
})
t.Run("Shared Via Team admin", func(t *testing.T) {
rec, err := testHandler.testDelete(nil, map[string]string{"listtask": "17"})
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "17"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
})
t.Run("Shared Via User readonly", func(t *testing.T) {
_, err := testHandler.testDelete(nil, map[string]string{"listtask": "18"})
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "18"})
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via User write", func(t *testing.T) {
rec, err := testHandler.testDelete(nil, map[string]string{"listtask": "19"})
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "19"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
})
t.Run("Shared Via User admin", func(t *testing.T) {
rec, err := testHandler.testDelete(nil, map[string]string{"listtask": "20"})
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "20"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
})
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
_, err := testHandler.testDelete(nil, map[string]string{"listtask": "21"})
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "21"})
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
rec, err := testHandler.testDelete(nil, map[string]string{"listtask": "22"})
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "22"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
})
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
rec, err := testHandler.testDelete(nil, map[string]string{"listtask": "23"})
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "23"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
})
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
_, err := testHandler.testDelete(nil, map[string]string{"listtask": "24"})
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "24"})
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
rec, err := testHandler.testDelete(nil, map[string]string{"listtask": "25"})
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "25"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
})
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
rec, err := testHandler.testDelete(nil, map[string]string{"listtask": "26"})
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "26"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
})
@ -490,84 +390,102 @@ func TestListTask(t *testing.T) {
})
t.Run("Create", func(t *testing.T) {
t.Run("Normal", func(t *testing.T) {
rec, err := testHandler.testCreate(nil, map[string]string{"list": "1"}, `{"text":"Lorem Ipsum"}`)
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "1"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"text":"Lorem Ipsum"`)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
t.Run("Nonexisting", func(t *testing.T) {
_, err := testHandler.testCreate(nil, map[string]string{"list": "9999"}, `{"text":"Lorem Ipsum"}`)
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "9999"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeListDoesNotExist)
})
t.Run("Rights check", func(t *testing.T) {
t.Run("Forbidden", func(t *testing.T) {
// Owned by user3
_, err := testHandler.testCreate(nil, map[string]string{"list": "2"}, `{"text":"Lorem Ipsum"}`)
// Owned by user13
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "20"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Team readonly", func(t *testing.T) {
_, err := testHandler.testCreate(nil, map[string]string{"list": "6"}, `{"text":"Lorem Ipsum"}`)
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "6"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Team write", func(t *testing.T) {
rec, err := testHandler.testCreate(nil, map[string]string{"list": "7"}, `{"text":"Lorem Ipsum"}`)
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "7"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"text":"Lorem Ipsum"`)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
t.Run("Shared Via Team admin", func(t *testing.T) {
rec, err := testHandler.testCreate(nil, map[string]string{"list": "8"}, `{"text":"Lorem Ipsum"}`)
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "8"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"text":"Lorem Ipsum"`)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
t.Run("Shared Via User readonly", func(t *testing.T) {
_, err := testHandler.testCreate(nil, map[string]string{"list": "9"}, `{"text":"Lorem Ipsum"}`)
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "9"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via User write", func(t *testing.T) {
rec, err := testHandler.testCreate(nil, map[string]string{"list": "10"}, `{"text":"Lorem Ipsum"}`)
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "10"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"text":"Lorem Ipsum"`)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
t.Run("Shared Via User admin", func(t *testing.T) {
rec, err := testHandler.testCreate(nil, map[string]string{"list": "11"}, `{"text":"Lorem Ipsum"}`)
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "11"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"text":"Lorem Ipsum"`)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
_, err := testHandler.testCreate(nil, map[string]string{"list": "12"}, `{"text":"Lorem Ipsum"}`)
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "12"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
rec, err := testHandler.testCreate(nil, map[string]string{"list": "13"}, `{"text":"Lorem Ipsum"}`)
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "13"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"text":"Lorem Ipsum"`)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
rec, err := testHandler.testCreate(nil, map[string]string{"list": "14"}, `{"text":"Lorem Ipsum"}`)
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "14"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"text":"Lorem Ipsum"`)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
_, err := testHandler.testCreate(nil, map[string]string{"list": "15"}, `{"text":"Lorem Ipsum"}`)
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "15"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
rec, err := testHandler.testCreate(nil, map[string]string{"list": "16"}, `{"text":"Lorem Ipsum"}`)
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "16"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"text":"Lorem Ipsum"`)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
rec, err := testHandler.testCreate(nil, map[string]string{"list": "17"}, `{"text":"Lorem Ipsum"}`)
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "17"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"text":"Lorem Ipsum"`)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
})
t.Run("Bucket", func(t *testing.T) {
t.Run("Normal", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "1"}, `{"title":"Lorem Ipsum","bucket_id":2}`)
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"bucket_id":2`)
assert.NotContains(t, rec.Body.String(), `"bucket_id":1`)
})
t.Run("Different List", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "1"}, `{"title":"Lorem Ipsum","bucket_id":4}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeBucketDoesNotBelongToList)
})
t.Run("Nonexisting Bucket", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "1"}, `{"title":"Lorem Ipsum","bucket_id":9999}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeBucketDoesNotExist)
})
})
})

View File

@ -1,18 +1,18 @@
// Vikunja is a todo-list application to facilitate your life.
// Copyright 2019 Vikunja and contributors. All rights reserved.
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// 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 General Public License for more details.
// 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package integrations

View File

@ -1,24 +1,24 @@
// Vikunja is a todo-list application to facilitate your life.
// Copyright 2019 Vikunja and contributors. All rights reserved.
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// 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 General Public License for more details.
// 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package integrations
import (
"code.vikunja.io/api/pkg/models"
apiv1 "code.vikunja.io/api/pkg/routes/api/v1"
"code.vikunja.io/api/pkg/user"
"github.com/stretchr/testify/assert"
"net/http"
"testing"
@ -39,7 +39,7 @@ func TestUserChangePassword(t *testing.T) {
"old_password": "invalid"
}`, nil, nil)
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeWrongUsernameOrPassword)
assertHandlerErrorCode(t, err, user.ErrCodeWrongUsernameOrPassword)
})
t.Run("Empty old password", func(t *testing.T) {
_, err := newTestRequestWithUser(t, http.MethodPost, apiv1.UserChangePassword, &testuser1, `{
@ -47,7 +47,7 @@ func TestUserChangePassword(t *testing.T) {
"old_password": ""
}`, nil, nil)
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeEmptyOldPassword)
assertHandlerErrorCode(t, err, user.ErrCodeEmptyOldPassword)
})
t.Run("Empty new password", func(t *testing.T) {
_, err := newTestRequestWithUser(t, http.MethodPost, apiv1.UserChangePassword, &testuser1, `{
@ -55,6 +55,6 @@ func TestUserChangePassword(t *testing.T) {
"old_password": "1234"
}`, nil, nil)
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeEmptyNewPassword)
assertHandlerErrorCode(t, err, user.ErrCodeEmptyNewPassword)
})
}

View File

@ -1,24 +1,24 @@
// Vikunja is a todo-list application to facilitate your life.
// Copyright 2019 Vikunja and contributors. All rights reserved.
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// 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 General Public License for more details.
// 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package integrations
import (
"code.vikunja.io/api/pkg/models"
apiv1 "code.vikunja.io/api/pkg/routes/api/v1"
"code.vikunja.io/api/pkg/user"
"github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert"
"net/http"
@ -35,16 +35,16 @@ func TestUserConfirmEmail(t *testing.T) {
_, err := newTestRequest(t, http.MethodPost, apiv1.UserConfirmEmail, `{}`)
assert.Error(t, err)
assert.Equal(t, http.StatusPreconditionFailed, err.(*echo.HTTPError).Code)
assertHandlerErrorCode(t, err, models.ErrCodeInvalidEmailConfirmToken)
assertHandlerErrorCode(t, err, user.ErrCodeInvalidEmailConfirmToken)
})
t.Run("Empty token", func(t *testing.T) {
_, err := newTestRequest(t, http.MethodPost, apiv1.UserConfirmEmail, `{"token": ""}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeInvalidEmailConfirmToken)
assertHandlerErrorCode(t, err, user.ErrCodeInvalidEmailConfirmToken)
})
t.Run("Invalid token", func(t *testing.T) {
_, err := newTestRequest(t, http.MethodPost, apiv1.UserConfirmEmail, `{"token": "invalidToken"}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeInvalidEmailConfirmToken)
assertHandlerErrorCode(t, err, user.ErrCodeInvalidEmailConfirmToken)
})
}

View File

@ -1,18 +1,18 @@
// Vikunja is a todo-list application to facilitate your life.
// Copyright 2019 Vikunja and contributors. All rights reserved.
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// 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 General Public License for more details.
// 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package integrations

View File

@ -1,24 +1,24 @@
// Vikunja is a todo-list application to facilitate your life.
// Copyright 2019 Vikunja and contributors. All rights reserved.
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// 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 General Public License for more details.
// 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package integrations
import (
"code.vikunja.io/api/pkg/models"
apiv1 "code.vikunja.io/api/pkg/routes/api/v1"
"code.vikunja.io/api/pkg/user"
"github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert"
"net/http"
@ -34,7 +34,7 @@ func TestUserRequestResetPasswordToken(t *testing.T) {
t.Run("Empty payload", func(t *testing.T) {
_, err := newTestRequest(t, http.MethodPost, apiv1.UserRequestResetPasswordToken, `{}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeNoUsernamePassword)
assertHandlerErrorCode(t, err, user.ErrCodeNoUsernamePassword)
})
t.Run("Invalid email address", func(t *testing.T) {
_, err := newTestRequest(t, http.MethodPost, apiv1.UserRequestResetPasswordToken, `{"email": "user1example.com"}`)
@ -44,6 +44,6 @@ func TestUserRequestResetPasswordToken(t *testing.T) {
t.Run("No user with that email address", func(t *testing.T) {
_, err := newTestRequest(t, http.MethodPost, apiv1.UserRequestResetPasswordToken, `{"email": "user1000@example.com"}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeUserDoesNotExist)
assertHandlerErrorCode(t, err, user.ErrCodeUserDoesNotExist)
})
}

View File

@ -1,24 +1,24 @@
// Vikunja is a todo-list application to facilitate your life.
// Copyright 2019 Vikunja and contributors. All rights reserved.
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// 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 General Public License for more details.
// 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package integrations
import (
"code.vikunja.io/api/pkg/models"
apiv1 "code.vikunja.io/api/pkg/routes/api/v1"
"code.vikunja.io/api/pkg/user"
"github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert"
"net/http"
@ -45,7 +45,7 @@ func TestUserPasswordReset(t *testing.T) {
"token": "passwordresettesttoken"
}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeNoUsernamePassword)
assertHandlerErrorCode(t, err, user.ErrCodeNoUsernamePassword)
})
t.Run("Invalid password reset token", func(t *testing.T) {
_, err := newTestRequest(t, http.MethodPost, apiv1.UserResetPassword, `{
@ -53,6 +53,6 @@ func TestUserPasswordReset(t *testing.T) {
"token": "invalidtoken"
}`)
assert.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeInvalidPasswordResetToken)
assertHandlerErrorCode(t, err, user.ErrCodeInvalidPasswordResetToken)
})
}

View File

@ -1,18 +1,18 @@
// Vikunja is a todo-list application to facilitate your life.
// Copyright 2019 Vikunja and contributors. All rights reserved.
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// 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 General Public License for more details.
// 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package integrations

View File

@ -1,27 +1,28 @@
// Vikunja is a todo-list application to facilitate your life.
// Copyright 2018 Vikunja and contributors. All rights reserved.
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// 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 General Public License for more details.
// 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package log
import (
"code.vikunja.io/api/pkg/config"
"github.com/op/go-logging"
"github.com/spf13/viper"
"io"
"log"
"os"
"strings"
"time"
)
@ -34,61 +35,60 @@ const WebFmt = `${time_rfc3339_nano}: WEB ` + "\t" + `▶ ${remote_ip} ${id} ${m
// Fmt is the general log format
const Fmt = `%{color}%{time:` + time.RFC3339Nano + `}: %{level}` + "\t" + `▶ %{shortpkg}/%{shortfunc} %{id:03x}%{color:reset} %{message}`
// Log is the handler for the logger
var Log = logging.MustGetLogger("vikunja")
const logModule = `vikunja`
// loginstance is the instance of the logger which is used under the hood to log
var logInstance = logging.MustGetLogger(logModule)
// InitLogger initializes the global log handler
func InitLogger() {
if !viper.GetBool("log.enabled") {
if !config.LogEnabled.GetBool() {
// Disable all logging when loggin in general is disabled, overwriting everything a user might have set.
viper.Set("log.errors", "off")
viper.Set("log.standard", "off")
viper.Set("log.database", "off")
viper.Set("log.http", "off")
viper.Set("log.echo", "off")
config.LogStandard.Set("off")
config.LogDatabase.Set("off")
config.LogHTTP.Set("off")
config.LogEcho.Set("off")
return
}
if viper.GetString("log.errors") == "file" || viper.GetString("log.standard") == "file" {
err := os.Mkdir(viper.GetString("log.path"), 0744)
// This show correct caller functions
logInstance.ExtraCalldepth = 1
if config.LogStandard.GetString() == "file" {
err := os.Mkdir(config.LogPath.GetString(), 0744)
if err != nil && !os.IsExist(err) {
log.Fatal("Could not create log folder: ", err.Error())
Fatalf("Could not create log folder: %s", err.Error())
}
}
var logBackends []logging.Backend
// We define our two backends
if viper.GetString("log.standard") != "off" {
if config.LogStandard.GetString() != "off" {
stdWriter := GetLogWriter("standard")
stdBackend := logging.NewLogBackend(stdWriter, "", 0)
// Set the standard backend
logBackends = append(logBackends, logging.NewBackendFormatter(stdBackend, logging.MustStringFormatter(Fmt+"\n")))
level, err := logging.LogLevel(strings.ToUpper(config.LogLevel.GetString()))
if err != nil {
Fatalf("Error setting database log level: %s", err.Error())
}
logBackend := logging.NewLogBackend(stdWriter, "", 0)
backend := logging.NewBackendFormatter(logBackend, logging.MustStringFormatter(Fmt+"\n"))
backendLeveled := logging.AddModuleLevel(backend)
backendLeveled.SetLevel(level, logModule)
logInstance.SetBackend(backendLeveled)
}
if viper.GetString("log.error") != "off" {
errWriter := GetLogWriter("error")
errBackend := logging.NewLogBackend(errWriter, "", 0)
// Only warnings and more severe messages should go to the error backend
errBackendLeveled := logging.AddModuleLevel(errBackend)
errBackendLeveled.SetLevel(logging.WARNING, "")
logBackends = append(logBackends, errBackendLeveled)
}
// Set our backends
logging.SetBackend(logBackends...)
}
// GetLogWriter returns the writer to where the normal log goes, depending on the config
func GetLogWriter(logfile string) (writer io.Writer) {
writer = os.Stderr // Set the default case to prevent nil pointer panics
writer = os.Stdout // Set the default case to prevent nil pointer panics
switch viper.GetString("log." + logfile) {
case "file":
f, err := os.OpenFile(viper.GetString("log.path")+"/"+logfile+".log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
fullLogFilePath := config.LogPath.GetString() + "/" + logfile + ".log"
f, err := os.OpenFile(fullLogFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600)
if err != nil {
log.Fatal(err)
Fatalf("Could not create logfile %s: %s", fullLogFilePath, err.Error())
}
writer = f
case "stderr":
@ -99,3 +99,71 @@ func GetLogWriter(logfile string) (writer io.Writer) {
}
return
}
// GetLogger returns the logging instance. DO NOT USE THIS TO LOG STUFF.
func GetLogger() *logging.Logger {
return logInstance
}
/////
// The following functions are to be used as an "eye-candy", so one can just write log.Error() instead of log.Log.Error()
// Debug is for debug messages
func Debug(args ...interface{}) {
logInstance.Debug(args...)
}
// Debugf is for debug messages
func Debugf(format string, args ...interface{}) {
logInstance.Debugf(format, args...)
}
// Info is for info messages
func Info(args ...interface{}) {
logInstance.Info(args...)
}
// Infof is for info messages
func Infof(format string, args ...interface{}) {
logInstance.Infof(format, args...)
}
// Error is for error messages
func Error(args ...interface{}) {
logInstance.Error(args...)
}
// Errorf is for error messages
func Errorf(format string, args ...interface{}) {
logInstance.Errorf(format, args...)
}
// Warning is for warning messages
func Warning(args ...interface{}) {
logInstance.Warning(args...)
}
// Warningf is for warning messages
func Warningf(format string, args ...interface{}) {
logInstance.Warningf(format, args...)
}
// Critical is for critical messages
func Critical(args ...interface{}) {
logInstance.Critical(args...)
}
// Criticalf is for critical messages
func Criticalf(format string, args ...interface{}) {
logInstance.Criticalf(format, args...)
}
// Fatal is for fatal messages
func Fatal(args ...interface{}) {
logInstance.Fatal(args...)
}
// Fatalf is for fatal messages
func Fatalf(format string, args ...interface{}) {
logInstance.Fatalf(format, args...)
}

141
pkg/log/xorm_logger.go Normal file
View File

@ -0,0 +1,141 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package log
import (
"code.vikunja.io/api/pkg/config"
"github.com/op/go-logging"
"strings"
"time"
"xorm.io/xorm/log"
)
// XormFmt defines the format for xorm logging strings
const XormFmt = `%{color}%{time:` + time.RFC3339Nano + `}: %{level}` + "\t" + `▶ [DATABASE] %{id:03x}%{color:reset} %{message}`
const xormLogModule = `vikunja_database`
// XormLogger holds an implementation of the xorm logger interface.
type XormLogger struct {
logger *logging.Logger
level log.LogLevel
showSQL bool
}
// NewXormLogger creates and initializes a new xorm logger
func NewXormLogger(lvl string) *XormLogger {
if lvl == "" {
lvl = strings.ToUpper(config.LogDatabaseLevel.GetString())
}
level, err := logging.LogLevel(lvl)
if err != nil {
Critical("Error setting database log level: %s", err.Error())
}
xormLogger := &XormLogger{
logger: logging.MustGetLogger(xormLogModule),
}
logBackend := logging.NewLogBackend(GetLogWriter("database"), "", 0)
backend := logging.NewBackendFormatter(logBackend, logging.MustStringFormatter(XormFmt+"\n"))
backendLeveled := logging.AddModuleLevel(backend)
backendLeveled.SetLevel(level, xormLogModule)
xormLogger.logger.SetBackend(backendLeveled)
switch level {
case logging.CRITICAL:
case logging.ERROR:
xormLogger.level = log.LOG_ERR
case logging.WARNING:
case logging.NOTICE:
xormLogger.level = log.LOG_WARNING
case logging.INFO:
xormLogger.level = log.LOG_INFO
case logging.DEBUG:
xormLogger.level = log.LOG_DEBUG
default:
xormLogger.level = log.LOG_OFF
}
xormLogger.showSQL = true
return xormLogger
}
// Debug logs a debug string
func (x *XormLogger) Debug(v ...interface{}) {
x.logger.Debug(v...)
}
// Debugf logs a debug string
func (x *XormLogger) Debugf(format string, v ...interface{}) {
x.logger.Debugf(format, v...)
}
// Error logs a debug string
func (x *XormLogger) Error(v ...interface{}) {
x.logger.Error(v...)
}
// Errorf logs a debug string
func (x *XormLogger) Errorf(format string, v ...interface{}) {
x.logger.Errorf(format, v...)
}
// Info logs an info string
func (x *XormLogger) Info(v ...interface{}) {
x.logger.Info(v...)
}
// Infof logs an info string
func (x *XormLogger) Infof(format string, v ...interface{}) {
x.logger.Infof(format, v...)
}
// Warn logs a warning string
func (x *XormLogger) Warn(v ...interface{}) {
x.logger.Warning(v...)
}
// Warnf logs a warning string
func (x *XormLogger) Warnf(format string, v ...interface{}) {
x.logger.Warningf(format, v...)
}
// Level returns the current set log level
func (x *XormLogger) Level() log.LogLevel {
return x.level
}
// SetLevel sets the log level
func (x *XormLogger) SetLevel(l log.LogLevel) {
x.level = l
}
// ShowSQL sets whether to show the log level or not
func (x *XormLogger) ShowSQL(show ...bool) {
if len(show) > 0 {
x.showSQL = show[0]
}
}
// IsShowSQL returns if sql queries should be shown
func (x *XormLogger) IsShowSQL() bool {
return x.showSQL
}

View File

@ -1,25 +1,25 @@
// Vikunja is a todo-list application to facilitate your life.
// Copyright 2018 Vikunja and contributors. All rights reserved.
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// 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 General Public License for more details.
// 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package mail
import (
"code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/log"
"crypto/tls"
"github.com/spf13/viper"
"gopkg.in/gomail.v2"
"time"
)
@ -27,22 +27,31 @@ import (
// Queue is the mail queue
var Queue chan *gomail.Message
func getDialer() *gomail.Dialer {
d := gomail.NewDialer(config.MailerHost.GetString(), config.MailerPort.GetInt(), config.MailerUsername.GetString(), config.MailerPassword.GetString())
// #nosec
d.TLSConfig = &tls.Config{
InsecureSkipVerify: config.MailerSkipTLSVerify.GetBool(),
ServerName: config.MailerHost.GetString(),
}
return d
}
// StartMailDaemon starts the mail daemon
func StartMailDaemon() {
Queue = make(chan *gomail.Message, viper.GetInt("mailer.queuelength"))
Queue = make(chan *gomail.Message, config.MailerQueuelength.GetInt())
if !viper.GetBool("mailer.enabled") {
if !config.MailerEnabled.GetBool() {
return
}
if viper.GetString("mailer.host") == "" {
log.Log.Warning("Mailer seems to be not configured! Please see the config docs for more details.")
if config.MailerHost.GetString() == "" {
log.Warning("Mailer seems to be not configured! Please see the config docs for more details.")
return
}
go func() {
d := gomail.NewDialer(viper.GetString("mailer.host"), viper.GetInt("mailer.port"), viper.GetString("mailer.username"), viper.GetString("mailer.password"))
d.TLSConfig = &tls.Config{InsecureSkipVerify: viper.GetBool("mailer.skiptlsverify")}
d := getDialer()
var s gomail.SendCloser
var err error
@ -55,21 +64,21 @@ func StartMailDaemon() {
}
if !open {
if s, err = d.Dial(); err != nil {
log.Log.Error("Error during connect to smtp server: %s", err)
log.Error("Error during connect to smtp server: %s", err)
}
open = true
}
if err := gomail.Send(s, m); err != nil {
log.Log.Error("Error when sending mail: %s", err)
log.Error("Error when sending mail: %s", err)
}
// Close the connection to the SMTP server if no email was sent in
// the last 30 seconds.
case <-time.After(viper.GetDuration("mailer.queuetimeout") * time.Second):
case <-time.After(config.MailerQueueTimeout.GetDuration() * time.Second):
if open {
if err := s.Close(); err != nil {
log.Log.Error("Error closing the mail server connection: %s\n", err)
log.Error("Error closing the mail server connection: %s\n", err)
}
log.Log.Infof("Closed connection to mailserver")
log.Infof("Closed connection to mailserver")
open = false
}
}

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