Compare commits

..
241 Commits
Author SHA1 Message Date
kolaente f08fc8eeb4 chore: release preparation 2023-12-19 16:21:30 +01:00
kolaente 06f9de1c64 chore(deps): update deps 2023-12-19 15:13:05 +01:00
kolaente e4b1d6e01b chore(deps): update sqlite bindings 2023-12-19 14:26:43 +01:00
kolaente c05f51b923 chore(deps): update golangci-lint rules 2023-12-19 13:34:31 +01:00
kolaente 48a173a563 chore(ci): sign drone config 2023-12-19 12:55:35 +01:00
kolaente 9a6588fcd7 chore(deps): update lockfile 2023-12-19 12:55:17 +01:00
renovateandkolaente 9b1455723b chore(deps): update goreleaser/nfpm docker tag to v2.34.0
(cherry picked from commit a816a6053b)
2023-12-19 12:54:50 +01:00
renovateandkolaente 2d594d2079 chore(deps): update golangci/golangci-lint docker tag to v1.55.2
(cherry picked from commit 59bc6dfc6e)
2023-12-19 12:54:34 +01:00
renovateandkolaente 410c54134a fix(deps): update module github.com/labstack/echo/v4 to v4.11.3
(cherry picked from commit 92fe2d9b19)
2023-12-19 12:54:25 +01:00
renovateandkolaente ac35b2ee83 fix(deps): update src.techknowlogick.com/xgo digest to c7ed783
(cherry picked from commit a5a7c587b1)
2023-12-19 12:54:18 +01:00
renovateandkolaente 55831c9bf1 fix(deps): update module github.com/golang-jwt/jwt/v5 to v5.2.0
(cherry picked from commit 974d4fa5ac)
2023-12-19 12:54:09 +01:00
renovateandkolaente 9d77b4156d fix(deps): update module golang.org/x/image to v0.14.0
(cherry picked from commit 1b2f40b250)
2023-12-19 12:53:26 +01:00
renovateandkolaente 6229e1caf6 fix(deps): update module golang.org/x/term to v0.15.0
(cherry picked from commit 9cf5c7039c)
2023-12-19 12:52:35 +01:00
renovateandkolaente 58e4462ca5 fix(deps): update module golang.org/x/crypto to v0.17.0
(cherry picked from commit 386286f0cd)
2023-12-19 12:51:46 +01:00
renovateandkolaente 51075f71e6 fix(deps): update module golang.org/x/oauth2 to v0.15.0
(cherry picked from commit 974e3b7cda)
2023-12-19 12:51:19 +01:00
renovateandkolaente d66f5e77d4 fix(deps): update module golang.org/x/text to v0.14.0
(cherry picked from commit 1d8da26a62)
2023-12-19 12:51:04 +01:00
kolaente 57bd5aca80 fix(webhooks): send application/json header 2023-12-06 14:09:49 +01:00
kolaente 74e40ccfb4 fix(files): keyvalue init in tests 2023-12-06 14:05:08 +01:00
kolaente 6ef1bc3944 fix: lint 2023-12-06 14:01:09 +01:00
kolaente cca42b9188 feat(metrics): add total number of attachments metric 2023-12-03 15:30:39 +01:00
kolaente fd0b2d103d feat(metrics): add total number of files metric 2023-12-03 15:22:41 +01:00
kolaente 0ce110fa52 feat(metrics): add active link share logins 2023-12-03 15:12:43 +01:00
kolaente 8a3f16fcfb fix(labels): make sure labels of shared sub projects are usable
Resolves https://community.vikunja.io/t/cannot-filter-on-projets-labels/1736
2023-12-03 14:33:43 +01:00
kolaente d35c30dd0c fix(import): make sure importing works if parent / child projects are created in a different order
Resolves https://community.vikunja.io/t/migrating-from-self-hosted-instance/1805
2023-12-03 14:14:27 +01:00
kolaente 66b44da85b fix(projects): delete child projects when deleting a project 2023-12-01 17:27:40 +01:00
erri120andkonrad 4615b4dbfb docs: add Authentik example config (#1660)
Reviewed-on: https://kolaente.dev/vikunja/api/pulls/1660
Reviewed-by: konrad <k@knt.li>
Co-authored-by: erri120 <erri120@protonmail.com>
Co-committed-by: erri120 <erri120@protonmail.com>
2023-12-01 14:08:14 +00:00
kolaente c6c465c273 feat: make unauthenticated user routes rate limit configurable 2023-11-28 22:27:51 +01:00
kolaente f26f1326ea chore(deps): update github.com/go-jose/go-jose/v3 2023-11-24 11:32:42 +01:00
kolaente bbc8da1e80 fix(tests): pass the map 2023-11-20 12:22:44 +01:00
kolaente df1f95871a fix(projects): correctly set project's archived state if their parent was archived
Resolves https://kolaente.dev/vikunja/frontend/issues/3795
2023-11-17 20:00:08 +01:00
Frederick [Bot]andkolaente 5da1075f88 [skip ci] Updated swagger docs 2023-11-16 12:20:35 +00:00
kolaente 1ed65d306d fix(docs): remove duplicate paths (params) in swagger docs 2023-11-16 13:07:01 +01:00
kolaente b9a54b019d fix(tests): remove duplicate projects from assertions 2023-11-15 13:17:04 +01:00
kolaente 61bce2b349 fix(projects): don't return child projects multiple times 2023-11-15 12:56:36 +01:00
kolaente ab2eda4bd5 fix(openid): make sure usernames with spaces work 2023-11-13 11:38:24 +01:00
225d65268d feat(caldav): Add support for subtasks (i.e. RELATED-TO property) in CalDAV (#1634)
As I mentioned [here](https://kolaente.dev/vikunja/api/pulls/1442#issuecomment-55215), this is mainly a cleanup of @zewaren 's original [PR](https://kolaente.dev/vikunja/api/pulls/1442).

It adds support for the `RELATED-TO` property in CalDAV's `VTODO` and the `RELTYPE=PARENT` and `RELTYPE=CHILD` relationships. In other words, it allows for `ParentTask->SubTask` relations to be handled supported through CalDAV.

In addition to the included tests, this has been tested by both @zewaren & myself with DAVx5 & Tasks (Android) and it's been working great.

Resolves https://kolaente.dev/vikunja/api/issues/1345

Co-authored-by: Miguel A. Arroyo <miguel@codeheads.dev>
Co-authored-by: Erwan Martin <public@fzwte.net>
Reviewed-on: https://kolaente.dev/vikunja/api/pulls/1634
Reviewed-by: konrad <k@knt.li>
Co-authored-by: Miguel Arroyo <mayanez@noreply.kolaente.de>
Co-committed-by: Miguel Arroyo <mayanez@noreply.kolaente.de>
2023-11-10 22:44:03 +00:00
kolaente 6169c2e12e fix(typesense): correctly convert date values for typesense
Resolves https://kolaente.dev/vikunja/api/issues/1636
2023-11-09 12:29:52 +01:00
Frederick [Bot]andkolaente 91146f139b [skip ci] Updated swagger docs 2023-11-08 23:29:34 +00:00
kolaente 46683a2516 feat(migration): migration from other services now happens in the background 2023-11-09 00:15:11 +01:00
kolaente 707bb6f89e fix(migration): make sub project hierarchy work when importing from other services 2023-11-08 22:56:10 +01:00
kolaente 8acc42cb0b chore(deps): update lockfile 2023-11-07 15:14:20 +01:00
renovateandkolaente b1b1efde88 fix(deps): update module github.com/yuin/goldmark to v1.6.0
(cherry picked from commit 355df3d1d2)
2023-11-07 15:13:59 +01:00
renovateandkolaente c4d18b13a3 fix(deps): update module src.techknowlogick.com/xormigrate to v1.7.1
(cherry picked from commit 15cb2fb845)
2023-11-07 15:13:47 +01:00
renovateandkolaente 7848a93293 fix(deps): update module github.com/spf13/cobra to v1.8.0
(cherry picked from commit 89347ab5fe)
2023-11-07 15:13:37 +01:00
renovateandkolaente 2105128b9c fix(deps): update module golang.org/x/sys to v0.14.0
(cherry picked from commit 252c2bb54a)
2023-11-07 15:13:32 +01:00
renovateandkolaente c27ecf18da fix(deps): update module golang.org/x/sync to v0.5.0
(cherry picked from commit 4eea5c4a0b)
2023-11-07 15:12:34 +01:00
renovateandkolaente eb96c6f2df fix(deps): update module github.com/mattn/go-sqlite3 to v1.14.18
(cherry picked from commit d5eed0ac29)
2023-11-07 15:12:22 +01:00
renovateandkolaente de20c5a972 fix(deps): update module github.com/redis/go-redis/v9 to v9.3.0
(cherry picked from commit d9021d08ad)
2023-11-07 15:12:09 +01:00
kolaente b2866ca3da feat(api): enable notifications for api token routes 2023-11-07 14:58:46 +01:00
kolaente 4d3ba495a7 fix(tests): use string IDs in Todoist test 2023-11-07 13:28:06 +01:00
kolaente a2925cf55b docs: clarify minimum required go version 2023-11-05 17:09:46 +01:00
kolaente 27984157c1 fix(migration): use string for todoist project note id 2023-11-03 12:06:23 +01:00
renovateandkonrad 66afe52afb fix(deps): update module xorm.io/xorm to v1.3.4 (#1630)
Reviewed-on: https://kolaente.dev/vikunja/api/pulls/1630
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-27 09:10:43 +00:00
kolaente f5e5b22641 chore(deps): update lockfile 2023-10-26 22:57:15 +02:00
renovateandkolaente ae77ee068b fix(deps): update module src.techknowlogick.com/xormigrate to v1.7.0
(cherry picked from commit 468acf72d6)
2023-10-26 22:56:43 +02:00
renovateandkolaente f1a2028f59 fix(deps): update module github.com/google/uuid to v1.4.0
(cherry picked from commit 14e2842cea)
2023-10-26 22:56:28 +02:00
renovateandkonrad 70cec74239 fix(deps): update module src.techknowlogick.com/xormigrate to v1.6.0 (#1627)
Reviewed-on: https://kolaente.dev/vikunja/api/pulls/1627
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-26 10:56:15 +00:00
kolaente db0153a721 docs: fix typo 2023-10-24 19:55:43 +02:00
kolaente 6a7aec2e9d docs: add n8n docs 2023-10-24 19:54:15 +02:00
Frederick [Bot]andkolaente 1c416ae73e [skip ci] Updated swagger docs 2023-10-24 14:38:04 +00:00
kolaente a375223872 fix: properly tag bucket-related operations 2023-10-24 16:13:15 +02:00
kolaente a1ea77f751 feat: accept hex values which start with a # 2023-10-24 16:12:22 +02:00
Frederick [Bot]andkolaente 4625377752 [skip ci] Updated swagger docs 2023-10-22 17:21:16 +00:00
kolaente aad6bc08f6 fix(ci): don't try to install when linting 2023-10-22 19:00:43 +02:00
kolaente 916e75da09 chore(ci): use golangci-lint docker image for lint step 2023-10-22 17:30:36 +02:00
kolaente 8a4856ad87 feat: convert all markdown content to html (#1625)
Migration for https://kolaente.dev/vikunja/frontend/pulls/2222

Reviewed-on: https://kolaente.dev/vikunja/api/pulls/1625
Co-authored-by: kolaente <k@knt.li>
Co-committed-by: kolaente <k@knt.li>
2023-10-22 13:48:22 +00:00
kolaente 10c9913e12 feat(notifications): add endpoint to mark all notifications as read 2023-10-20 16:40:47 +02:00
kolaente 66cf7ab50a feat(reminders): include project in reminder notification 2023-10-20 13:56:14 +02:00
Frederick [Bot]andkolaente b2b4b5423f [skip ci] Updated swagger docs 2023-10-20 11:39:17 +00:00
kolaente 1eefe265c5 chore(deps): update lockfile 2023-10-20 13:08:47 +02:00
renovateandkolaente edab83b7c5 fix(deps): update src.techknowlogick.com/xgo digest to ecfba3d
(cherry picked from commit 552cc1384a)
2023-10-20 13:08:18 +02:00
renovateandkolaente 78812e47b2 fix(deps): update module github.com/coreos/go-oidc/v3 to v3.7.0
(cherry picked from commit 8b59eef595)
2023-10-20 13:08:02 +02:00
konrad 4d9baa38d0 feat: webhooks (#1624)
Reviewed-on: https://kolaente.dev/vikunja/api/pulls/1624
2023-10-20 11:06:36 +00:00
kolaente 55d345e236 feat(webhooks): validate events and target url 2023-10-20 12:42:28 +02:00
kolaente 61cd08fa13 fix(webhooks): add created by user object when creating a webhook 2023-10-18 22:18:45 +02:00
kolaente 72366a5b27 feat(webhooks): add created by user object when returning all webhooks 2023-10-18 20:06:07 +02:00
kolaente b4e3d8ee47 fix(webhooks): lint 2023-10-17 20:40:09 +02:00
kolaente 7a74e491da fix(webhooks): lint 2023-10-17 20:35:14 +02:00
kolaente 2c84cec044 docs(webhooks): add swagger docs for all webhook endpoints 2023-10-17 20:35:14 +02:00
kolaente 35e8183f6a docs(webhooks): add general docs about webhooks 2023-10-17 20:35:14 +02:00
kolaente fc0029eed7 fix(webhooks): don't send the proxy auth header to the webhook target 2023-10-17 20:35:14 +02:00
kolaente 177f367a8c feat(webhooks): expose whether webhooks are enabled 2023-10-17 20:35:14 +02:00
kolaente 1b82f26d3e chore(webhooks): simplify registering webhook events 2023-10-17 20:35:13 +02:00
kolaente ec4aa606e2 chore(webhooks): reuse webhook client 2023-10-17 20:35:13 +02:00
kolaente c3947e1016 docs(webhooks): add webhook config to sample config 2023-10-17 20:35:13 +02:00
kolaente 831aa4a014 feat(webhooks): add support for webhook proxy 2023-10-17 20:35:13 +02:00
kolaente b38360c9a5 feat(webhooks): add timeout config option 2023-10-17 20:35:13 +02:00
kolaente 34a92b759e feat(webhooks): add setting to enable webhooks 2023-10-17 20:35:13 +02:00
kolaente 8cc775ac4c fix(webhooks): routes should use the common schema used for other routes already 2023-10-17 20:35:13 +02:00
kolaente a0d8b28813 feat(webhooks): add hmac signing 2023-10-17 20:35:13 +02:00
kolaente a3a323cbf1 feat(webhooks): set user agent header to Vikunja 2023-10-17 20:35:13 +02:00
kolaente 4253d14367 chore(webhooks): remove WebhookEvent interface 2023-10-17 20:35:13 +02:00
kolaente 96ccf6b923 feat(webhooks): add route to get all available webhook events 2023-10-17 20:35:13 +02:00
kolaente eb1b9247ad feat(webhooks): prevent link shares from managing webhooks 2023-10-17 20:35:13 +02:00
kolaente 57de44694c feat(webhooks): add index on project id 2023-10-17 20:35:13 +02:00
kolaente 8d7a492936 feat(webhooks): add filter based on project id 2023-10-17 20:35:13 +02:00
kolaente 7d1c5c50c5 feat(webhooks): add basic sending of webhooks 2023-10-17 20:35:12 +02:00
kolaente 7f3c300240 feat(webhooks): add routes 2023-10-17 20:35:12 +02:00
kolaente c5de41f183 feat(webhooks): add event listener to send webhook payload 2023-10-17 20:35:12 +02:00
kolaente e5b8d8bd2d feat(webhooks): register task and project events as webhook 2023-10-17 20:35:12 +02:00
kolaente ad7d485eb5 feat(webhooks): add basic crud actions for webhooks 2023-10-17 20:35:12 +02:00
kolaente a1d0541a7a docs: add config guide for NGINX Proxy Manager
Taken from https://github.com/go-vikunja/frontend/issues/28#issuecomment-1765096790
2023-10-17 19:00:40 +02:00
kolaente e1525fca6e docs: clarify required language code 2023-10-17 18:35:06 +02:00
kolaente 872acd329a chore(deps): update lockfile 2023-10-13 14:34:36 +02:00
renovateandkolaente baa907f738 fix(deps): update module github.com/gabriel-vasile/mimetype to v1.4.3
(cherry picked from commit 29e55577d0)
2023-10-13 14:32:49 +02:00
kolaente 21d0676399 chore(deps): update xgo to go 1.21 2023-10-11 23:05:51 +02:00
kolaente 9a29b29a04 fix(user): allow openid users to request their deletion
Resolves https://community.vikunja.io/t/delete-user-not-possible-when-using-oidc/1689/4
2023-10-11 19:06:59 +02:00
kolaente 58497f29e6 fix(kanban): filter for tasks in buckets by assignee should not modify the filter directly
Resolves https://github.com/go-vikunja/api/issues/84
2023-10-11 18:43:28 +02:00
renovateandkonrad 9cdccd7005 chore(deps): update mariadb docker tag to v11 (#1544)
Reviewed-on: https://kolaente.dev/vikunja/api/pulls/1544
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-11 13:06:42 +00:00
renovateandkonrad 3097336054 chore(deps): update goreleaser/nfpm docker tag to v2.33.1 (#1560)
Reviewed-on: https://kolaente.dev/vikunja/api/pulls/1560
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-11 10:25:21 +00:00
renovateandkonrad 0449aaba0b chore(deps): update postgres docker tag to v16 (#1618)
Reviewed-on: https://kolaente.dev/vikunja/api/pulls/1618
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-10-11 08:51:55 +00:00
Frederick [Bot]andkolaente 775b98b729 [skip ci] Updated swagger docs 2023-10-11 08:50:34 +00:00
kolaente 4ac8012117 chore(deps): update lockfile 2023-10-11 10:21:52 +02:00
renovateandkolaente 83025fb527 fix(deps): update module github.com/labstack/echo/v4 to v4.11.2
(cherry picked from commit 365b46e147)
2023-10-11 10:21:13 +02:00
renovateandkolaente 1019948216 fix(deps): update module golang.org/x/sync to v0.4.0
(cherry picked from commit 8bd5ab7f32)
2023-10-11 10:21:02 +02:00
renovateandkolaente f5883db889 fix(deps): update module golang.org/x/image to v0.13.0
(cherry picked from commit aecd95bd8c)
2023-10-11 10:20:51 +02:00
renovateandkolaente d30965554c fix(deps): update module github.com/swaggo/swag to v1.16.2
(cherry picked from commit 52c21eb6d5)
2023-10-11 10:20:38 +02:00
KaibutsuXandkolaente 0769d59054 feat(cli): added --confirm/-c argument when deleting users to bypass prompt (#86)
Reviewed-On: https://github.com/go-vikunja/api/pull/86
2023-10-10 21:24:25 +02:00
kolaente 332a7403ed chore(deps): update lockfile 2023-10-10 21:23:06 +02:00
renovateandkolaente eef4f0afa2 fix(deps): update module github.com/spf13/afero to v1.10.0
(cherry picked from commit 4ede68d7a7)
2023-10-10 21:22:28 +02:00
renovateandkolaente 5b7bb9f983 fix(deps): update module github.com/spf13/viper to v1.17.0
(cherry picked from commit 33ed4b71ad)
2023-10-10 21:22:04 +02:00
kolaente 7eb59f577c feat: add very basic bruno collection 2023-10-10 21:04:57 +02:00
kolaente 250043dd75 chore(deps): update lockfile 2023-10-10 20:41:12 +02:00
renovateandkolaente ea476e738c fix(deps): update module github.com/getsentry/sentry-go to v0.25.0
(cherry picked from commit ef3d658f2c)
2023-10-10 20:40:45 +02:00
renovateandkolaente 7ce18860a6 fix(deps): update src.techknowlogick.com/xgo digest to 6fc6b16
(cherry picked from commit 95c5743438)
2023-10-10 20:40:37 +02:00
renovateandkolaente 75085302c9 fix(deps): update module github.com/prometheus/client_golang to v1.17.0
(cherry picked from commit f8c5b66685)
2023-10-10 20:40:19 +02:00
renovateandkolaente 4c8712c70d fix(deps): update github.com/dustinkirkland/golang-petname digest to 6a283f1
(cherry picked from commit 4bc3d50349)
2023-10-10 20:39:50 +02:00
kolaente 56625b0b90 fix: lint 2023-10-10 20:35:43 +02:00
kolaente c9aec495d5 fix(ci): use the same go image for everything 2023-10-10 18:46:20 +02:00
kolaente f2b4702893 chore(deps): update lockfile 2023-10-10 18:21:09 +02:00
kolaente b3a932e903 chore(deps): update lockfile 2023-10-10 18:15:08 +02:00
renovateandkolaente ac91a8a3a8 fix(deps): update module golang.org/x/oauth2 to v0.13.0
(cherry picked from commit 8b714eee7f)
2023-10-10 18:10:22 +02:00
renovateandkolaente 33af96265b fix(deps): update module github.com/threedotslabs/watermill to v1.3.5
(cherry picked from commit 9cccf7387c)
2023-10-10 18:10:08 +02:00
renovateandkolaente 30e8dc53e5 fix(deps): update module github.com/redis/go-redis/v9 to v9.2.1
(cherry picked from commit a601589985)
2023-10-10 18:09:57 +02:00
renovateandkolaente 6e41567f9e fix(deps): update module github.com/jinzhu/copier to v0.4.0
(cherry picked from commit a5ee64466b)
2023-10-10 18:09:44 +02:00
renovateandkolaente 7c008b1693 fix(deps): update module xorm.io/xorm to v1.3.3
(cherry picked from commit f3fe371f03)
2023-10-10 18:09:29 +02:00
renovateandkolaente 367a35912b fix(deps): update module github.com/yuin/goldmark to v1.5.6
(cherry picked from commit 635351d0a2)
2023-10-10 18:09:15 +02:00
renovateandkolaente 4f53a608a7 fix(deps): update src.techknowlogick.com/xgo digest to 1510ee0
(cherry picked from commit b555b010fd)
2023-10-10 18:08:58 +02:00
KaibutsuXandGitHub 137f3bc151 chore: assume username instead of id when parsing fails for user commands (#87)
Reviewed-On: https://github.com/go-vikunja/api/pull/87
2023-10-10 18:06:10 +02:00
kolaente 0abf686f66 chore: add pr lockdown 2023-10-10 17:56:50 +02:00
kolaente 83f02b1ebc chore: update contributing guidelines 2023-10-10 17:48:10 +02:00
kolaente f5ac3abb2a chore(test): add task deleted assertion to project deletion test 2023-10-03 15:52:38 +02:00
ad04d302af chore: reverse the coupling of module log and config (#1606)
This way the config module can already use the log module with the same result (default logging to StdOut with Level INFO, same output as before) but ENV variables can already change the logging of config file related log output). It is now possible to dump as a cronjob without having to filter the default log about the used config file.

Also:
- all logging modules are now configurable when initializing which makes testing easier
- viper dependency removed from logging
- log correct settings when configured error level is invalid
- deprecation of value "false" for log.standard and log.events (already not mentioned in https://vikunja.io/docs/config-options/)

Co-authored-by: Berengar W. Lehr <Berengar.Lehr@uni-jena.de>
Reviewed-on: https://kolaente.dev/vikunja/api/pulls/1606
Reviewed-by: konrad <k@knt.li>
Co-authored-by: Peter H0ffmann <hoffmannp@noreply.kolaente.de>
Co-committed-by: Peter H0ffmann <hoffmannp@noreply.kolaente.de>
2023-10-03 09:28:28 +00:00
kolaente c217233e08 fix(typesense): getting all data from typesense 2023-09-29 21:26:12 +02:00
kolaente 98102e59f2 feat(typesense): add new tasks to typesense directly when they are created 2023-09-29 21:15:28 +02:00
kolaente 8f4ee3a089 fix(typesense): make sure searching works when no task has a comment at index time 2023-09-29 16:35:59 +02:00
kolaente 70d1903dca docs: add typesense setup 2023-09-29 12:30:53 +02:00
feacbbff74 fix(caldav): do not update dates of tasks when repositioning them (#1605)
When a task is updated, the position of the tasks of the whole project/bucket are updated. This leads to column "updated" of model Task to be updated quite often. However, that column is used for the ETag field of CALDAV.
Thus, changing a task marks all the other tasks as updated, which prevents clients from synchronizing their edited tasks.

Co-authored-by: Erwan Martin <erwan@pepper.com>
Reviewed-on: https://kolaente.dev/vikunja/api/pulls/1605
Co-authored-by: Erwan Martin <public@fzwte.net>
Co-committed-by: Erwan Martin <public@fzwte.net>
2023-09-27 16:17:52 +00:00
kolaente f065dcf4ad fix(typesense): add typesense sync to initial structs 2023-09-15 12:53:29 +02:00
kolaente addcbdd8ca fix(test): don't check for error 2023-09-13 12:52:42 +02:00
kolaente 054f21821c fix(typesense): don't try to index tasks if there are none 2023-09-13 12:19:09 +02:00
kolaente 38a3a5c6e8 fix(typesense): explicitely create typesense sync table 2023-09-13 12:05:37 +02:00
kolaente 1ee243f2bd fix(project background): add more checks for whether a background file exists when duplicating or deleting a project
Related discussion: https://community.vikunja.io/t/500-internal-server-error-when-selecting-unsplash-background-image/778/18
2023-09-13 11:20:59 +02:00
kolaente 191c154150 chore(build): use our own goproxy to prevent issues with packages not found 2023-09-12 13:34:35 +02:00
kolaente 378759e06d fix(build): don't remove swagger files when running build:clean step 2023-09-12 13:12:30 +02:00
kolaente c5c74e9537 chore(caldav): improve trimming .ics file ending 2023-09-07 15:52:37 +02:00
kolaente e34f503674 fix: lint 2023-09-07 11:31:35 +02:00
kolaente bfcefa0217 fix(caldav): check for related tasks synced back from a caldav client
Related discussion https://community.vikunja.io/t/tasks-org-subtask-syncing-issue/737/9
2023-09-07 11:29:44 +02:00
kolaente c6bdb5752a fix(import): create related tasks without an id 2023-09-07 11:16:04 +02:00
kolaente 68d4dcd7e6 fix(projects): don't limit results to top-level projects when searching
Resolves https://github.com/go-vikunja/api/issues/82
2023-09-07 10:56:59 +02:00
kolaente b2f3a23cb3 fix(import): correctly set child project relations 2023-09-07 10:45:15 +02:00
kolaente 93795d2f29 fix(import): resolve task relations by old task ids 2023-09-07 10:24:15 +02:00
kolaente adf4b95ed3 fix(import): ignore duplicate project identifier 2023-09-07 10:12:15 +02:00
kolaente ce3a06f03b fix(import): don't fail when importing from dev exports 2023-09-07 10:11:59 +02:00
kolaente 2c0c3ea24e fix(build): don't require swagger to build 2023-09-06 21:08:09 +02:00
kolaente 2d9cf672b8 fix(ci): don't generate swagger docs in ci 2023-09-06 20:52:19 +02:00
kolaente ae766f52c7 fix(build): don't generate swagger files when building 2023-09-06 18:36:08 +02:00
kolaente 107b0b791f fix(swagger): add generated swagger docs to repo 2023-09-06 18:01:45 +02:00
kolaente 985233ac38 fix(build): don't run go mod commands when generating swagger docs 2023-09-06 16:41:39 +02:00
kolaente 424bf7647b fix: lint 2023-09-06 14:56:25 +02:00
kolaente 06bc92556e fix(docs): add empty swagger file so that the package exists 2023-09-06 13:13:51 +02:00
kolaente 7c4b2c9b39 chore: go mod tidy 2023-09-06 12:02:48 +02:00
kolaente b8e73f4fa5 fix: lint 2023-09-06 10:52:30 +02:00
kolaente bfb01898c2 fix(api tokens)!: make sure task create routes are available to use with the api
BREAKING CHANGE: The api route to create a new task is now /projects/:project/tasks instead of /projects/:project
2023-09-06 10:33:52 +02:00
kolaente d6db498853 fix: lint 2023-09-04 14:25:46 +02:00
kolaente 80b40bb2c0 feat(notify): don't notify disabled users 2023-09-04 14:23:56 +02:00
kolaente c28d1af877 fix(cmd): do not initialize asnyc operations when running certain cli commands 2023-09-04 11:22:50 +02:00
kolaente f38535b2f4 fix(typesense): upsert all documents at once 2023-09-04 11:19:55 +02:00
kolaente 093b5b99a0 fix(typesense): pass the correct user when fetching task comments 2023-09-04 10:42:10 +02:00
kolaente 490d374cfd fix(typesense): add more error logging 2023-09-04 10:20:51 +02:00
kolaente 21a1f02ea3 fix(typesense): add more error logging 2023-09-03 22:54:50 +02:00
kolaente 1a492722dd fix(typesense): upsert one document at a time 2023-09-03 22:51:43 +02:00
kolaente 8a15c91a4f chore(tasks): better error messages when indexing tasks into Typesense 2023-09-03 22:22:27 +02:00
konrad b99b323c4c feat: add setting for default bucket
Reviewed-on: https://kolaente.dev/vikunja/api/pulls/1602
2023-09-03 15:13:52 +00:00
kolaente 0e32d478d1 fix(projects): save done and default bucket when updating project 2023-09-03 15:50:47 +02:00
kolaente 60bd5c8a79 feat: make default bucket configurable 2023-09-03 15:17:17 +02:00
kolaente bbbb45d224 feat: move done bucket setting to project 2023-09-03 15:01:35 +02:00
kolaente 97b5cd306f feat: add demo mode flag
Related to https://kolaente.dev/vikunja/frontend/issues/2453
2023-09-01 17:47:43 +02:00
kolaenteandedel b3a847e581 fix(kanban): create missing kanban buckets (#1601)
Co-authored-by: edel <edel.altares@pm.me>
Reviewed-on: https://kolaente.dev/vikunja/api/pulls/1601
Co-authored-by: kolaente <k@knt.li>
Co-committed-by: kolaente <k@knt.li>
2023-09-01 15:41:31 +00:00
edelandkolaente 11810c9b3e fix: validate usernames on registration 2023-09-01 17:40:31 +02:00
konrad 60cd1250a0 feat: api tokens
Reviewed-on: https://kolaente.dev/vikunja/api/pulls/1600
2023-09-01 14:34:39 +00:00
kolaente edbfc06a41 fix(kanban): don't prevent setting a different bucket as done bucket
Resolves https://kolaente.dev/vikunja/frontend/issues/3373
2023-09-01 16:18:59 +02:00
kolaente 769b4f8d66 fix(task): remove task relation in the other direction as well
Resolves https://community.vikunja.io/t/removing-parent-relationship-doesnt-remove-the-subtask-relationship/1492/3
2023-09-01 16:12:20 +02:00
kolaente eac4e455fd fix(api tokens): lint 2023-09-01 15:56:35 +02:00
kolaente a4d946b4a9 fix(api tokens): test 2023-09-01 15:54:39 +02:00
kolaente 974e1878f8 chore(api tokens): add swagger docs about api token auth 2023-09-01 15:53:27 +02:00
kolaente 14c5a8ca5b fix(api tokens): make sure read one routes show up in routes endpoint 2023-09-01 12:58:29 +02:00
kolaente e3c5a93f4f chore(api tokens): remove updated date from tokens as it can't be updated anyway 2023-09-01 11:16:36 +02:00
kolaente 8f3d18a809 feat(api tokens): better error message for invalid tokens 2023-09-01 10:27:56 +02:00
kolaente e295d75e6e feat(api tokens): move token validation middleware to new function 2023-09-01 10:19:55 +02:00
kolaente d9bfcdab8e feat(api tokens): add tests 2023-09-01 10:19:31 +02:00
kolaente e3dac16398 feat(api tokens): check permissions when saving 2023-09-01 08:52:57 +02:00
kolaente e4c71123ef fix: lint 2023-09-01 08:39:25 +02:00
kolaente 5c6c6cd9f0 feat(api tokens): check for scopes 2023-09-01 08:36:15 +02:00
kolaente 677bd5cfc9 feat(api tokens): check for expiry date 2023-09-01 08:36:15 +02:00
kolaente fb2a1c59db feat(api tokens): check if a provided token matched a hashed on in the database 2023-09-01 08:36:15 +02:00
kolaente c88cbaa973 feat(api tokens): properly hash tokens 2023-09-01 08:36:11 +02:00
kolaente e6b25bd57b feat(api tokens): add crud routes to manage api tokens 2023-09-01 08:35:54 +02:00
kolaente 3faf48706a feat(api tokens): add api token struct and migration 2023-09-01 08:35:54 +02:00
kolaente 1ca93a678e feat: endpoint to get all token routes 2023-09-01 08:35:54 +02:00
kolaente e518fb1191 chore: remove year from copyright headers
Resolves https://kolaente.dev/vikunja/api/pulls/1483
2023-09-01 08:32:28 +02:00
kolaente 5811d2a13b fix: order by clause in task comments 2023-08-31 15:44:59 +02:00
kolaente 5392ca788c fix(comments): make sure comment sort order is stable
Resolves https://community.vikunja.io/t/comment-order-is-not-by-time/1147/3?u=kolaente
2023-08-29 14:09:53 +02:00
konrad 6f825fa413 feat: search improvemens (#1598)
Reviewed-on: https://kolaente.dev/vikunja/api/pulls/1598
2023-08-29 11:23:35 +00:00
kolaente 02184663e5 fix(filter): assignee search by partial username test 2023-08-29 11:40:53 +02:00
kolaente 29317b980e fix: lint 2023-08-29 11:32:45 +02:00
kolaente c1ccbe8186 feat(docs): update sample config and docs about Typesense config 2023-08-29 11:23:52 +02:00
kolaente 4f2796ac58 fix(filters): make "in" filter comparator work with Typesense 2023-08-29 11:14:36 +02:00
kolaente d0e3062bee feat(tasks): allow filtering for reminders, assignees and labels with Typesense 2023-08-29 10:54:18 +02:00
kolaente 748651447a feat(tasks): find tasks by their identifier when searching with Typesense 2023-08-29 09:31:36 +02:00
kolaente 2ca193e63b feat(tasks): make sorting and filtering work with Typesense 2023-08-28 19:10:18 +02:00
kolaente 09cfe41e4f feat(tasks): remove deleted tasks from Typesense 2023-08-28 13:34:47 +02:00
kolaente 1a82d6da44 feat(tasks): add periodic resync of updated tasks to Typesense 2023-08-28 13:26:40 +02:00
kolaente 010b4ce783 feat(tasks): add searching via typesense 2023-08-28 12:14:50 +02:00
kolaente dee46d527a feat(tasks): add typesense indexing 2023-08-28 11:11:30 +02:00
kolaente 693a77ae51 fix(user): don't prevent deleting a user if their default project was shared
Resolves https://kolaente.dev/vikunja/api/issues/1595
2023-08-25 15:14:25 +02:00
kolaente 49d8713388 fix: typo 2023-08-24 11:16:07 +02:00
kolaente 1b9c4204a8 fix(reminders): make sure reminders are only sent once per user
Previously, when a user was creator and assigned to a task, they would get two reminder notifications for the same task. This was caused by Vikunja first fetching all creators and then all assignees and not removing duplicates from that list.

Related: https://community.vikunja.io/t/duplicate-email-reminders/1505/3
2023-08-24 10:47:17 +02:00
kolaente 5e8084c194 fix(project): duplicating a project should not create two backlog buckets
Resolves https://community.vikunja.io/t/when-duplicating-a-project-the-resulting-project-has-an-extra-bucket/1524
2023-08-23 16:19:42 +02:00
kolaente acb03c430e fix(user): allow deleting a user if they have a default project
Resolves https://github.com/go-vikunja/api/issues/78
2023-08-23 16:10:51 +02:00
kolaente 40037f25f2 fix(user): don't crash when attempting to change a user's password
Resolves https://kolaente.dev/vikunja/api/issues/1587
Resolves https://github.com/go-vikunja/api/issues/79
2023-08-23 15:56:18 +02:00
cr1xusandkonrad 665c046717 docs: Add Caddyfile to reverse proxies setup (#1580)
Reviewed-on: https://kolaente.dev/vikunja/api/pulls/1580
Co-authored-by: cr1xus <christoearth@gmail.com>
Co-committed-by: cr1xus <christoearth@gmail.com>
2023-08-21 11:45:35 +00:00
renovateandkonrad 2081852004 fix(deps): update module github.com/arran4/golang-ical to v0.1.0 2023-08-21 10:57:54 +00:00
renovateandkonrad 69451d14a6 fix(deps): update module github.com/getsentry/sentry-go to v0.23.0 2023-08-21 10:57:45 +00:00
renovate 3523325663 fix(deps): update module golang.org/x/image to v0.11.0 2023-08-04 21:02:14 +00:00
renovate f07adb5edd fix(deps): update module xorm.io/builder to v0.3.13 2023-07-20 11:02:05 +00:00
renovate 1647282f0b fix(deps): update module github.com/labstack/echo/v4 to v4.11.1 2023-07-16 18:01:45 +00:00
renovate e42278f93f fix(deps): update module github.com/labstack/echo/v4 to v4.11.0 2023-07-14 21:01:48 +00:00
renovate 4c04a7bd5f fix(deps): update module github.com/iancoleman/strcase to v0.3.0 2023-07-13 01:02:01 +00:00
renovate d913edbcb6 fix(deps): update src.techknowlogick.com/xgo digest to 617d3b6 2023-07-11 19:02:02 +00:00
renovate adbc1d2997 fix(deps): update module github.com/wneessen/go-mail to v0.4.0 2023-07-08 12:02:02 +00:00
411 changed files with 33403 additions and 3886 deletions
+66 -33
View File
@@ -39,7 +39,7 @@ volumes:
services:
- name: test-mysql-unit
image: mariadb:10
image: mariadb:11
environment:
MYSQL_ROOT_PASSWORD: vikunjatest
MYSQL_DATABASE: vikunjatest
@@ -47,7 +47,7 @@ services:
- name: tmp-mysql-unit
path: /var/lib/mysql
- name: test-mysql-integration
image: mariadb:10
image: mariadb:11
environment:
MYSQL_ROOT_PASSWORD: vikunjatest
MYSQL_DATABASE: vikunjatest
@@ -55,7 +55,7 @@ services:
- name: tmp-mysql-integration
path: /var/lib/mysql
- name: test-mysql-migration
image: mariadb:10
image: mariadb:11
environment:
MYSQL_ROOT_PASSWORD: vikunjatest
MYSQL_DATABASE: vikunjatest
@@ -63,7 +63,7 @@ services:
- name: tmp-mysql-migration
path: /var/lib/mysql
- name: test-postgres-unit
image: postgres:14
image: postgres:16
environment:
POSTGRES_PASSWORD: vikunjatest
POSTGRES_DB: vikunjatest
@@ -73,7 +73,7 @@ services:
commands:
- docker-entrypoint.sh -c fsync=off -c full_page_writes=off # turns of wal
- name: test-postgres-integration
image: postgres:14
image: postgres:16
environment:
POSTGRES_PASSWORD: vikunjatest
POSTGRES_DB: vikunjatest
@@ -83,7 +83,7 @@ services:
commands:
- docker-entrypoint.sh -c fsync=off -c full_page_writes=off # turns of wal
- name: test-postgres-migration
image: postgres:14
image: postgres:16
environment:
POSTGRES_PASSWORD: vikunjatest
POSTGRES_DB: vikunjatest
@@ -121,38 +121,25 @@ steps:
when:
event: [ push, tag, pull_request ]
- name: prepare-build
- name: build
image: vikunja/golang-build:latest
pull: always
environment:
GOPROXY: 'https://goproxy.kolaente.de'
depends_on: [ mage ]
commands:
- ./mage-static do-the-swag
when:
event: [ push, tag, pull_request ]
- name: build
image: vikunja/golang-build:latest
pull: always
environment:
GOPROXY: 'https://goproxy.kolaente.de'
depends_on: [ prepare-build ]
commands:
- ./mage-static build:build
when:
event: [ push, tag, pull_request ]
- name: lint
image: golang:1.19-alpine
image: golangci/golangci-lint:v1.55.2
pull: always
environment:
GOPROXY: 'https://goproxy.kolaente.de'
depends_on: [ build ]
commands:
- export "GOROOT=$(go env GOROOT)"
- apk --no-cache add build-base git
- wget -O - -q https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.53.2
- ./mage-static check:golangci
when:
event: [ push, tag, pull_request ]
@@ -230,7 +217,7 @@ steps:
GOPROXY: 'https://goproxy.kolaente.de'
commands:
- ./mage-static test:unit
depends_on: [ fetch-tags, prepare-build ]
depends_on: [ fetch-tags, mage ]
when:
event: [ push, tag, pull_request ]
@@ -247,7 +234,7 @@ steps:
path: /db
commands:
- ./mage-static test:unit
depends_on: [ fetch-tags, prepare-build ]
depends_on: [ fetch-tags, mage ]
when:
event: [ push, tag, pull_request ]
@@ -264,7 +251,7 @@ steps:
VIKUNJA_DATABASE_DATABASE: vikunjatest
commands:
- ./mage-static test:unit
depends_on: [ fetch-tags, prepare-build ]
depends_on: [ fetch-tags, mage ]
when:
event: [ push, tag, pull_request ]
@@ -282,7 +269,7 @@ steps:
VIKUNJA_DATABASE_SSLMODE: disable
commands:
- ./mage-static test:unit
depends_on: [ fetch-tags, prepare-build ]
depends_on: [ fetch-tags, mage ]
when:
event: [ push, tag, pull_request ]
@@ -293,7 +280,7 @@ steps:
GOPROXY: 'https://goproxy.kolaente.de'
commands:
- ./mage-static test:integration
depends_on: [ fetch-tags, prepare-build ]
depends_on: [ fetch-tags, mage ]
when:
event: [ push, tag, pull_request ]
@@ -310,7 +297,7 @@ steps:
path: /db
commands:
- ./mage-static test:integration
depends_on: [ fetch-tags, prepare-build ]
depends_on: [ fetch-tags, mage ]
when:
event: [ push, tag, pull_request ]
@@ -327,7 +314,7 @@ steps:
VIKUNJA_DATABASE_DATABASE: vikunjatest
commands:
- ./mage-static test:integration
depends_on: [ fetch-tags, prepare-build ]
depends_on: [ fetch-tags, mage ]
when:
event: [ push, tag, pull_request ]
@@ -345,10 +332,54 @@ steps:
VIKUNJA_DATABASE_SSLMODE: disable
commands:
- ./mage-static test:integration
depends_on: [ fetch-tags, prepare-build ]
depends_on: [ fetch-tags, mage ]
when:
event: [ push, tag, pull_request ]
---
kind: pipeline
type: docker
name: generate-swagger-docs
depends_on:
- testing
workspace:
base: /go
path: src/code.vikunja.io/api
trigger:
branch:
include:
- main
event:
include:
- push
steps:
- name: generate-swagger-docs
image: vikunja/golang-build:latest
pull: always
environment:
GOPROXY: 'https://goproxy.kolaente.de'
commands:
- mage do-the-swag
- name: push
pull: always
image: appleboy/drone-git-push
depends_on:
- generate-swagger-docs
settings:
author_email: "frederik@vikunja.io"
author_name: Frederick [Bot]
branch: main
commit: true
commit_message: "[skip ci] Updated swagger docs"
remote: "ssh://git@kolaente.dev:9022/vikunja/api.git"
ssh_key:
from_secret: git_push_ssh_key
---
########
# Build a release when tagging
@@ -396,7 +427,6 @@ steps:
- export PATH=$PATH:$GOPATH/bin
- go install github.com/magefile/mage
- ./mage-static release:dirs
- ./mage-static do-the-swag
depends_on: [ fetch-tags, mage ]
- name: static-build-windows
@@ -406,6 +436,7 @@ steps:
# This path does not exist. However, when we set the gopath to /go, the build fails. Not sure why.
# Leaving this here until we know how to resolve this properly.
GOPATH: /srv/app
GOPROXY: https://goproxy.kolaente.de
commands:
- export PATH=$PATH:$GOPATH/bin
- go install github.com/magefile/mage
@@ -419,6 +450,7 @@ steps:
# This path does not exist. However, when we set the gopath to /go, the build fails. Not sure why.
# Leaving this here until we know how to resolve this properly.
GOPATH: /srv/app
GOPROXY: https://goproxy.kolaente.de
commands:
- export PATH=$PATH:$GOPATH/bin
- go install github.com/magefile/mage
@@ -432,6 +464,7 @@ steps:
# This path does not exist. However, when we set the gopath to /go, the build fails. Not sure why.
# Leaving this here until we know how to resolve this properly.
GOPATH: /srv/app
GOPROXY: https://goproxy.kolaente.de
commands:
- export PATH=$PATH:$GOPATH/bin
- go install github.com/magefile/mage
@@ -518,7 +551,7 @@ steps:
# Build os packages and push it to our bucket
- name: build-os-packages-unstable
image: goreleaser/nfpm:v2.30.1
image: goreleaser/nfpm:v2.34.0
pull: always
commands:
- apk add git go
@@ -534,7 +567,7 @@ steps:
depends_on: [ after-build-compress ]
- name: build-os-packages-version
image: goreleaser/nfpm:v2.30.1
image: goreleaser/nfpm:v2.34.0
pull: always
commands:
- apk add git go
@@ -743,6 +776,6 @@ steps:
- failure
---
kind: signature
hmac: d47bd1cf6f3e9be2ff3eed2039e65c8b6de2b16c1e636699f66382f941277411
hmac: e189b9133e2c96f81c06b90dd7793f0649c15aa809d4ee13c27f67d318f2edf3
...
+23
View File
@@ -0,0 +1,23 @@
name: 'Repo Lockdown'
on:
pull_request_target:
types: opened
permissions:
issues: write
pull-requests: write
jobs:
action:
runs-on: ubuntu-latest
steps:
- uses: dessant/repo-lockdown@v3
with:
pr-comment: 'Hi! Thank you for your contribution.
This repo is only a mirror and unfortunately we can''t accept PRs made here. Please re-submit your changes to [our Gitea instance](https://kolaente.dev/vikunja/api/pulls).
Also check out the [contribution guidelines](https://vikunja.io/docs/development/#pull-requests).
Thank you for your understanding.'
+1 -1
View File
@@ -27,4 +27,4 @@ vikunja-dump*
vendor/
os-packages/
mage_output_file.go
pkg/swagger/*
mage-static
+4 -2
View File
@@ -6,7 +6,6 @@ linters:
enable:
- megacheck
- govet
- goconst
- gocritic
- gocyclo
- goerr113
@@ -18,6 +17,7 @@ linters:
disable:
- scopelint # Obsolete, using exportloopref instead
- durationcheck
- goconst
presets:
- bugs
- unused
@@ -54,7 +54,6 @@ issues:
- path: pkg/migration/*
linters:
- exhaustive
- goconst
- goerr113
- path: pkg/models/task_collection_filter\.go
linters:
@@ -97,3 +96,6 @@ issues:
- musttag
- path: pkg/models/task_collection.go
text: 'append result not assigned to the same slice'
- path: pkg/modules/migration/ticktick/ticktick_test.go
linters:
- testifylint
+258 -1
View File
@@ -7,6 +7,263 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
All releases can be found on https://code.vikunja.io/api/releases.
## [0.22.0] - 2023-12-19
### Bug Fixes
* *(api tokens)* Make sure read one routes show up in routes endpoint
* *(api tokens)* Test
* *(api tokens)* Lint
* *(api tokens)* Make sure task create routes are available to use with the api
- **BREAKING**: The api route to create a new task is now /projects/:project/tasks instead of /projects/:project
* *(build)* Don't run go mod commands when generating swagger docs
* *(build)* Don't generate swagger files when building
* *(build)* Don't require swagger to build
* *(build)* Don't remove swagger files when running build:clean step
* *(caldav)* Check for related tasks synced back from a caldav client
* *(caldav)* Do not update dates of tasks when repositioning them (#1605)
* *(ci)* Don't generate swagger docs in ci
* *(ci)* Use the same go image for everything
* *(ci)* Don't try to install when linting
* *(cmd)* Do not initialize asnyc operations when running certain cli commands
* *(comments)* Make sure comment sort order is stable
* *(docs)* Add empty swagger file so that the package exists
* *(docs)* Remove duplicate paths (params) in swagger docs
* *(files)* Keyvalue init in tests
* *(filter)* Assignee search by partial username test
* *(filters)* Make "in" filter comparator work with Typesense
* *(import)* Don't fail when importing from dev exports
* *(import)* Ignore duplicate project identifier
* *(import)* Resolve task relations by old task ids
* *(import)* Correctly set child project relations
* *(import)* Create related tasks without an id
* *(import)* Make sure importing works if parent / child projects are created in a different order
* *(kanban)* Don't prevent setting a different bucket as done bucket
* *(kanban)* Create missing kanban buckets (#1601)
* *(kanban)* Filter for tasks in buckets by assignee should not modify the filter directly
* *(labels)* Make sure labels of shared sub projects are usable
* *(migration)* Use string for todoist project note id
* *(migration)* Make sub project hierarchy work when importing from other services
* *(openid)* Make sure usernames with spaces work
* *(project)* Duplicating a project should not create two backlog buckets
* *(project background)* Add more checks for whether a background file exists when duplicating or deleting a project
* *(projects)* Save done and default bucket when updating project
* *(projects)* Don't limit results to top-level projects when searching
* *(projects)* Don't return child projects multiple times
* *(projects)* Correctly set project's archived state if their parent was archived
* *(projects)* Delete child projects when deleting a project
* *(reminders)* Make sure reminders are only sent once per user
* *(swagger)* Add generated swagger docs to repo
* *(task)* Remove task relation in the other direction as well
* *(test)* Don't check for error
* *(tests)* Use string IDs in Todoist test
* *(tests)* Remove duplicate projects from assertions
* *(tests)* Pass the map
* *(typesense)* Upsert one document at a time
* *(typesense)* Add more error logging
* *(typesense)* Add more error logging
* *(typesense)* Pass the correct user when fetching task comments
* *(typesense)* Upsert all documents at once
* *(typesense)* Explicitely create typesense sync table
* *(typesense)* Don't try to index tasks if there are none
* *(typesense)* Add typesense sync to initial structs
* *(typesense)* Make sure searching works when no task has a comment at index time
* *(typesense)* Getting all data from typesense
* *(typesense)* Correctly convert date values for typesense
* *(user)* Don't crash when attempting to change a user's password
* *(user)* Allow deleting a user if they have a default project
* *(user)* Don't prevent deleting a user if their default project was shared
* *(user)* Allow openid users to request their deletion
* *(webhooks)* Routes should use the common schema used for other routes already
* *(webhooks)* Don't send the proxy auth header to the webhook target
* *(webhooks)* Lint
* *(webhooks)* Lint
* *(webhooks)* Add created by user object when creating a webhook
* *(webhooks)* Send application/json header* Typo ([49d8713](49d87133885b4fa660c300fc38768bd91f56340e))
* Lint ([29317b9](29317b980e68b7e10b127e7e93afff1dd56ace3e))
* Order by clause in task comments ([5811d2a](5811d2a13b5a1017cdd0b393599ffe01db95e836))
* Lint ([e4c7112](e4c71123ef91480d41284288bee38939cd17ae39))
* Validate usernames on registration ([11810c9](11810c9b3e1a4bb4c5fc1f4a3ac44e8552f6a937))
* Lint ([d6db498](d6db49885383ed3e4f98acf649dc302ed1411ccd))
* Lint ([b8e73f4](b8e73f4fa5821ce07b42667cf84c1ff9b87e0888))
* Lint ([424bf76](424bf7647baa34e0fa594c2c36eec542ebea531b))
* Lint ([e34f503](e34f503674c2aab06c7215cba9e2133037e96b6a))
* Lint ([56625b0](56625b0b90d659bd49fc95749691d0100e964dcd))
* Properly tag bucket-related operations ([a375223](a3752238729d50b38a5cf0b811e050c3d9f8985f))
* Lint ([6ef1bc3](6ef1bc3944980588238fb44295b520695a4ed19a))
### Dependencies
* *(deps)* Update module github.com/wneessen/go-mail to v0.4.0
* *(deps)* Update src.techknowlogick.com/xgo digest to 617d3b6
* *(deps)* Update module github.com/iancoleman/strcase to v0.3.0
* *(deps)* Update module github.com/labstack/echo/v4 to v4.11.0
* *(deps)* Update module github.com/labstack/echo/v4 to v4.11.1
* *(deps)* Update module xorm.io/builder to v0.3.13
* *(deps)* Update module golang.org/x/image to v0.11.0
* *(deps)* Update module github.com/getsentry/sentry-go to v0.23.0
* *(deps)* Update module github.com/arran4/golang-ical to v0.1.0
* *(deps)* Update src.techknowlogick.com/xgo digest to 1510ee0
* *(deps)* Update module github.com/yuin/goldmark to v1.5.6
* *(deps)* Update module xorm.io/xorm to v1.3.3
* *(deps)* Update module github.com/jinzhu/copier to v0.4.0
* *(deps)* Update module github.com/redis/go-redis/v9 to v9.2.1
* *(deps)* Update module github.com/threedotslabs/watermill to v1.3.5
* *(deps)* Update module golang.org/x/oauth2 to v0.13.0
* *(deps)* Update lockfile
* *(deps)* Update lockfile
* *(deps)* Update github.com/dustinkirkland/golang-petname digest to 6a283f1
* *(deps)* Update module github.com/prometheus/client_golang to v1.17.0
* *(deps)* Update src.techknowlogick.com/xgo digest to 6fc6b16
* *(deps)* Update module github.com/getsentry/sentry-go to v0.25.0
* *(deps)* Update lockfile
* *(deps)* Update module github.com/spf13/viper to v1.17.0
* *(deps)* Update module github.com/spf13/afero to v1.10.0
* *(deps)* Update lockfile
* *(deps)* Update module github.com/swaggo/swag to v1.16.2
* *(deps)* Update module golang.org/x/image to v0.13.0
* *(deps)* Update module golang.org/x/sync to v0.4.0
* *(deps)* Update module github.com/labstack/echo/v4 to v4.11.2
* *(deps)* Update lockfile
* *(deps)* Update postgres docker tag to v16 (#1618)
* *(deps)* Update goreleaser/nfpm docker tag to v2.33.1 (#1560)
* *(deps)* Update mariadb docker tag to v11 (#1544)
* *(deps)* Update xgo to go 1.21
* *(deps)* Update module github.com/gabriel-vasile/mimetype to v1.4.3
* *(deps)* Update lockfile
* *(deps)* Update module github.com/coreos/go-oidc/v3 to v3.7.0
* *(deps)* Update src.techknowlogick.com/xgo digest to ecfba3d
* *(deps)* Update lockfile
* *(deps)* Update module src.techknowlogick.com/xormigrate to v1.6.0 (#1627)
* *(deps)* Update module github.com/google/uuid to v1.4.0
* *(deps)* Update module src.techknowlogick.com/xormigrate to v1.7.0
* *(deps)* Update lockfile
* *(deps)* Update module xorm.io/xorm to v1.3.4 (#1630)
* *(deps)* Update module github.com/redis/go-redis/v9 to v9.3.0
* *(deps)* Update module github.com/mattn/go-sqlite3 to v1.14.18
* *(deps)* Update module golang.org/x/sync to v0.5.0
* *(deps)* Update module golang.org/x/sys to v0.14.0
* *(deps)* Update module github.com/spf13/cobra to v1.8.0
* *(deps)* Update module src.techknowlogick.com/xormigrate to v1.7.1
* *(deps)* Update module github.com/yuin/goldmark to v1.6.0
* *(deps)* Update lockfile
* *(deps)* Update github.com/go-jose/go-jose/v3
* *(deps)* Update module golang.org/x/text to v0.14.0
* *(deps)* Update module golang.org/x/oauth2 to v0.15.0
* *(deps)* Update module golang.org/x/crypto to v0.17.0
* *(deps)* Update module golang.org/x/term to v0.15.0
* *(deps)* Update module golang.org/x/image to v0.14.0
* *(deps)* Update module github.com/golang-jwt/jwt/v5 to v5.2.0
* *(deps)* Update src.techknowlogick.com/xgo digest to c7ed783
* *(deps)* Update module github.com/labstack/echo/v4 to v4.11.3
* *(deps)* Update golangci/golangci-lint docker tag to v1.55.2
* *(deps)* Update goreleaser/nfpm docker tag to v2.34.0
* *(deps)* Update lockfile
* *(deps)* Update golangci-lint rules
* *(deps)* Update sqlite bindings
* *(deps)* Update deps
### Documentation
* *(webhooks)* Add general docs about webhooks
* *(webhooks)* Add swagger docs for all webhook endpoints* Add Caddyfile to reverse proxies setup (#1580) ([665c046](665c04671739fd08e5b24e59749707ce5de83daa))
* *(webhooks)* Add webhook config to sample config
* Add Authentik example config (#1660) ([4615b4d](4615b4dbfbbf8514d9c41176e6e68a8ba3a453ce))
* Add config guide for NGINX Proxy Manager ([a1d0541](a1d0541a7a6926127ba0bac4df03ce62b74f0c84))
* Add n8n docs ([6a7aec2](6a7aec2e9ded619b074ef27f360c96c313e4449c))
* Add typesense setup ([70d1903](70d1903dcac67e33bdfdf54d0ba561af76dbf927))
* Clarify minimum required go version ([a2925cf](a2925cf55bee4c71ac5be1bad66cb3ec2230056d))
* Clarify required language code ([e1525fc](e1525fca6eb5af17afa332d2c76a37b288673c5b))
* Fix typo ([db0153a](db0153a7213a9b0bbafb43bc2762e2060f1ec9d1))
### Features
* *(api tokens)* Add api token struct and migration
* *(api tokens)* Add crud routes to manage api tokens
* *(api tokens)* Add tests
* *(api tokens)* Better error message for invalid tokens
* *(api tokens)* Check for expiry date
* *(api tokens)* Check for scopes
* *(api tokens)* Check if a provided token matched a hashed on in the database
* *(api tokens)* Check permissions when saving
* *(api tokens)* Move token validation middleware to new function
* *(api tokens)* Properly hash tokens
* *(api)* Enable notifications for api token routes
* *(caldav)* Add support for subtasks (i.e. `RELATED-TO` property) in CalDAV (#1634)
* *(cli)* Added --confirm/-c argument when deleting users to bypass prompt (#86)
* *(docs)* Update sample config and docs about Typesense config
* *(metrics)* Add active link share logins
* *(metrics)* Add total number of attachments metric
* *(metrics)* Add total number of files metric
* *(migration)* Migration from other services now happens in the background
* *(notifications)* Add endpoint to mark all notifications as read
* *(notify)* Don't notify disabled users
* *(reminders)* Include project in reminder notification
* *(tasks)* Add periodic resync of updated tasks to Typesense
* *(tasks)* Add searching via typesense
* *(tasks)* Add typesense indexing
* *(tasks)* Allow filtering for reminders, assignees and labels with Typesense
* *(tasks)* Find tasks by their identifier when searching with Typesense
* *(tasks)* Make sorting and filtering work with Typesense
* *(tasks)* Remove deleted tasks from Typesense
* *(typesense)* Add new tasks to typesense directly when they are created
* *(webhooks)* Add basic crud actions for webhooks
* *(webhooks)* Add basic sending of webhooks
* *(webhooks)* Add created by user object when returning all webhooks
* *(webhooks)* Add event listener to send webhook payload
* *(webhooks)* Add filter based on project id
* *(webhooks)* Add hmac signing
* *(webhooks)* Add index on project id
* *(webhooks)* Add route to get all available webhook events
* *(webhooks)* Add routes
* *(webhooks)* Add setting to enable webhooks
* *(webhooks)* Add support for webhook proxy
* *(webhooks)* Add timeout config option
* *(webhooks)* Expose whether webhooks are enabled
* *(webhooks)* Prevent link shares from managing webhooks
* *(webhooks)* Register task and project events as webhook
* *(webhooks)* Set user agent header to Vikunja
* *(webhooks)* Validate events and target url* Search improvemens (#1598) ([6f825fa](6f825fa4133a3200dab8a46faa2932cf5633263c))
* Accept hex values which start with a # ([a1ea77f](a1ea77f7519efe7696bce018814071cbabaaa62c))
* Add demo mode flag ([97b5cd3](97b5cd306f44a23d5f8923b1cf750533c1ca3e10))
* Add setting for default bucket ([b99b323](b99b323c4c5a003c5b34e0196da566816469c608))
* Add very basic bruno collection ([7eb59f5](7eb59f577c32791af77770e5c4ca2e1d7c01ee04))
* Api tokens ([60cd125](60cd1250a0431f33748f83da3256f19ee8144dde))
* Convert all markdown content to html (#1625) ([8a4856a](8a4856ad8747dd590f61e80212f77fb6e41cfb4b))
* Endpoint to get all token routes ([1ca93a6](1ca93a678e6d931aa3afb3aaa654763ee8304d3b))
* Make default bucket configurable ([60bd5c8](60bd5c8a79af18b09cb87c650436d0eff771d670))
* Make unauthenticated user routes rate limit configurable ([c6c465c](c6c465c273037fd2c1f02360e647366834ab0cde))
* Move done bucket setting to project ([bbbb45d](bbbb45d22461ed88d744cc1d66f74a743a51b843))
* Webhooks (#1624) ([4d9baa3](4d9baa38d0861c082aa21713744927d520750fd6))
### Miscellaneous Tasks
* *(api tokens)* Add swagger docs about api token auth
* *(api tokens)* Remove updated date from tokens as it can't be updated anyway
* *(build)* Use our own goproxy to prevent issues with packages not found
* *(caldav)* Improve trimming .ics file ending
* *(ci)* Sign drone config
* *(ci)* Use golangci-lint docker image for lint step
* *(tasks)* Better error messages when indexing tasks into Typesense
* *(test)* Add task deleted assertion to project deletion test
* *(webhooks)* Remove WebhookEvent interface
* *(webhooks)* Reuse webhook client
* *(webhooks)* Simplify registering webhook events* Remove year from copyright headers ([e518fb1](e518fb1191c0a21180f91bf2defcef80e26f02a7))
* Add pr lockdown ([0abf686](0abf686f6630e052c43537cfcaf7b90eebcaa910))
* Assume username instead of id when parsing fails for user commands (#87) ([137f3bc](137f3bc151d6417ba3cc8362afec1e7457915ef5))
* Go mod tidy ([7c4b2c9](7c4b2c9b3911214d42ab9ab9a01605828013da55))
* Reverse the coupling of module log and config (#1606) ([ad04d30](ad04d302af94fe3cf8e5a70ebb87af9002da5610))
* Update contributing guidelines ([83f02b1](83f02b1ebc4ceda8226fb6d9c004241c0c47ae8d))
### Other
* *(other)* [skip ci] Updated swagger docs
## [0.21.0] - 2023-07-07
### Bug Fixes
@@ -2174,4 +2431,4 @@ Misc bugfixes and improvements to the build process
## [0.2] - 2018-10-17
## [0.1] - 2018-09-20
## [0.1] - 2018-09-20
+3
View File
@@ -0,0 +1,3 @@
# Contribution Guidelines
Please check out the guidelines on https://vikunja.io/docs/development/
+2 -1
View File
@@ -3,7 +3,7 @@
# │─││ │││ │ │
# ┘─┘┘─┘┘┘─┘┘─┘
FROM --platform=$BUILDPLATFORM techknowlogick/xgo:go-1.20.x AS builder
FROM --platform=$BUILDPLATFORM techknowlogick/xgo:go-1.21.x AS builder
RUN go install github.com/magefile/mage@latest && \
mv /go/bin/mage /usr/local/go/bin
@@ -13,6 +13,7 @@ COPY . ./
ARG TARGETOS TARGETARCH TARGETVARIANT
ENV GOPROXY https://goproxy.kolaente.de
RUN export PATH=$PATH:$GOPATH/bin && \
mage build:clean && \
mage release:xgo "${TARGETOS}/${TARGETARCH}/${TARGETVARIANT}"
+1 -1
View File
@@ -2,7 +2,7 @@
[![Build Status](https://drone.kolaente.de/api/badges/vikunja/api/status.svg)](https://drone.kolaente.de/vikunja/api)
[![License: AGPL v3](https://img.shields.io/badge/License-AGPL%20v3-blue.svg)](LICENSE)
[![Download](https://img.shields.io/badge/download-v0.21.0-brightgreen.svg)](https://dl.vikunja.io)
[![Download](https://img.shields.io/badge/download-v0.22.0-brightgreen.svg)](https://dl.vikunja.io)
[![Docker Pulls](https://img.shields.io/docker/pulls/vikunja/api.svg)](https://hub.docker.com/r/vikunja/api/)
[![Swagger Docs](https://img.shields.io/badge/swagger-docs-brightgreen.svg)](https://try.vikunja.io/api/v1/docs)
[![Go Report Card](https://goreportcard.com/badge/kolaente.dev/vikunja/api)](https://goreportcard.com/report/kolaente.dev/vikunja/api)
-12
View File
@@ -1,12 +0,0 @@
#!/usr/bin/env bash
curl -X POST http://localhost:3456/api/v1/register -H 'Content-Type: application/json' -d '{"username":"demo","password":"demo","email":"demo@vikunja.io"}'
BEARER=`curl -X POST -H 'Content-Type: application/json' -d '{"username": "demo", "password":"demo"}' localhost:3456/api/v1/login | jq -r '.token'`
echo "Bearer: $BEARER"
curl -X POST localhost:3456/api/v1/tokenTest -H "Authorization: Bearer $BEARER"
curl -X PUT localhost:3456/api/v1/namespaces/1/lists -H 'Content-Type: application/json' -H "Authorization: Bearer $BEARER" -d '{"title":"lorem"}'
curl -X PUT localhost:3456/api/v1/lists/1 -H 'Content-Type: application/json' -H "Authorization: Bearer $BEARER" -d '{"text":"lorem"}'
curl -X PUT -H "Authorization: Bearer $BEARER" localhost:3456/api/v1/tasks/1/attachments -F 'files=@/home/konrad/Pictures/Wallpaper/greg-rakozy-_Q4mepyyjMw-unsplash.jpg'
-29
View File
@@ -1,29 +0,0 @@
### Authorization by token, part 1. Retrieve and save token.
POST http://localhost:8080/api/v1/login
Content-Type: application/json
{
"username": "user3",
"password": "1234"
}
> {% client.global.set("auth_token", response.body.token); %}
### Register
POST http://localhost:8080/api/v1/register
Content-Type: application/json
{
"username": "user",
"password": "1234",
"email": "5@knt.li"
}
###
# Token test
POST http://localhost:8080/api/v1/tokenTest
Authorization: Bearer {{auth_token}}
Content-Type: application/json
###
-70
View File
@@ -1,70 +0,0 @@
# Get all labels
GET http://localhost:8080/api/v1/labels
Authorization: Bearer {{auth_token}}
###
# Add a new label
PUT http://localhost:8080/api/v1/labels
Authorization: Bearer {{auth_token}}
Content-Type: application/json
{
"title": "test5"
}
###
# Delete a label
DELETE http://localhost:8080/api/v1/labels/6
Authorization: Bearer {{auth_token}}
###
# Update a label
POST http://localhost:8080/api/v1/labels/1
Authorization: Bearer {{auth_token}}
Content-Type: application/json
{
"title": "testschinkenbrot",
"description": "käsebrot"
}
###
# Get one label
GET http://localhost:8080/api/v1/labels/1
Authorization: Bearer {{auth_token}}
###
# Get all labels on a task
GET http://localhost:8080/api/v1/tasks/3565/labels
Authorization: Bearer {{auth_token}}
###
# Add a new label to a task
PUT http://localhost:8080/api/v1/tasks/35236365/labels
Authorization: Bearer {{auth_token}}
Content-Type: application/json
{
"label_id": 1
}
###
# Delete a label from a task
DELETE http://localhost:8080/api/v1/tasks/3565/labels/1
Authorization: Bearer {{auth_token}}
###
# Add a new label to a task
POST http://localhost:8080/api/v1/tasks/3565/labels/bulk
Authorization: Bearer {{auth_token}}
Content-Type: application/json
{
"labels": [
{"id": 1},
{"id": 2},
{"id": 3}
]
}
###
-177
View File
@@ -1,177 +0,0 @@
# Get all lists
GET http://localhost:8080/api/v1/namespaces/35/lists
Authorization: Bearer {{auth_token}}
###
# Get one list
GET http://localhost:8080/api/v1/lists/3
Authorization: Bearer {{auth_token}}
###
# Add a new list
PUT http://localhost:8080/api/v1/namespaces/35/lists
Authorization: Bearer {{auth_token}}
Content-Type: application/json
{
"title": "test"
}
###
# Add a new item
PUT http://localhost:8080/api/v1/lists/1
Authorization: Bearer {{auth_token}}
Content-Type: application/json
{
"text": "Task",
"description": "Schinken"
}
###
# Delete a task from a list
DELETE http://localhost:8080/api/v1/lists/14
Authorization: Bearer {{auth_token}}
###
# Get all teams who have access to that list
GET http://localhost:8080/api/v1/lists/28/teams
Authorization: Bearer {{auth_token}}
###
# Give a team access to that list
PUT http://localhost:8080/api/v1/lists/1/teams
Authorization: Bearer {{auth_token}}
Content-Type: application/json
{"team_id":2, "right": 1}
###
# Update a teams access to that list
POST http://localhost:8080/api/v1/lists/1/teams/2
Authorization: Bearer {{auth_token}}
Content-Type: application/json
{"right": 0}
###
# Delete a team from a list
DELETE http://localhost:8080/api/v1/lists/10235/teams/1
Authorization: Bearer {{auth_token}}
###
# Delete a team from a list
DELETE http://localhost:8080/api/v1/lists/10235/teams/1
Authorization: Bearer {{auth_token}}
###
# Get all users who have access to that list
GET http://localhost:8080/api/v1/lists/28/users
Authorization: Bearer {{auth_token}}
###
# Give a user access to that list
PUT http://localhost:8080/api/v1/lists/3/users
Authorization: Bearer {{auth_token}}
Content-Type: application/json
{"userID":"user4", "right":1}
###
# Update a users access to that list
POST http://localhost:8080/api/v1/lists/30/users/3
Authorization: Bearer {{auth_token}}
Content-Type: application/json
{"right":2}
###
# Delete a user from a list
DELETE http://localhost:8080/api/v1/lists/28/users/3
Authorization: Bearer {{auth_token}}
###
# Get all pending tasks
GET http://localhost:8080/api/v1/tasks/all
Authorization: Bearer {{auth_token}}
###
# Get all pending tasks with priorities
GET http://localhost:8080/api/v1/tasks/all?sort=priorityasc
Authorization: Bearer {{auth_token}}
###
# Get all pending tasks in a range
GET http://localhost:8080/api/v1/tasks/all/dueadateasc/1546784000/1548784000
Authorization: Bearer {{auth_token}}
###
# Get all pending tasks in caldav
GET http://localhost:8080/api/v1/tasks/caldav
#Authorization: Bearer {{auth_token}}
###
# Update a task
POST http://localhost:8080/api/v1/tasks/3565
Authorization: Bearer {{auth_token}}
Content-Type: application/json
{
"priority": 0
}
###
# Bulk update multiple tasks at once
POST http://localhost:8080/api/v1/tasks/bulk
Authorization: Bearer {{auth_token}}
Content-Type: application/json
{
"task_ids": [3518,3519,3521],
"text":"bulkupdated"
}
###
# Get all assignees
GET http://localhost:8080/api/v1/tasks/3565/assignees
Authorization: Bearer {{auth_token}}
###
# Add a bunch of assignees
PUT http://localhost:8080/api/v1/tasks/3565/assignees/bulk
Authorization: Bearer {{auth_token}}
Content-Type: application/json
{
"assignees": [
{"id": 17}
]
}
###
# Get all users who have access to a list
GET http://localhost:8080/api/v1/lists/3/users
Authorization: Bearer {{auth_token}}
###
-71
View File
@@ -1,71 +0,0 @@
# Get all namespaces
GET http://localhost:8080/api/v1/namespaces
Authorization: Bearer {{auth_token}}
###
# Get one namespaces
GET http://localhost:8080/api/v1/namespaces/-1
Authorization: Bearer {{auth_token}}
###
# Get all users who have access to that namespace
GET http://localhost:8080/api/v1/namespaces/12/users
Authorization: Bearer {{auth_token}}
###
# Give a user access to that namespace
PUT http://localhost:8080/api/v1/namespaces/1/users
Authorization: Bearer {{auth_token}}
Content-Type: application/json
{"user_id":3, "right": 0}
###
# Update a users access to that namespace
POST http://localhost:8080/api/v1/namespaces/1/users/3
Authorization: Bearer {{auth_token}}
Content-Type: application/json
{"right": 2}
###
# Delete a user from a namespace
DELETE http://localhost:8080/api/v1/namespaces/1/users/2
Authorization: Bearer {{auth_token}}
###
# Get all teams who have access to that namespace
GET http://localhost:8080/api/v1/namespaces/1/teams
Authorization: Bearer {{auth_token}}
###
# Give a team access to that namespace
PUT http://localhost:8080/api/v1/namespaces/1/teams
Authorization: Bearer {{auth_token}}
Content-Type: application/json
{"team_id":3, "right": 0}
###
# Update a teams access to that namespace
POST http://localhost:8080/api/v1/namespaces/1/teams/1
Authorization: Bearer {{auth_token}}
Content-Type: application/json
{"right": 0}
###
# Delete a team from a namespace
DELETE http://localhost:8080/api/v1/namespaces/1/teams/2
Authorization: Bearer {{auth_token}}
###
-29
View File
@@ -1,29 +0,0 @@
# Get all teams
GET http://localhost:8080/api/v1/teams
Authorization: Bearer {{auth_token}}
###
# Get one team
GET http://localhost:8080/api/v1/teams/28
Authorization: Bearer {{auth_token}}
###
# Add a new member to that team
PUT http://localhost:8080/api/v1/teams/28/members
Authorization: Bearer {{auth_token}}
Content-Type: application/json
{
"user_id": 2
}
###
# Delete a member from a team
DELETE http://localhost:8080/api/v1/teams/28/members/2
Authorization: Bearer {{auth_token}}
###
-53
View File
@@ -1,53 +0,0 @@
# Get all users
GET http://localhost:8080/api/v1/user
Authorization: Bearer {{auth_token}}
######
# Search for a user
GET http://localhost:8080/api/v1/users?s=3
Authorization: Bearer {{auth_token}}
###
## Update password
POST http://localhost:8080/api/v1/user/password
Authorization: Bearer {{auth_token}}
Content-Type: application/json
{
"old_password": "1234",
"new_password": "1234"
}
### Request a password to reset a password
POST http://localhost:8080/api/v1/user/password/token
Content-Type: application/json
Accept: application/json
{
"email": "k@knt.li"
}
### Request a token to reset a password
POST http://localhost:8080/api/v1/user/password/reset
Content-Type: application/json
Accept: application/json
{
"token": "eAsZzakgqARnjzXHqsHqZtSUKuiOhoJjHANhgTxUIDBSalhbtdpAdLeywGXzVDBuRQGNpHdMxoHXhLVSlzpJsFvuoJgMdkhRhkNhaQXfufuZCdtUlerZHSJQLgYMUryHIxIREcmZLtWoZVrYyARkCvkyFhcGtoCwQOEjAOEZMQQuxTVoGYfAqcfNggQnerUcXCiRIgRtkusXSnltomhaeyRwAbrckXFeXxUjslgplSGqSTOqJTYuhrSzAVTwNvuYyvuXLaZoNnJEyeVDWlRydnxfgUQjQZOKwCBRWVQPKpZhlslLUyUAMsRQkHITkruQCjDnOGCCRsSNplbNCEuDmMfpWYHSQAcQIDZtbQWkxzpfmHDMQvvKPPrxEnrTErlvTfKDKICFYPQxXNpNE",
"new_password": "1234"
}
### Confirm a users email address
POST http://localhost:8080/api/v1/user/confirm
Content-Type: application/json
Accept: application/json
{
"token": ""
}
###
+1 -1
View File
@@ -1,5 +1,5 @@
Vikunja is a to-do list application to facilitate your life.
Copyright 2018-2021 Vikunja and contributors. All rights reserved.
Copyright 2018-present Vikunja and contributors. All rights reserved.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public Licensee as published by
+30 -1
View File
@@ -59,6 +59,9 @@ service:
# The maximum size clients will be able to request for user avatars.
# If clients request a size bigger than this, it will be changed on the fly.
maxavatarsize: 1024
# If set to true, the frontend will show a big red warning not to use this instance for real data as it will be cleared out.
# You probably don't need to set this value, it was created specifically for usage on [try](https://try.vikunja.io).
demomode: false
database:
# Database type to use. Supported types are mysql, postgres and sqlite.
@@ -91,6 +94,18 @@ database:
# Enable SSL/TLS for mysql connections. Options: false, true, skip-verify, preferred
tls: false
typesense:
# Whether to enable the Typesense integration. If true, all tasks will be synced to the configured Typesense
# instance and all search and filtering will run through Typesense instead of only through the database.
# Typesense allows fast fulltext search including fuzzy matching support. It may return different results than
# what you'd get with a database-only search.
enabled: false
# The url to the Typesense instance you want to use. Can be hosted locally or in Typesense Cloud as long
# as Vikunja is able to reach it.
url: ''
# The Typesense API key you want to use.
apikey: ''
redis:
# Whether to enable redis or not
enabled: false
@@ -176,6 +191,10 @@ ratelimit:
# Possible values are "keyvalue", "memory" or "redis".
# When choosing "keyvalue" this setting follows the one configured in the "keyvalue" section.
store: keyvalue
# The number of requests a user can make from the same IP to all unauthenticated routes (login, register,
# password confirmation, email verification, password reset request) per minute. This limit cannot be disabled.
# You should only change this if you know what you're doing.
noauthlimit: 10
files:
# The path where files are stored
@@ -327,7 +346,17 @@ defaultsettings:
default_project_id: 0
# Start of the week for the user. `0` is sunday, `1` is monday and so on.
week_start: 0
# The language of the user interface. Must be an ISO 639-1 language code. Will default to the browser language the user uses when signing up.
# The language of the user interface. Must be an ISO 639-1 language code followed by an ISO 3166-1 alpha-2 country code. Check https://kolaente.dev/vikunja/frontend/src/branch/main/src/i18n/lang for a list of possible languages. Will default to the browser language the user uses when signing up.
language: <unset>
# The time zone of each individual user. This will affect when users get reminders and overdue task emails.
timezone: <time zone set at service.timezone>
webhooks:
# Whether to enable support for webhooks
enabled: true
# The timout in seconds until a webhook request fails when no response has been received.
timoutseconds: 30
# The URL of [a mole instance](https://github.com/frain-dev/mole) to use to proxy outgoing webhook requests. You should use this and configure appropriately if you're not the only one using your Vikunja instance. More info about why: https://webhooks.fyi/best-practices/webhook-providers#implement-security-on-egress-communication. Must be used in combination with `webhooks.password` (see below).
proxyurl:
# The proxy password to use when authenticating against the proxy.
proxypassword:
+47 -5
View File
@@ -17,7 +17,9 @@ menu:
## General
To contribute to Vikunja, fork the project and work on the main branch.
Once you feel like your changes are ready, open a PR in the respective repo.
Once you feel like your changes are ready, open a PR in the respective repo [on our Gitea instance](https://kolaente.dev/vikunja).
We cannot accept PRs on mirror sites.
A maintainer will take a look and give you feedback. Once everyone is happy, the PR gets merged and released.
If you plan to do a bigger change, it is better to open an issue for discussion first.
@@ -26,7 +28,7 @@ If you plan to do a bigger change, it is better to open an issue for discussion
The code for the api is located at [code.vikunja.io/api](https://code.vikunja.io/api).
We use go modules to manage third-party libraries for Vikunja, so you'll need at least go `1.17` to use these.
You'll need at least Go 1.21 to build Vikunja's api.
A lot of developing tasks are automated using a Magefile, so make sure to [take a look at it]({{< ref "mage.md">}}).
@@ -38,11 +40,51 @@ Make sure to check the other doc articles for specific development tasks like [t
The code for the frontend is located at [code.vikunja.io/frontend](https://code.vikunja.io/frontend).
More instructions can be found in the repo's README.
You need to have [pnpm](https://pnpm.io/) and nodejs in version 16 or 18 installed.
You need to have [pnpm](https://pnpm.io/) and Node.JS in version 18 or higher installed.
## Git flow
## Pull Requests
All Pull Requests must be made [on our Gitea instance](https://kolaente.dev/vikunja).
We cannot accept PRs on mirror sites.
Please try to make your pull request easy to review.
For that, please read the [*Best Practices for Faster Reviews*](https://github.com/kubernetes/community/blob/261cb0fd089b64002c91e8eddceebf032462ccd6/contributors/guide/pull-requests.md#best-practices-for-faster-reviews) guide.
It has lots of useful tips for any project you may want to contribute to.
Some of the key points:
- Make small pull requests.
The smaller, the faster to review and the more likely it will be merged soon.
- Don't make changes unrelated to your PR.
Maybe there are typos on some comments, maybe refactoring would be welcome on a function…
but if that is not related to your PR, please make *another* PR for that.
- Split big pull requests into multiple small ones.
An incremental change will be faster to review than a huge PR.
- Allow edits by maintainers. This way, the maintainers will take care of merging the PR later on instead of you.
### PR title and summary
In the PR title, describe the problem you are fixing, not how you are fixing it.
Use the first comment as a summary of your PR.
In the PR summary, you can describe exactly how you are fixing this problem.
Keep this summary up-to-date as the PR evolves.
If your PR changes the UI, you must add **after** screenshots in the PR summary.
If your PR closes an issue, you must note that in a way that both GitHub and Gitea understand, i.e. by appending a paragraph like
```text
Fixes/Closes/Resolves #<ISSUE_NR_X>.
Fixes/Closes/Resolves #<ISSUE_NR_Y>.
```
to your summary.
Each issue that will be closed must stand on a separate line.
If your PR is related to a discussion in the forum, you must add a link to the forum discussion.
### Git flow
The `main` branch is the latest and bleeding edge branch with all changes. Unstable releases are automatically created from this branch.
New Pull-Requests should be made against the `main` branch.
A release gets tagged from the main branch with the version name as tag name.
@@ -52,4 +94,4 @@ Backports and point-releases should go to a `release/version` branch, based on t
We're using [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) because they greatly simplify generating release notes.
It is not required to use them when creating a PR, but appreciated.
It is not required to use them when creating a PR, but appreciated.
+1 -1
View File
@@ -19,7 +19,7 @@ To completely build Vikunja from source, you need to build the api and frontend.
The Vikunja API has no other dependencies than go itself.
That means compiling it boils down to these steps:
1. Make sure [Go](https://golang.org/doc/install) is properly installed on your system. You'll need at least Go `1.19`.
1. Make sure [Go](https://golang.org/doc/install) is properly installed on your system. You'll need at least Go `1.21`.
2. Make sure [Mage](https://magefile.org) is properly installed on your system.
3. Clone the repo with `git clone https://code.vikunja.io/api` and switch into the directory.
4. Run `mage build` in the source of this repo. This will build a binary in the root of the repo which will be able to run on your system.
+119 -1
View File
@@ -333,6 +333,18 @@ Full path: `service.maxavatarsize`
Environment path: `VIKUNJA_SERVICE_MAXAVATARSIZE`
### demomode
If set to true, the frontend will show a big red warning not to use this instance for real data as it will be cleared out.
You probably don't need to set this value, it was created specifically for usage on [try](https://try.vikunja.io).
Default: `false`
Full path: `service.demomode`
Environment path: `VIKUNJA_SERVICE_DEMOMODE`
---
## database
@@ -494,6 +506,49 @@ Full path: `database.tls`
Environment path: `VIKUNJA_DATABASE_TLS`
---
## typesense
### enabled
Whether to enable the Typesense integration. If true, all tasks will be synced to the configured Typesense
instance and all search and filtering will run through Typesense instead of only through the database.
Typesense allows fast fulltext search including fuzzy matching support. It may return different results than
what you'd get with a database-only search.
Default: `false`
Full path: `typesense.enabled`
Environment path: `VIKUNJA_TYPESENSE_ENABLED`
### url
The url to the Typesense instance you want to use. Can be hosted locally or in Typesense Cloud as long
as Vikunja is able to reach it.
Default: `<empty>`
Full path: `typesense.url`
Environment path: `VIKUNJA_TYPESENSE_URL`
### apikey
The Typesense API key you want to use.
Default: `<empty>`
Full path: `typesense.apikey`
Environment path: `VIKUNJA_TYPESENSE_APIKEY`
---
## redis
@@ -914,6 +969,19 @@ Full path: `ratelimit.store`
Environment path: `VIKUNJA_RATELIMIT_STORE`
### noauthlimit
The number of requests a user can make from the same IP to all unauthenticated routes (login, register,
password confirmation, email verification, password reset request) per minute. This limit cannot be disabled.
You should only change this if you know what you're doing.
Default: `10`
Full path: `ratelimit.noauthlimit`
Environment path: `VIKUNJA_RATELIMIT_NOAUTHLIMIT`
---
## files
@@ -1252,7 +1320,7 @@ Environment path: `VIKUNJA_DEFAULTSETTINGS_WEEK_START`
### language
The language of the user interface. Must be an ISO 639-1 language code. Will default to the browser language the user uses when signing up.
The language of the user interface. Must be an ISO 639-1 language code followed by an ISO 3166-1 alpha-2 country code. Check https://kolaente.dev/vikunja/frontend/src/branch/main/src/i18n/lang for a list of possible languages. Will default to the browser language the user uses when signing up.
Default: `<unset>`
@@ -1272,3 +1340,53 @@ Full path: `defaultsettings.timezone`
Environment path: `VIKUNJA_DEFAULTSETTINGS_TIMEZONE`
---
## webhooks
### enabled
Whether to enable support for webhooks
Default: `true`
Full path: `webhooks.enabled`
Environment path: `VIKUNJA_WEBHOOKS_ENABLED`
### timoutseconds
The timout in seconds until a webhook request fails when no response has been received.
Default: `30`
Full path: `webhooks.timoutseconds`
Environment path: `VIKUNJA_WEBHOOKS_TIMOUTSECONDS`
### proxyurl
The URL of [a mole instance](https://github.com/frain-dev/mole) to use to proxy outgoing webhook requests. You should use this and configure appropriately if you're not the only one using your Vikunja instance. More info about why: https://webhooks.fyi/best-practices/webhook-providers#implement-security-on-egress-communication. Must be used in combination with `webhooks.password` (see below).
Default: `<empty>`
Full path: `webhooks.proxyurl`
Environment path: `VIKUNJA_WEBHOOKS_PROXYURL`
### proxypassword
The proxy password to use when authenticating against the proxy.
Default: `<empty>`
Full path: `webhooks.proxypassword`
Environment path: `VIKUNJA_WEBHOOKS_PROXYPASSWORD`
+24
View File
@@ -88,3 +88,27 @@ Keycloak Config:
- Set `Root Url` to `https://vikunja.mydomain.com`
- Set `Valid redirect URIs` to `/auth/openid/keycloak`
- Create the client the navigate to the credentials tab and copy the `Client secret`
## Authentik
Authentik Config:
- Create a new Provider called "Vikunja" in Authentik
- Set the `Redirect URIs/Origins (RegEx)` to `https://vikunja.mydomain.com/auth/openid/authentik`
- Copy the Client ID and Client Secret
Vikunja Config:
```yaml
auth:
openid:
enabled: true
redirecturl: "https://vikunja.mydomain.com/auth/openid/"
providers:
- name: authentik
authurl: "https://authentik.mydomain.com/application/o/vikunja"
logouturl: "https://authentik.mydomain.com/application/o/vikunja/end-session/"
clientid: "" # copy from Authetik
clientsecret: "" # copy from Authentik
```
**Note:** The `authurl` that Vikunja requires is not the `Authorize URL` that you can see in the Provider. Vikunja uses Open ID Discovery to find the correct endpoint to use. Vikunja does this by automatically accessing the `OpenID Configuration URL` (usually `https://authentik.mydomain.com/application/o/vikunja/.well-known/openid-configuration`). Use this URL without the `.well-known/openid-configuration` as the `authurl`.
+316 -127
View File
@@ -1,127 +1,316 @@
---
date: "2019-02-12:00:00+02:00"
title: "Reverse Proxy"
draft: false
type: "doc"
menu:
sidebar:
parent: "setup"
---
# Setup behind a reverse proxy which also serves the frontend
These examples assume you have an instance of the backend running on your server listening on port `3456`.
If you've changed this setting, you need to update the server configurations accordingly.
{{< table_of_contents >}}
## NGINX
Below are two example configurations which you can put in your `nginx.conf`:
You may need to adjust `server_name` and `root` accordingly.
### with gzip enabled (recommended)
{{< highlight conf >}}
gzip on;
gzip_disable "msie6";
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_min_length 256;
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/vnd.ms-fontobject application/x-font-ttf font/opentype image/svg+xml;
server {
listen 80;
server_name localhost;
location / {
root /path/to/vikunja/static/frontend/files;
try_files $uri $uri/ /;
index index.html index.htm;
}
location ~* ^/(api|dav|\.well-known)/ {
proxy_pass http://localhost:3456;
client_max_body_size 20M;
}
}
{{< /highlight >}}
<div class="notification is-warning">
<b>NOTE:</b> If you change the max upload size in Vikunja's settings, you'll need to also change the <code>client_max_body_size</code> in the nginx proxy config.
</div>
### without gzip
{{< highlight conf >}}
server {
listen 80;
server_name localhost;
location / {
root /path/to/vikunja/static/frontend/files;
try_files $uri $uri/ /;
index index.html index.htm;
}
location ~* ^/(api|dav|\.well-known)/ {
proxy_pass http://localhost:3456;
client_max_body_size 20M;
}
}
{{< /highlight >}}
<div class="notification is-warning">
<b>NOTE:</b> If you change the max upload size in Vikunja's settings, you'll need to also change the <code>client_max_body_size</code> in the nginx proxy config.
</div>
## NGINX Proxy Manager (NPM)
1. Create a standard Proxy Host for the Vikunja Frontend within NPM and point it to the URL you plan to use. The next several steps will enable the Proxy Host to successfully navigate to the API (on port 3456).
2. Verify that the page will pull up in your browser. (Do not bother trying to log in. It won't work. Trust me.)
3. Now, we'll work with the NPM container, so you need to identify the container name for your NPM installation. e.g. NGINX-PM
4. From the command line, enter `sudo docker exec -it [NGINX-PM container name] /bin/bash` and navigate to the proxy hosts folder where the `.conf` files are stashed. Probably `/data/nginx/proxy_host`. (This folder is a persistent folder created in the NPM container and mounted by NPM.)
5. Locate the `.conf` file where the server_name inside the file matches your Vikunja Proxy Host. Once found, add the following code, unchanged, just above the existing location block in that file. (They are listed by number, not name.)
```nginx
location ~* ^/(api|dav|\.well-known)/ {
proxy_pass http://api:3456;
client_max_body_size 20M;
}
```
6. After saving the edited file, return to NPM's UI browser window and refresh the page to verify your Proxy Host for Vikunja is still online.
7. Now, switch over to your Vikunja browser window and hit refresh. If you configured your URL correctly in original Vikunja container, you should be all set and the browser will correctly show Vikunja. If not, you'll need to adjust the address in the top of the login subscreen to match your proxy address.
## Apache
Put the following config in `cat /etc/apache2/sites-available/vikunja.conf`:
{{< highlight aconf >}}
<VirtualHost *:80>
ServerName localhost
<Proxy *>
Order Deny,Allow
Allow from all
</Proxy>
ProxyPass /api http://localhost:3456/api
ProxyPassReverse /api http://localhost:3456/api
ProxyPass /dav http://localhost:3456/dav
ProxyPassReverse /dav http://localhost:3456/dav
ProxyPass /.well-known http://localhost:3456/.well-known
ProxyPassReverse /.well-known http://localhost:3456/.well-known
DocumentRoot /var/www/html
RewriteEngine On
RewriteRule ^\/?(favicon\.ico|assets|audio|fonts|images|manifest\.webmanifest|robots\.txt|sw\.js|workbox-.*|api|dav|\.well-known) - [L]
RewriteRule ^(.*)$ /index.html [QSA,L]
</VirtualHost>
{{< /highlight >}}
**Note:** The apache modules `proxy`, `proxy_http` and `rewrite` must be enabled for this.
For more details see the [frontend apache configuration]({{< ref "install-frontend.md#apache">}}).
---
date: "2019-02-12:00:00+02:00"
title: "Reverse Proxy"
draft: false
type: "doc"
menu:
sidebar:
parent: "setup"
---
# Setup behind a reverse proxy which also serves the frontend
These examples assume you have an instance of the backend running on your server listening on port `3456`.
If you've changed this setting, you need to update the server configurations accordingly.
{{< table_of_contents >}}
## NGINX
Below are two example configurations which you can put in your `nginx.conf`:
You may need to adjust `server_name` and `root` accordingly.
### with gzip enabled (recommended)
{{< highlight conf >}}
gzip on;
gzip_disable "msie6";
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_min_length 256;
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/vnd.ms-fontobject application/x-font-ttf font/opentype image/svg+xml;
server {
listen 80;
server_name localhost;
location / {
root /path/to/vikunja/static/frontend/files;
try_files $uri $uri/ /;
index index.html index.htm;
}
location ~* ^/(api|dav|\.well-known)/ {
proxy_pass http://localhost:3456;
client_max_body_size 20M;
}
}
{{< /highlight >}}
<div class="notification is-warning">
<b>NOTE:</b> If you change the max upload size in Vikunja's settings, you'll need to also change the <code>client_max_body_size</code> in the nginx proxy config.
</div>
### without gzip
{{< highlight conf >}}
server {
listen 80;
server_name localhost;
location / {
root /path/to/vikunja/static/frontend/files;
try_files $uri $uri/ /;
index index.html index.htm;
}
location ~* ^/(api|dav|\.well-known)/ {
proxy_pass http://localhost:3456;
client_max_body_size 20M;
}
}
{{< /highlight >}}
<div class="notification is-warning">
<b>NOTE:</b> If you change the max upload size in Vikunja's settings, you'll need to also change the <code>client_max_body_size</code> in the nginx proxy config.
</div>
## NGINX Proxy Manager (NPM)
### Method 1
Following the [Docker Walkthrough]({{< ref "docker-start-to-finish.md" >}}) guide, you should be able to get Vikunja to work via HTTP connection to your server IP.
From there, all you have to do is adjust the following things:
#### In `docker-compose.yml`
Under `api:`,
1. Change `VIKUNJA_SERVICE_FRONTENDURL:` to your desired domain with `https://` and `/`.
2. Expose your desired port on host under `ports:`.
example:
```yaml
api:
image: vikunja/api
environment:
VIKUNJA_DATABASE_HOST: db
VIKUNJA_DATABASE_PASSWORD: secret
VIKUNJA_DATABASE_TYPE: mysql
VIKUNJA_DATABASE_USER: vikunja
VIKUNJA_DATABASE_DATABASE: vikunja
VIKUNJA_SERVICE_JWTSECRET: <your-random-secret>
VIKUNJA_SERVICE_FRONTENDURL: https://vikunja.your-domain.com/ # change vikunja.your-domain.com to your desired domain/subdomain.
ports:
- 3456:3456 # Change 3456 on the left to the port of your choice.
volumes:
- ./files:/app/vikunja/files
depends_on:
- db
restart: unless-stopped
```
Under `frontend:`,
1. Add `VIKUNJA_API_URL:` under `environment:` and input your desired `API` domain with `https://` and `/api/v1/`. The `API` domain should be different from the one in `VIKUNJA_SERVICE_FRONTENDURL:`.
example:
```yaml
frontend:
image: vikunja/frontend
environment:
VIKUNJA_API_URL: https://api.your-domain.com/api/v1/ # change api.your-domain.com to your desired domain/subdomain, it should be different from your frontend domain
restart: unless-stopped
```
Under `proxy:`,
1. Since we'll be using Nginx Proxy Manager, it should by default uses the port `80` and thus you should change `ports:` to expose another port not occupied by any service.
example:
```yaml
proxy:
image: nginx
ports:
- 1078:80 # change the number infront (host port) to whatever you desire, but make sure it's not 80 which will be used by Nginx Proxy Manager
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
- api
- frontend
restart: unless-stopped
```
#### In your DNS provider
Add two `A` records that points to your server IP.
1. `vikunja` for accessing the frontend
2. `api` for accessing the api
You are of course free to change them to whatever domain/subdomain you desire and modify the `docker-compose.yml` accordingly but the two should be different.
(Tested on Cloudflare DNS. Settings are different for different DNS provider, in this case the end result should bei `vikunja.your-domain.com` and `api.your-domain.com` respectively.)
#### In Nginx Proxy Manager
Add two Proxy Host as you normally would, and you don't have to add anything extra in Advanced.
##### Frontend
Under `Details`:
```
Domain Names:
vikunja.your-domain.com
Scheme:
http
Forward Hostname/IP:
your-server-ip
Forward Port:
1078
Cached Assets:
Optional.
Block Common Exploits:
Toggled.
Websockets Support:
Toggled.
```
Under `SSL`:
```
SSL Certificate:
However you prefer.
Force SSL:
Toggled.
HTTP/2 Support:
Toggled.
HSTS Enabled:
Toggled.
HSTS Subdomains:
Toggled.
Use a DNS Challenge:
Not toggled.
Email Address for Let's Encrypt:
your-email@email.com
```
##### API
Under `Details`:
```
Domain Names:
api.your-domain.com
Scheme:
http
Forward Hostname/IP:
your-server-ip
Forward Port:
3456
Cached Assets:
Optional.
Block Common Exploits:
Toggled.
Websockets Support:
Toggled.
```
Under `SSL`:
```
SSL Certificate:
However you prefer.
Force SSL:
Toggled.
HTTP/2 Support:
Toggled.
HSTS Enabled:
Toggled.
HSTS Subdomains:
Toggled.
Use a DNS Challenge:
Not toggled.
Email Address for Let's Encrypt:
your-email@email.com
```
Your Vikunja service should now work and your HTTPS frontend should be able to reach the API after `docker-compose`.
### Method 2
1. Create a standard Proxy Host for the Vikunja Frontend within NPM and point it to the URL you plan to use. The next several steps will enable the Proxy Host to successfully navigate to the API (on port 3456).
2. Verify that the page will pull up in your browser. (Do not bother trying to log in. It won't work. Trust me.)
3. Now, we'll work with the NPM container, so you need to identify the container name for your NPM installation. e.g. NGINX-PM
4. From the command line, enter `sudo docker exec -it [NGINX-PM container name] /bin/bash` and navigate to the proxy hosts folder where the `.conf` files are stashed. Probably `/data/nginx/proxy_host`. (This folder is a persistent folder created in the NPM container and mounted by NPM.)
5. Locate the `.conf` file where the server_name inside the file matches your Vikunja Proxy Host. Once found, add the following code, unchanged, just above the existing location block in that file. (They are listed by number, not name.)
```nginx
location ~* ^/(api|dav|\.well-known)/ {
proxy_pass http://api:3456;
client_max_body_size 20M;
}
```
6. After saving the edited file, return to NPM's UI browser window and refresh the page to verify your Proxy Host for Vikunja is still online.
7. Now, switch over to your Vikunja browser window and hit refresh. If you configured your URL correctly in original Vikunja container, you should be all set and the browser will correctly show Vikunja. If not, you'll need to adjust the address in the top of the login subscreen to match your proxy address.
## Apache
Put the following config in `cat /etc/apache2/sites-available/vikunja.conf`:
{{< highlight aconf >}}
<VirtualHost *:80>
ServerName localhost
<Proxy *>
Order Deny,Allow
Allow from all
</Proxy>
ProxyPass /api http://localhost:3456/api
ProxyPassReverse /api http://localhost:3456/api
ProxyPass /dav http://localhost:3456/dav
ProxyPassReverse /dav http://localhost:3456/dav
ProxyPass /.well-known http://localhost:3456/.well-known
ProxyPassReverse /.well-known http://localhost:3456/.well-known
DocumentRoot /var/www/html
RewriteEngine On
RewriteRule ^\/?(favicon\.ico|assets|audio|fonts|images|manifest\.webmanifest|robots\.txt|sw\.js|workbox-.*|api|dav|\.well-known) - [L]
RewriteRule ^(.*)$ /index.html [QSA,L]
</VirtualHost>
{{< /highlight >}}
**Note:** The apache modules `proxy`, `proxy_http` and `rewrite` must be enabled for this.
For more details see the [frontend apache configuration]({{< ref "install-frontend.md#apache">}}).
## Caddy
{{< highlight conf >}}
vikunja.domainname.tld {
@paths {
path /api/* /.well-known/* /dav/*
}
handle @paths {
reverse_proxy 127.0.0.1:3456
}
handle {
encode zstd gzip
root * /var/www/html/vikunja
try_files {path} index.html
file_server
}
}
{{< /highlight >}}
+23
View File
@@ -0,0 +1,23 @@
---
title: "Typesense"
date: 2023-09-29T12:23:55+02:00
draft: false
menu:
sidebar:
parent: "setup"
---
# Use Typesense for enhanced search capabilities
Vikunja supports using [Typesense](https://typesense.org/) for a better search experience.
Typesense allows fast fulltext search including fuzzy matching support.
It may return different results than what you'd get with a database-only search, but generally, the results are more relevant to what you're looking for.
This document explains how to set up and use Typesense with Vikunja.
## Setup
1. First, install Typesense on your system. Refer to [their documentation](https://typesense.org/docs/guide/install-typesense.html) for specific instructions.
2. Once Typesense is available on your system and reachable by Vikunja, add the relevant configuration keys to your Vikunja config. [Check out the docs article about this]({{< ref "config.md#typesense">}}).
3. Index all tasks currently in Vikunja. To do that, run the `vikunja index` command with the api binary. This may take a while, depending on the size of your instance.
4. Restart the api. From now on, all task changes will be automatically indexed in Typesense.
+43
View File
@@ -0,0 +1,43 @@
---
title: "n8n"
date: 2023-10-24T19:31:35+02:00
draft: false
menu:
sidebar:
parent: "usage"
---
# Using Vikunja with n8n
Vikunja maintains a [community node](https://github.com/go-vikunja/n8n-vikunja-nodes) for [n8n](https://n8n.io),
allowing you to easily integrate Vikunja with all kinds of other tools and services.
{{< table_of_contents >}}
## Installation
To install the node in your n8n installation:
1. In your n8n instance, go to **Settings > Community Nodes**.
2. Select Install.
3. Enter `n8n-nodes-vikunja` as the npm Package Name
4. Agree to the risks of using community nodes: select I understand the risks of installing unverified code from a
public source.
5. Select Install. n8n installs the node, and returns to the Community Nodes list in Settings.
6. Vikunja actions and triggers are now available in n8n.
[Official n8n docs about the installation](https://docs.n8n.io/integrations/community-nodes/installation/)
## Authentication
To authenticate your automation against Vikunja:
1. In Vikunja, go to **Settings > API Tokens** and create a new token. Use all scopes for the kind of task you want to
do. \
*Note:* If you want to use the webhook trigger node, the api token should have permissions to create, read and delete
webhooks.
2. Now in n8n, go to **Credentials** and then click on **Add Credential**.
3. Search for `Vikunja API` and click *Continue*
4. Enter the API key you created in step 1.
5. Enter the API URL of your Vikunja instance, with `/api/v1` suffix.
6. When you now create a Vikunja node, select the created credentials.
+58
View File
@@ -0,0 +1,58 @@
---
title: "Webhooks"
date: 2023-10-17T19:51:32+02:00
draft: false
type: doc
menu:
sidebar:
parent: "usage"
---
# Webhooks
Starting with version 0.22.0, Vikunja allows you to define webhooks to notify other services of events happening within Vikunja.
{{< table_of_contents >}}
## How to create webhooks
To create a webhook, in the project options select "Webhooks". The form will allow you to create and modify webhooks.
Check out [the api docs](https://try.vikunja.io/api/v1/docs#tag/webhooks) for information about how to create webhooks programatically.
## Available events and their payload
All events registered as webhook events in [the event listeners definition](https://kolaente.dev/vikunja/api/src/branch/main/pkg/models/listeners.go#L69) can be used as webhook target.
A webhook payload will look similar to this:
```json
{
"event_name": "task.created",
"time": "2023-10-17T19:39:32.924194436+02:00",
"data": {}
}
```
The `data` property will contain the raw event data as it was registered in the `listeners.go` file.
The `time` property holds the time when the webhook payload data was sent.
It always uses the ISO 8601 format with date, time and time zone offset.
## Security considerations
### Signing
Vikunja allows you to provide a secret when creating the webhook.
If you set a secret, all outgoing webhook requests will contain an `X-Vikunja-Signature` header with an HMAC signature over the webhook json payload.
Check out [webhooks.fyi](https://webhooks.fyi/security/hmac) for more information about how to validate the HMAC signature.
### Hosting webhook infrastructure
Vikunja has support to use [mole](https://github.com/frain-dev/mole) as a proxy for outgoing webhook requests.
This allows you to prevent SSRF attacts on your own infrastructure.
You should use this and [configure it appropriately]({{< ref "../setup/config.md">}}#webhooks) if you're not the only one using your Vikunja instance.
Check out [webhooks.fyi](https://webhooks.fyi/best-practices/webhook-providers#implement-security-on-egress-communication) for more information about the attack vector and reasoning to prevent this.
+86 -62
View File
@@ -19,63 +19,65 @@ module code.vikunja.io/api
require (
code.vikunja.io/web v0.0.0-20210706160506-d85def955bd3
dario.cat/mergo v1.0.0
github.com/ThreeDotsLabs/watermill v1.2.0
github.com/ThreeDotsLabs/watermill v1.3.5
github.com/adlio/trello v1.10.0
github.com/arran4/golang-ical v0.0.0-20230425234049-f69e132f2b0c
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef
github.com/arran4/golang-ical v0.2.2
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2
github.com/bbrks/go-blurhash v1.1.1
github.com/c2h5oh/datasize v0.0.0-20220606134207-859f65c6625b
github.com/coreos/go-oidc/v3 v3.6.0
github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500
github.com/coreos/go-oidc/v3 v3.9.0
github.com/cweill/gotests v1.6.0
github.com/d4l3k/messagediff v1.2.1
github.com/disintegration/imaging v1.6.2
github.com/dustinkirkland/golang-petname v0.0.0-20230626224747-e794b9370d49
github.com/gabriel-vasile/mimetype v1.4.2
github.com/getsentry/sentry-go v0.22.0
github.com/dustinkirkland/golang-petname v0.0.0-20231002161417-6a283f1aaaf2
github.com/gabriel-vasile/mimetype v1.4.3
github.com/getsentry/sentry-go v0.25.0
github.com/go-sql-driver/mysql v1.7.1
github.com/go-testfixtures/testfixtures/v3 v3.9.0
github.com/gocarina/gocsv v0.0.0-20230616125104-99d496ca653d
github.com/golang-jwt/jwt/v5 v5.0.0
github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a
github.com/golang-jwt/jwt/v5 v5.2.0
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
github.com/google/uuid v1.3.0
github.com/google/uuid v1.5.0
github.com/hashicorp/go-version v1.6.0
github.com/iancoleman/strcase v0.2.0
github.com/jinzhu/copier v0.3.5
github.com/iancoleman/strcase v0.3.0
github.com/jinzhu/copier v0.4.0
github.com/jszwedko/go-datemath v0.1.1-0.20230526204004-640a500621d6
github.com/labstack/echo-jwt/v4 v4.2.0
github.com/labstack/echo/v4 v4.10.2
github.com/labstack/gommon v0.4.0
github.com/labstack/echo/v4 v4.11.3
github.com/labstack/gommon v0.4.1
github.com/lib/pq v1.10.9
github.com/magefile/mage v1.15.0
github.com/mattn/go-sqlite3 v1.14.17
github.com/mattn/go-sqlite3 v1.14.19
github.com/olekukonko/tablewriter v0.0.5
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
github.com/pquerna/otp v1.4.0
github.com/prometheus/client_golang v1.16.0
github.com/redis/go-redis/v9 v9.0.5
github.com/prometheus/client_golang v1.17.0
github.com/redis/go-redis/v9 v9.3.1
github.com/robfig/cron/v3 v3.0.1
github.com/samedi/caldav-go v3.0.0+incompatible
github.com/spf13/afero v1.9.5
github.com/spf13/cobra v1.7.0
github.com/spf13/viper v1.16.0
github.com/spf13/afero v1.11.0
github.com/spf13/cobra v1.8.0
github.com/spf13/viper v1.18.2
github.com/stretchr/testify v1.8.4
github.com/swaggo/swag v1.8.12
github.com/swaggo/swag v1.16.2
github.com/tkuchiki/go-timezone v0.2.2
github.com/typesense/typesense-go v0.9.0
github.com/ulule/limiter/v3 v3.11.2
github.com/wneessen/go-mail v0.3.9
github.com/yuin/goldmark v1.5.4
golang.org/x/crypto v0.11.0
golang.org/x/image v0.9.0
golang.org/x/oauth2 v0.10.0
golang.org/x/sync v0.3.0
golang.org/x/sys v0.10.0
golang.org/x/term v0.10.0
github.com/wneessen/go-mail v0.4.0
github.com/yuin/goldmark v1.6.0
golang.org/x/crypto v0.17.0
golang.org/x/image v0.14.0
golang.org/x/oauth2 v0.15.0
golang.org/x/sync v0.5.0
golang.org/x/sys v0.15.0
golang.org/x/term v0.15.0
golang.org/x/text v0.14.0
gopkg.in/d4l3k/messagediff.v1 v1.2.1
gopkg.in/yaml.v3 v3.0.1
src.techknowlogick.com/xgo v1.7.1-0.20230606181629-494bc06f804a
src.techknowlogick.com/xormigrate v1.5.0
xorm.io/builder v0.3.12
xorm.io/xorm v1.3.2
src.techknowlogick.com/xgo v1.7.1-0.20231205202227-c7ed78300ce9
src.techknowlogick.com/xormigrate v1.7.1
xorm.io/builder v0.3.13
xorm.io/xorm v1.3.4
)
require (
@@ -85,25 +87,34 @@ require (
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/andybalholm/brotli v1.0.5 // indirect
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
github.com/beevik/etree v1.1.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
github.com/bytedance/sonic v1.10.0 // indirect
github.com/cenkalti/backoff/v3 v3.2.2 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
github.com/chenzhuoyu/iasm v0.9.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/deepmap/oapi-codegen v1.13.4 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/ghodss/yaml v1.0.0 // indirect
github.com/go-chi/chi/v5 v5.0.8 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/gin-gonic/gin v1.9.1 // indirect
github.com/go-chi/chi/v5 v5.0.10 // indirect
github.com/go-faster/city v1.0.1 // indirect
github.com/go-faster/errors v0.6.1 // indirect
github.com/go-jose/go-jose/v3 v3.0.0 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.19.6 // indirect
github.com/go-jose/go-jose/v3 v3.0.1 // indirect
github.com/go-openapi/jsonpointer v0.20.1 // indirect
github.com/go-openapi/jsonreference v0.20.3 // indirect
github.com/go-openapi/spec v0.20.4 // indirect
github.com/go-openapi/swag v0.19.15 // indirect
github.com/goccy/go-json v0.10.0 // indirect
github.com/go-openapi/swag v0.22.5 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.15.1 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.4 // indirect
@@ -113,14 +124,16 @@ require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.16.5 // indirect
github.com/klauspost/compress v1.17.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
github.com/laurent22/ical-go v0.1.1-0.20181107184520-7e5d6ade8eef // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/lithammer/shortuuid/v3 v3.0.7 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.18 // indirect
github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
@@ -129,38 +142,49 @@ require (
github.com/onsi/ginkgo v1.16.4 // indirect
github.com/onsi/gomega v1.16.0 // indirect
github.com/paulmach/orb v0.9.0 // indirect
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
github.com/pierrec/lz4/v4 v4.1.17 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.42.0 // indirect
github.com/prometheus/procfs v0.10.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect
github.com/prometheus/common v0.44.0 // indirect
github.com/prometheus/procfs v0.11.1 // indirect
github.com/rivo/uniseg v0.4.4 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/segmentio/asm v1.2.0 // indirect
github.com/shopspring/decimal v1.3.1 // indirect
github.com/spf13/cast v1.5.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/sony/gobreaker v0.5.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.4.2 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/syndtr/goleveldb v1.0.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
github.com/urfave/cli/v2 v2.3.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/yosssi/gohtml v0.0.0-20201013000340-ee4748c638f4 // indirect
go.opentelemetry.io/otel v1.15.0 // indirect
go.opentelemetry.io/otel/trace v1.15.0 // indirect
golang.org/x/mod v0.9.0 // indirect
golang.org/x/net v0.12.0 // indirect
golang.org/x/text v0.11.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.7.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/arch v0.4.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/mod v0.12.0 // indirect
golang.org/x/net v0.19.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.13.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
)
replace github.com/samedi/caldav-go => github.com/kolaente/caldav-go v3.0.1-0.20190610114120-2a4eb8b5dcc9+incompatible // Branch: feature/dynamic-supported-components, PR: https://github.com/samedi/caldav-go/pull/6 and https://github.com/samedi/caldav-go/pull/7
go 1.20
go 1.21
toolchain go1.21.2
+296 -476
View File
File diff suppressed because it is too large Load Diff
+3 -16
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@@ -177,9 +177,6 @@ func init() {
// Some variables have external dependencies (like git) which may not always be available.
func initVars() {
Tags = os.Getenv("TAGS")
if !strings.Contains(Tags, "swagger") {
Tags += " swagger"
}
setVersion()
setBinLocation()
setPkgVersion()
@@ -349,10 +346,6 @@ const swaggerDocsFolderLocation = `./pkg/swagger/`
// Generates the swagger docs from the code annotations
func DoTheSwag() {
mg.Deps(initVars)
if _, err := os.Stat(swaggerDocsFolderLocation + "swagger.json"); err == nil {
fmt.Println("Swagger docs already generated, not generating. Remove the files in " + swaggerDocsFolderLocation + " and run this command again to regenerate them.")
return
}
checkAndInstallGoTool("swag", "github.com/swaggo/swag/cmd/swag")
runAndStreamOutput("swag", "init", "-g", "./pkg/routes/routes.go", "--parseDependency", "-d", RootPath, "-o", RootPath+"/pkg/swagger")
@@ -419,7 +412,7 @@ func checkGolangCiLintInstalled() {
mg.Deps(initVars)
if err := exec.Command("golangci-lint").Run(); err != nil && strings.Contains(err.Error(), "executable file not found") {
fmt.Println("Please manually install golangci-lint by running")
fmt.Println("curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.53.2")
fmt.Println("curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.55.2")
os.Exit(1)
}
}
@@ -460,16 +453,12 @@ func (Build) Clean() error {
if err := os.RemoveAll(BinLocation); err != nil && !os.IsNotExist(err) {
return err
}
if err := os.RemoveAll(swaggerDocsFolderLocation); err != nil && !os.IsNotExist(err) {
return err
}
return nil
}
// Builds a vikunja binary, ready to run
func (Build) Build() {
mg.Deps(initVars)
mg.Deps(DoTheSwag)
runAndStreamOutput("go", "build", Goflags[0], "-tags", Tags, "-ldflags", "-s -w "+Ldflags, "-o", Executable)
}
@@ -479,7 +468,6 @@ type Release mg.Namespace
func (Release) Release(ctx context.Context) error {
mg.Deps(initVars)
mg.Deps(Release.Dirs)
mg.Deps(DoTheSwag)
// Run compiling in parallel to speed it up
errs, _ := errgroup.WithContext(ctx)
@@ -521,7 +509,6 @@ func (Release) Dirs() error {
func runXgo(targets string) error {
mg.Deps(initVars)
mg.Deps(DoTheSwag)
checkAndInstallGoTool("xgo", "src.techknowlogick.com/xgo")
extraLdflags := `-linkmode external -extldflags "-static" `
@@ -770,7 +757,7 @@ func (Dev) MakeMigration() error {
date := time.Now().Format("20060102150405")
migration := `// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
+1 -1
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
+63 -21
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@@ -38,21 +38,21 @@ type Todo struct {
UID string
// Optional
Summary string
Description string
Completed time.Time
Organizer *user.User
Priority int64 // 0-9, 1 is highest
RelatedToUID string
Color string
Categories []string
Start time.Time
End time.Time
DueDate time.Time
Duration time.Duration
RepeatAfter int64
RepeatMode models.TaskRepeatMode
Alarms []Alarm
Summary string
Description string
Completed time.Time
Organizer *user.User
Priority int64 // 0-9, 1 is highest
Relations []Relation
Color string
Categories []string
Start time.Time
End time.Time
DueDate time.Time
Duration time.Duration
RepeatAfter int64
RepeatMode models.TaskRepeatMode
Alarms []Alarm
Created time.Time
Updated time.Time // last-mod
@@ -66,6 +66,11 @@ type Alarm struct {
Description string
}
type Relation struct {
Type models.RelationKind
UID string
}
// Config is the caldav calendar config
type Config struct {
Name string
@@ -147,11 +152,6 @@ STATUS:COMPLETED`
ORGANIZER;CN=:` + t.Organizer.Username
}
if t.RelatedToUID != "" {
caldavtodos += `
RELATED-TO:` + t.RelatedToUID
}
if t.DueDate.Unix() > 0 {
caldavtodos += `
DUE:` + makeCalDavTimeFromTimeStamp(t.DueDate)
@@ -185,6 +185,7 @@ CATEGORIES:` + strings.Join(t.Categories, ",")
caldavtodos += `
LAST-MODIFIED:` + makeCalDavTimeFromTimeStamp(t.Updated)
caldavtodos += ParseAlarms(t.Alarms, t.Summary)
caldavtodos += ParseRelations(t.Relations)
caldavtodos += `
END:VTODO`
}
@@ -222,6 +223,47 @@ END:VALARM`
return caldavalarms
}
func ParseRelations(relations []Relation) (caldavrelatedtos string) {
for _, r := range relations {
switch r.Type {
case models.RelationKindParenttask:
caldavrelatedtos += `
RELATED-TO;RELTYPE=PARENT:`
case models.RelationKindSubtask:
caldavrelatedtos += `
RELATED-TO;RELTYPE=CHILD:`
case models.RelationKindUnknown:
continue
case models.RelationKindRelated:
continue
case models.RelationKindDuplicateOf:
continue
case models.RelationKindDuplicates:
continue
case models.RelationKindBlocking:
continue
case models.RelationKindBlocked:
continue
case models.RelationKindPreceeds:
continue
case models.RelationKindFollows:
continue
case models.RelationKindCopiedFrom:
continue
case models.RelationKindCopiedTo:
continue
default:
caldavrelatedtos += `
RELATED-TO:`
}
caldavrelatedtos += r.UID
}
return caldavrelatedtos
}
func makeCalDavTimeFromTimeStamp(ts time.Time) (caldavtime string) {
return ts.In(time.UTC).Format(DateFormat) + "Z"
}
+44 -1
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@@ -326,6 +326,49 @@ ACTION:DISPLAY
DESCRIPTION:Todo #1
END:VALARM
END:VTODO
END:VCALENDAR`,
},
{
name: "with related-to",
args: args{
config: &Config{
Name: "test",
ProdID: "RandomProdID which is not random",
},
todos: []*Todo{
{
Summary: "Todo #1",
Description: "Lorem Ipsum",
UID: "randommduid",
Relations: []Relation{
{
Type: models.RelationKindParenttask,
UID: "parentuid",
},
{
Type: models.RelationKindSubtask,
UID: "subtaskuid",
},
},
Timestamp: time.Unix(1543626724, 0).In(config.GetTimeZone()),
},
},
},
wantCaldavtasks: `BEGIN:VCALENDAR
VERSION:2.0
METHOD:PUBLISH
X-PUBLISHED-TTL:PT4H
X-WR-CALNAME:test
PRODID:-//RandomProdID which is not random//EN
BEGIN:VTODO
UID:randommduid
DTSTAMP:20181201T011204Z
SUMMARY:Todo #1
DESCRIPTION:Lorem Ipsum
LAST-MODIFIED:00010101T000000Z
RELATED-TO;RELTYPE=PARENT:parentuid
RELATED-TO;RELTYPE=CHILD:subtaskuid
END:VTODO
END:VCALENDAR`,
},
}
+45 -1
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@@ -50,6 +50,16 @@ func GetCaldavTodosForTasks(project *models.ProjectWithTasksAndBuckets, projectT
})
}
var relations []Relation
for reltype, tasks := range t.RelatedTasks {
for _, r := range tasks {
relations = append(relations, Relation{
Type: reltype,
UID: r.UID,
})
}
}
caldavtodos = append(caldavtodos, &Todo{
Timestamp: t.Updated,
UID: t.UID,
@@ -68,6 +78,7 @@ func GetCaldavTodosForTasks(project *models.ProjectWithTasksAndBuckets, projectT
RepeatAfter: t.RepeatAfter,
RepeatMode: t.RepeatMode,
Alarms: alarms,
Relations: relations,
})
}
@@ -90,8 +101,12 @@ func ParseTaskFromVTODO(content string) (vTask *models.Task, err error) {
}
// We put the vTodo details in a map to be able to handle them more easily
task := make(map[string]ics.IANAProperty)
var relations []ics.IANAProperty
for _, c := range vTodo.UnknownPropertiesIANAProperties() {
task[c.IANAToken] = c
if strings.HasPrefix(c.IANAToken, "RELATED-TO") {
relations = append(relations, c)
}
}
// Parse the priority
@@ -134,6 +149,35 @@ func ParseTaskFromVTODO(content string) (vTask *models.Task, err error) {
DoneAt: caldavTimeToTimestamp(task["COMPLETED"]),
}
for _, c := range relations {
var relTypeStr string
if _, ok := c.ICalParameters["RELTYPE"]; ok {
if len(c.ICalParameters["RELTYPE"]) != 1 {
continue
}
relTypeStr = c.ICalParameters["RELTYPE"][0]
}
var relationKind models.RelationKind
switch relTypeStr {
case "PARENT":
relationKind = models.RelationKindParenttask
case "CHILD":
relationKind = models.RelationKindSubtask
default:
relationKind = models.RelationKindParenttask
}
if vTask.RelatedTasks == nil {
vTask.RelatedTasks = make(map[models.RelationKind][]*models.Task)
}
vTask.RelatedTasks[relationKind] = append(vTask.RelatedTasks[relationKind], &models.Task{
UID: c.Value,
})
}
if task["STATUS"].Value == "COMPLETED" {
vTask.Done = true
}
+183 -1
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@@ -219,6 +219,70 @@ END:VCALENDAR`,
Updated: time.Unix(1543626724, 0).In(config.GetTimeZone()),
},
},
{
name: "With parent",
args: args{content: `BEGIN:VCALENDAR
VERSION:2.0
METHOD:PUBLISH
X-PUBLISHED-TTL:PT4H
X-WR-CALNAME:test
PRODID:-//RandomProdID which is not random//EN
BEGIN:VTODO
UID:randomuid
DTSTAMP:20181201T011204
SUMMARY:SubTask #1
DESCRIPTION:Lorem Ipsum
LAST-MODIFIED:00010101T000000
RELATED-TO;RELTYPE=PARENT:randomuid_parent
END:VTODO
END:VCALENDAR`,
},
wantVTask: &models.Task{
Title: "SubTask #1",
UID: "randomuid",
Description: "Lorem Ipsum",
Updated: time.Unix(1543626724, 0).In(config.GetTimeZone()),
RelatedTasks: map[models.RelationKind][]*models.Task{
models.RelationKindParenttask: {
{
UID: "randomuid_parent",
},
},
},
},
},
{
name: "With subtask",
args: args{content: `BEGIN:VCALENDAR
VERSION:2.0
METHOD:PUBLISH
X-PUBLISHED-TTL:PT4H
X-WR-CALNAME:test
PRODID:-//RandomProdID which is not random//EN
BEGIN:VTODO
UID:randomuid
DTSTAMP:20181201T011204
SUMMARY:Parent
DESCRIPTION:Lorem Ipsum
LAST-MODIFIED:00010101T000000
RELATED-TO;RELTYPE=CHILD:randomuid_child
END:VTODO
END:VCALENDAR`,
},
wantVTask: &models.Task{
Title: "Parent",
UID: "randomuid",
Description: "Lorem Ipsum",
Updated: time.Unix(1543626724, 0).In(config.GetTimeZone()),
RelatedTasks: map[models.RelationKind][]*models.Task{
models.RelationKindSubtask: {
{
UID: "randomuid_child",
},
},
},
},
},
{
name: "example task from tasks.org app",
args: args{content: `BEGIN:VCALENDAR
@@ -392,6 +456,124 @@ ACTION:DISPLAY
DESCRIPTION:Task 1
END:VALARM
END:VTODO
END:VCALENDAR`,
},
{
name: "Format Task with Related Tasks as CalDAV",
args: args{
list: &models.ProjectWithTasksAndBuckets{
Project: models.Project{
Title: "List title",
},
},
tasks: []*models.TaskWithComments{
{
Task: models.Task{
Title: "Parent Task",
UID: "randomuid_parent",
Description: "A parent task",
Priority: 3,
Created: time.Unix(1543626721, 0).In(config.GetTimeZone()),
Updated: time.Unix(1543626725, 0).In(config.GetTimeZone()),
RelatedTasks: map[models.RelationKind][]*models.Task{
models.RelationKindSubtask: {
{
Title: "Subtask 1",
UID: "randomuid_child_1",
Description: "The first child task",
Created: time.Unix(1543626724, 0).In(config.GetTimeZone()),
Updated: time.Unix(1543626724, 0).In(config.GetTimeZone()),
},
{
Title: "Subtask 2",
UID: "randomuid_child_2",
Description: "The second child task",
Created: time.Unix(1543626724, 0).In(config.GetTimeZone()),
Updated: time.Unix(1543626724, 0).In(config.GetTimeZone()),
},
},
},
},
},
{
Task: models.Task{
Title: "Subtask 1",
UID: "randomuid_child_1",
Description: "The first child task",
Created: time.Unix(1543626724, 0).In(config.GetTimeZone()),
Updated: time.Unix(1543626724, 0).In(config.GetTimeZone()),
RelatedTasks: map[models.RelationKind][]*models.Task{
models.RelationKindParenttask: {
{
Title: "Parent task",
UID: "randomuid_parent",
Description: "A parent task",
Priority: 3,
Created: time.Unix(1543626721, 0).In(config.GetTimeZone()),
Updated: time.Unix(1543626725, 0).In(config.GetTimeZone()),
},
},
},
},
},
{
Task: models.Task{
Title: "Subtask 2",
UID: "randomuid_child_2",
Description: "The second child task",
Created: time.Unix(1543626724, 0).In(config.GetTimeZone()),
Updated: time.Unix(1543626724, 0).In(config.GetTimeZone()),
RelatedTasks: map[models.RelationKind][]*models.Task{
models.RelationKindParenttask: {
{
Title: "Parent task",
UID: "randomuid_parent",
Description: "A parent task",
Priority: 3,
Created: time.Unix(1543626721, 0).In(config.GetTimeZone()),
Updated: time.Unix(1543626725, 0).In(config.GetTimeZone()),
},
},
},
},
},
},
},
wantCaldav: `BEGIN:VCALENDAR
VERSION:2.0
METHOD:PUBLISH
X-PUBLISHED-TTL:PT4H
X-WR-CALNAME:List title
PRODID:-//Vikunja Todo App//EN
BEGIN:VTODO
UID:randomuid_parent
DTSTAMP:20181201T011205Z
SUMMARY:Parent Task
DESCRIPTION:A parent task
CREATED:20181201T011201Z
PRIORITY:3
LAST-MODIFIED:20181201T011205Z
RELATED-TO;RELTYPE=CHILD:randomuid_child_1
RELATED-TO;RELTYPE=CHILD:randomuid_child_2
END:VTODO
BEGIN:VTODO
UID:randomuid_child_1
DTSTAMP:20181201T011204Z
SUMMARY:Subtask 1
DESCRIPTION:The first child task
CREATED:20181201T011204Z
LAST-MODIFIED:20181201T011204Z
RELATED-TO;RELTYPE=PARENT:randomuid_parent
END:VTODO
BEGIN:VTODO
UID:randomuid_child_2
DTSTAMP:20181201T011204Z
SUMMARY:Subtask 2
DESCRIPTION:The second child task
CREATED:20181201T011204Z
LAST-MODIFIED:20181201T011204Z
RELATED-TO;RELTYPE=PARENT:randomuid_parent
END:VTODO
END:VCALENDAR`,
},
}
+1 -1
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
+1 -1
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
+1 -1
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
+2 -2
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@@ -33,7 +33,7 @@ var dumpCmd = &cobra.Command{
Use: "dump",
Short: "Dump all vikunja data into a zip file. Includes config, files and db.",
PreRun: func(cmd *cobra.Command, args []string) {
initialize.FullInit()
initialize.FullInitWithoutAsync()
},
Run: func(cmd *cobra.Command, args []string) {
filename := "vikunja-dump_" + time.Now().Format("2006-01-02_15-03-05") + ".zip"
+58
View File
@@ -0,0 +1,58 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public Licensee for more details.
//
// You should have received a copy of the GNU Affero General Public Licensee
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package cmd
import (
"code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/initialize"
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/models"
"github.com/spf13/cobra"
)
func init() {
rootCmd.AddCommand(indexCmd)
}
var indexCmd = &cobra.Command{
Use: "index",
Short: "Reindex all of Vikunja's data into Typesense. This will remove any existing index.",
PreRun: func(cmd *cobra.Command, args []string) {
initialize.FullInitWithoutAsync()
},
Run: func(cmd *cobra.Command, args []string) {
if !config.TypesenseEnabled.GetBool() {
log.Error("Typesense not enabled")
return
}
log.Infof("Indexing… This may take a while.")
err := models.CreateTypesenseCollections()
if err != nil {
log.Criticalf("Could not create Typesense collections: %s", err.Error())
return
}
err = models.ReindexAllTasks()
if err != nil {
log.Criticalf("Could not reindex all tasks into Typesense: %s", err.Error())
return
}
log.Infof("Done!")
},
}
+1 -1
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
+2 -2
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@@ -32,7 +32,7 @@ var restoreCmd = &cobra.Command{
Short: "Restores all vikunja data from a vikunja dump.",
Args: cobra.ExactArgs(1),
PreRun: func(cmd *cobra.Command, args []string) {
initialize.FullInit()
initialize.FullInitWithoutAsync()
},
Run: func(cmd *cobra.Command, args []string) {
if err := dump.Restore(args[0]); err != nil {
+1 -1
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
+12 -4
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@@ -46,6 +46,7 @@ var (
userFlagEnableUser bool
userFlagDisableUser bool
userFlagDeleteNow bool
userFlagDeleteConfirm bool
)
func init() {
@@ -73,6 +74,9 @@ func init() {
// User deletion flags
userDeleteCmd.Flags().BoolVarP(&userFlagDeleteNow, "now", "n", false, "If provided, deletes the user immediately instead of sending them an email first.")
// Bypass confirm prompt
userDeleteCmd.Flags().BoolVarP(&userFlagDeleteConfirm, "confirm", "c", false, "Bypasses any prompts confirming the deletion request, use with caution!")
userCmd.AddCommand(userListCmd, userCreateCmd, userUpdateCmd, userResetPasswordCmd, userChangeEnabledCmd, userDeleteCmd)
rootCmd.AddCommand(userCmd)
}
@@ -100,12 +104,16 @@ func getPasswordFromFlagOrInput() (pw string) {
}
func getUserFromArg(s *xorm.Session, arg string) *user.User {
filter := user.User{}
id, err := strconv.ParseInt(arg, 10, 64)
if err != nil {
log.Fatalf("Invalid user id: %s", err)
log.Infof("Invalid user ID [%s], assuming username instead", arg)
filter.Username = arg
} else {
filter.ID = id
}
u, err := user.GetUserWithEmail(s, &user.User{ID: id})
u, err := user.GetUserWithEmail(s, &filter)
if err != nil {
log.Fatalf("Could not get user: %s", err)
}
@@ -322,7 +330,7 @@ var userDeleteCmd = &cobra.Command{
initialize.FullInit()
},
Run: func(cmd *cobra.Command, args []string) {
if userFlagDeleteNow {
if userFlagDeleteNow && !userFlagDeleteConfirm {
fmt.Println("You requested to delete the user immediately. Are you sure?")
fmt.Println(`To confirm, please type "yes, I confirm" in all uppercase:`)
+1 -1
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
+1 -1
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
+39 -13
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@@ -19,7 +19,6 @@ package config
import (
"crypto/rand"
"fmt"
"log"
"os"
"os/exec"
"path"
@@ -29,6 +28,7 @@ import (
"time"
_ "time/tzdata" // Imports time zone data instead of relying on the os
"code.vikunja.io/api/pkg/log"
"github.com/spf13/viper"
)
@@ -49,6 +49,7 @@ const (
ServiceRootpath Key = `service.rootpath`
ServiceStaticpath Key = `service.staticpath`
ServiceMaxItemsPerPage Key = `service.maxitemsperpage`
ServiceDemoMode Key = `service.demomode`
// Deprecated: Use metrics.enabled
ServiceEnableMetrics Key = `service.enablemetrics`
ServiceMotd Key = `service.motd`
@@ -87,6 +88,10 @@ const (
DatabaseSslRootCert Key = `database.sslrootcert`
DatabaseTLS Key = `database.tls`
TypesenseEnabled Key = `typesense.enabled`
TypesenseURL Key = `typesense.url`
TypesenseAPIKey Key = `typesense.apikey`
MailerEnabled Key = `mailer.enabled`
MailerHost Key = `mailer.host`
MailerPort Key = `mailer.port`
@@ -117,11 +122,12 @@ const (
LogMail Key = `log.mail`
LogMailLevel Key = `log.maillevel`
RateLimitEnabled Key = `ratelimit.enabled`
RateLimitKind Key = `ratelimit.kind`
RateLimitPeriod Key = `ratelimit.period`
RateLimitLimit Key = `ratelimit.limit`
RateLimitStore Key = `ratelimit.store`
RateLimitEnabled Key = `ratelimit.enabled`
RateLimitKind Key = `ratelimit.kind`
RateLimitPeriod Key = `ratelimit.period`
RateLimitLimit Key = `ratelimit.limit`
RateLimitStore Key = `ratelimit.store`
RateLimitNoAuthRoutesLimit Key = `ratelimit.noauthlimit`
FilesBasePath Key = `files.basepath`
FilesMaxSize Key = `files.maxsize`
@@ -167,6 +173,11 @@ const (
DefaultSettingsLanguage Key = `defaultsettings.language`
DefaultSettingsTimezone Key = `defaultsettings.timezone`
DefaultSettingsOverdueTaskRemindersTime Key = `defaultsettings.overdue_tasks_reminders_time`
WebhooksEnabled Key = `webhooks.enabled`
WebhooksTimeoutSeconds Key = `webhooks.timeoutseconds`
WebhooksProxyURL Key = `webhooks.proxyurl`
WebhooksProxyPassword Key = `webhooks.proxypassword`
)
// GetString returns a string config value
@@ -296,6 +307,7 @@ func InitDefaultConfig() {
ServiceEnableEmailReminders.setDefault(true)
ServiceEnableUserDeletion.setDefault(true)
ServiceMaxAvatarSize.setDefault(1024)
ServiceDemoMode.setDefault(false)
// Auth
AuthLocalEnabled.setDefault(true)
@@ -317,6 +329,9 @@ func InitDefaultConfig() {
DatabaseSslRootCert.setDefault("")
DatabaseTLS.setDefault("false")
// Typesense
TypesenseEnabled.setDefault(false)
// Mailer
MailerEnabled.setDefault(false)
MailerHost.setDefault("")
@@ -353,6 +368,7 @@ func InitDefaultConfig() {
RateLimitLimit.setDefault(100)
RateLimitPeriod.setDefault(60)
RateLimitStore.setDefault("memory")
RateLimitNoAuthRoutesLimit.setDefault(10)
// Files
FilesBasePath.setDefault("files")
FilesMaxSize.setDefault("20MB")
@@ -378,6 +394,9 @@ func InitDefaultConfig() {
DefaultSettingsAvatarProvider.setDefault("initials")
DefaultSettingsOverdueTaskRemindersEnabled.setDefault(true)
DefaultSettingsOverdueTaskRemindersTime.setDefault("9:00")
// Webhook
WebhooksEnabled.setDefault(true)
WebhooksTimeoutSeconds.setDefault(30)
}
// InitConfig initializes the config, sets defaults etc.
@@ -391,13 +410,17 @@ func InitConfig() {
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
viper.AutomaticEnv()
// Just load environment variables
_ = viper.ReadInConfig()
log.ConfigLogger(LogEnabled.GetBool(), LogStandard.GetString(), LogPath.GetString(), LogLevel.GetString())
// Load the config file
viper.AddConfigPath(ServiceRootpath.GetString())
viper.AddConfigPath("/etc/vikunja/")
homeDir, err := os.UserHomeDir()
if err != nil {
log.Printf("No home directory found, not using config from ~/.config/vikunja/. Error was: %s\n", err.Error())
log.Debugf("No home directory found, not using config from ~/.config/vikunja/. Error was: %s\n", err.Error())
} else {
viper.AddConfigPath(path.Join(homeDir, ".config", "vikunja"))
}
@@ -406,15 +429,18 @@ func InitConfig() {
viper.SetConfigName("config")
err = viper.ReadInConfig()
if viper.ConfigFileUsed() != "" {
log.Printf("Using config file: %s", viper.ConfigFileUsed())
log.Infof("Using config file: %s", viper.ConfigFileUsed())
if err != nil {
log.Println(err.Error())
log.Println("Using default config.")
log.Warning(err.Error())
log.Warning("Using default config.")
} else {
log.ConfigLogger(LogEnabled.GetBool(), LogStandard.GetString(), LogPath.GetString(), LogLevel.GetString())
}
} else {
log.Println("No config file found, using default or config from environment variables.")
log.Info("No config file found, using default or config from environment variables.")
}
if RateLimitStore.GetString() == "keyvalue" {
@@ -446,7 +472,7 @@ func InitConfig() {
}
if ServiceEnableMetrics.GetBool() {
log.Println("WARNING: service.enablemetrics is deprecated and will be removed in a future release. Please use metrics.enable.")
log.Warning("service.enablemetrics is deprecated and will be removed in a future release. Please use metrics.enable.")
MetricsEnabled.Set(true)
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
+2 -2
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@@ -79,7 +79,7 @@ func CreateDBEngine() (engine *xorm.Engine, err error) {
}
engine.SetTZDatabase(loc)
engine.SetMapper(names.GonicMapper{})
logger := log.NewXormLogger("")
logger := log.NewXormLogger(config.LogEnabled.GetBool(), config.LogDatabase.GetString(), config.LogDatabaseLevel.GetString())
engine.SetLogger(logger)
x = engine
+1 -1
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
+30
View File
@@ -0,0 +1,30 @@
- id: 1
title: 'test token 1'
token_salt: iC1Qbpf7H1
token_hash: a1813a558185d99f5197d2d549e4dd91292376aa00210229d70f77b57e165f6613fd12c1f790aa6493548cb9bceff33b45b4
token_last_eight: 75f29d2e
permissions: '{"tasks":["read_all","update"]}'
expires_at: 2099-01-01 00:00:00
owner_id: 1
created: 2023-09-01 07:00:00
# token in plaintext is tk_2eef46f40ebab3304919ab2e7e39993f75f29d2e
- id: 2
title: 'test token 2'
token_salt: EtwMsqDfOA
token_hash: 5c4d80c58947f21295064d473937709f1159ab09085eb59e38783da6032181069ec2e1d236486533b66999f9f4ac375b45f5
token_last_eight: 235008c8
permissions: '{"tasks":["read_all","update"]}'
expires_at: 2023-01-01 00:00:00
owner_id: 1
created: 2023-09-01 07:00:00
# token in plaintext is tk_a5e6f92ddbad68f49ee2c63e52174db0235008c8
- id: 3
title: 'test token 3'
token_salt: AHeetyp1aB
token_hash: da4b9c3aa72633274c37ab3419fbfbe4c5b79310b76027ac36f85e4c5ad0c2342a1d9e1c9b72ca07ec0a66ad2ee3505539af
token_last_eight: 0b8dcb7c
permissions: '{"tasks":["read_all","update"]}'
expires_at: 2099-01-01 00:00:00
owner_id: 2
created: 2023-09-01 07:00:00
# token in plaintext is tk_5e29ae2ae079781ff73b0a3e0fe4d75a0b8dcb7c
+8 -2
View File
@@ -18,7 +18,6 @@
title: testbucket3
project_id: 1
created_by_id: 1
is_done_bucket: 1
position: 3
created: 2020-04-18 21:13:52
updated: 2020-04-18 21:13:52
@@ -26,7 +25,6 @@
title: testbucket4 - other project
project_id: 2
created_by_id: 1
is_done_bucket: 1
created: 2020-04-18 21:13:52
updated: 2020-04-18 21:13:52
# The following are not or only partly owned by user 1
@@ -40,6 +38,7 @@
title: testbucket6
project_id: 6
created_by_id: 1
position: 1
created: 2020-04-18 21:13:52
updated: 2020-04-18 21:13:52
- id: 7
@@ -137,6 +136,7 @@
title: testbucket22
project_id: 6
created_by_id: 1
position: 2
created: 2020-04-18 21:13:52
updated: 2020-04-18 21:13:52
- id: 23
@@ -236,3 +236,9 @@
created_by_id: 15
created: 2020-04-18 21:13:52
updated: 2020-04-18 21:13:52
- id: 39
title: testbucket38
project_id: 38
created_by_id: 15
created: 2020-04-18 21:13:52
updated: 2020-04-18 21:13:52
+21
View File
@@ -5,6 +5,7 @@
identifier: test1
owner_id: 1
position: 3
done_bucket_id: 3
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
-
@@ -14,6 +15,7 @@
identifier: test2
owner_id: 3
position: 2
done_bucket_id: 4
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
-
@@ -50,6 +52,7 @@
identifier: test6
owner_id: 6
position: 6
default_bucket_id: 22
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
-
@@ -315,3 +318,21 @@
position: 1
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
-
id: 37
title: Project 37
description: Lorem Ipsum
identifier: test37
owner_id: 16
position: 1
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
-
id: 38
title: Project 38 for Caldav tests
description: Lorem Ipsum
identifier: test38
owner_id: 15
position: 2
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
+36
View File
@@ -34,3 +34,39 @@
relation_kind: 'related'
created_by_id: 1
created: 2018-12-01 15:13:12
- id: 7
task_id: 41
other_task_id: 43
relation_kind: 'subtask'
created_by_id: 15
created: 2018-12-01 15:13:12
- id: 8
task_id: 43
other_task_id: 41
relation_kind: 'parenttask'
created_by_id: 15
created: 2018-12-01 15:13:12
- id: 9
task_id: 41
other_task_id: 44
relation_kind: 'subtask'
created_by_id: 15
created: 2018-12-01 15:13:12
- id: 10
task_id: 44
other_task_id: 41
relation_kind: 'parenttask'
created_by_id: 15
created: 2018-12-01 15:13:12
- id: 11
task_id: 45
other_task_id: 46
relation_kind: 'subtask'
created_by_id: 15
created: 2018-12-01 15:13:12
- id: 12
task_id: 46
other_task_id: 45
relation_kind: 'parenttask'
created_by_id: 15
created: 2018-12-01 15:13:12
+85 -1
View File
@@ -374,5 +374,89 @@
due_date: 2023-03-01 15:00:00
created: 2018-12-01 01:12:04
updated: 2018-12-01 01:12:04
bucket_id: 1
bucket_id: 38
position: 39
- id: 41
uid: 'uid-caldav-test-parent-task'
title: 'Parent task for Caldav Test'
description: 'Description Caldav Test'
priority: 3
done: false
created_by_id: 15
project_id: 36
index: 40
due_date: 2023-03-01 15:00:00
created: 2018-12-01 01:12:04
updated: 2018-12-01 01:12:04
bucket_id: 38
position: 40
- id: 42
uid: 'uid-caldav-test-parent-task-2'
title: 'Parent task for Caldav Test 2'
description: 'Description Caldav Test 2'
priority: 3
done: false
created_by_id: 15
project_id: 36
index: 41
due_date: 2023-03-01 15:00:00
created: 2018-12-01 01:12:04
updated: 2018-12-01 01:12:04
bucket_id: 38
position: 41
- id: 43
uid: 'uid-caldav-test-child-task'
title: 'Child task for Caldav Test'
description: 'Description Caldav Test'
priority: 3
done: false
created_by_id: 15
project_id: 36
index: 42
due_date: 2023-03-01 15:00:00
created: 2018-12-01 01:12:04
updated: 2018-12-01 01:12:04
bucket_id: 38
position: 42
- id: 44
uid: 'uid-caldav-test-child-task-2'
title: 'Child task for Caldav Test '
description: 'Description Caldav Test'
priority: 3
done: false
created_by_id: 15
project_id: 38
index: 43
due_date: 2023-03-01 15:00:00
created: 2018-12-01 01:12:04
updated: 2018-12-01 01:12:04
bucket_id: 38
position: 43
- id: 45
uid: 'uid-caldav-test-parent-task-another-list'
title: 'Parent task for Caldav Test'
description: 'Description Caldav Test'
priority: 3
done: false
created_by_id: 15
project_id: 36
index: 44
due_date: 2023-03-01 15:00:00
created: 2018-12-01 01:12:04
updated: 2018-12-01 01:12:04
bucket_id: 38
position: 44
- id: 46
uid: 'uid-caldav-test-child-task-another-list'
title: 'Child task for Caldav Test '
description: 'Description Caldav Test'
priority: 3
done: false
created_by_id: 15
project_id: 38
index: 45
due_date: 2023-03-01 15:00:00
created: 2018-12-01 01:12:04
updated: 2018-12-01 01:12:04
bucket_id: 38
position: 45
+8
View File
@@ -118,3 +118,11 @@
issuer: local
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
- id: 16
username: 'user16'
password: '$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.' # 1234
email: 'user16@example.com'
issuer: local
default_project_id: 37
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
+12
View File
@@ -100,3 +100,15 @@
right: 0
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
- id: 18
user_id: 15
project_id: 36
right: 0
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
- id: 19
user_id: 15
project_id: 38
right: 0
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
+1 -1
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
+8 -7
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@@ -26,6 +26,7 @@ import (
"code.vikunja.io/api/pkg/log"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"xorm.io/builder"
"xorm.io/xorm"
"xorm.io/xorm/names"
@@ -52,7 +53,7 @@ func CreateTestEngine() (engine *xorm.Engine, err error) {
}
engine.SetMapper(names.GonicMapper{})
logger := log.NewXormLogger("DEBUG")
logger := log.NewXormLogger(config.LogEnabled.GetBool(), config.LogDatabase.GetString(), "DEBUG")
logger.ShowSQL(os.Getenv("UNIT_TESTS_VERBOSE") == "1")
engine.SetLogger(logger)
engine.SetTZLocation(config.GetTimeZone())
@@ -93,14 +94,14 @@ func AssertExists(t *testing.T, table string, values map[string]interface{}, cus
} else {
exists, err = x.Table(table).Where(values).Get(&v)
}
assert.NoError(t, err, fmt.Sprintf("Failed to assert entries exist in db, error was: %s", err))
require.NoError(t, err, fmt.Sprintf("Failed to assert entries exist in db, error was: %s", err))
if !exists {
all := []map[string]interface{}{}
err = x.Table(table).Find(&all)
assert.NoError(t, err, fmt.Sprintf("Failed to assert entries exist in db, error was: %s", err))
require.NoError(t, err, fmt.Sprintf("Failed to assert entries exist in db, error was: %s", err))
pretty, err := json.MarshalIndent(all, "", " ")
assert.NoError(t, err, fmt.Sprintf("Failed to assert entries exist in db, error was: %s", err))
require.NoError(t, err, fmt.Sprintf("Failed to assert entries exist in db, error was: %s", err))
t.Errorf(fmt.Sprintf("Entries %v do not exist in table %s\n\nFound entries instead: %v", values, table, string(pretty)))
}
@@ -110,13 +111,13 @@ func AssertExists(t *testing.T, table string, values map[string]interface{}, cus
func AssertMissing(t *testing.T, table string, values map[string]interface{}) {
v := make(map[string]interface{})
exists, err := x.Table(table).Where(values).Exist(&v)
assert.NoError(t, err, fmt.Sprintf("Failed to assert entries don't exist in db, error was: %s", err))
require.NoError(t, err, fmt.Sprintf("Failed to assert entries don't exist in db, error was: %s", err))
assert.False(t, exists, fmt.Sprintf("Entries %v exist in table %s", values, table))
}
// AssertCount checks if a number of entries exists in the database
func AssertCount(t *testing.T, table string, where builder.Cond, count int64) {
dbCount, err := x.Table(table).Where(where).Count()
assert.NoError(t, err, fmt.Sprintf("Failed to assert count in db, error was: %s", err))
require.NoError(t, err, fmt.Sprintf("Failed to assert count in db, error was: %s", err))
assert.Equal(t, count, dbCount, fmt.Sprintf("Found %d entries instead of expected %d in table %s", dbCount, count, table))
}
+4 -3
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@@ -22,8 +22,9 @@ import (
"testing"
"code.vikunja.io/api/pkg/config"
"github.com/go-testfixtures/testfixtures/v3"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"xorm.io/xorm/schemas"
)
@@ -108,5 +109,5 @@ func LoadFixtures() error {
// LoadAndAssertFixtures loads all fixtures defined before and asserts they are correctly loaded
func LoadAndAssertFixtures(t *testing.T) {
err := LoadFixtures()
assert.NoError(t, err)
require.NoError(t, err)
}
+3 -2
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@@ -21,6 +21,7 @@ import (
"encoding/json"
"time"
"code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/log"
vmetrics "code.vikunja.io/api/pkg/metrics"
"github.com/ThreeDotsLabs/watermill"
@@ -39,7 +40,7 @@ type Event interface {
// InitEvents sets up everything needed to work with events
func InitEvents() (err error) {
logger := log.NewWatermillLogger()
logger := log.NewWatermillLogger(config.LogEnabled.GetBool(), config.LogEvents.GetString(), config.LogEventsLevel.GetString())
router, err := message.NewRouter(
message.RouterConfig{},
+1 -1
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
+4 -4
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@@ -22,8 +22,8 @@ import (
"github.com/ThreeDotsLabs/watermill"
"github.com/ThreeDotsLabs/watermill/message"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var (
@@ -55,9 +55,9 @@ func AssertDispatched(t *testing.T, event Event) {
// TestListener takes an event and a listener and calls the listener's Handle method.
func TestListener(t *testing.T, event Event, listener Listener) {
content, err := json.Marshal(event)
assert.NoError(t, err)
require.NoError(t, err)
msg := message.NewMessage(watermill.NewUUID(), content)
err = listener.Handle(msg)
assert.NoError(t, err)
require.NoError(t, err)
}
+1 -1
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
+1 -1
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
+1 -1
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
+7 -3
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@@ -23,8 +23,10 @@ import (
"code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/modules/keyvalue"
"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// This file handles storing and retrieving a file for different backends
@@ -55,7 +57,7 @@ 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)
require.NoError(t, err)
}
// InitTests handles the actual bootstrapping of the test env
@@ -77,6 +79,8 @@ func InitTests() {
}
InitTestFileHandler()
keyvalue.InitStorage()
}
// FileStat stats a file. This is an exported function to be able to test this from outide of the package
+13 -6
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@@ -23,14 +23,16 @@ import (
"strconv"
"time"
"xorm.io/xorm"
"code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/metrics"
"code.vikunja.io/api/pkg/modules/keyvalue"
"code.vikunja.io/web"
"github.com/c2h5oh/datasize"
"github.com/spf13/afero"
"xorm.io/xorm"
)
// File holds all information about a file
@@ -148,10 +150,15 @@ func (f *File) Delete() (err error) {
return err
}
return
return keyvalue.DecrBy(metrics.FilesCountKey, 1)
}
// Save saves a file to storage
func (f *File) Save(fcontent io.Reader) error {
return afs.WriteReader(f.getFileName(), fcontent)
func (f *File) Save(fcontent io.Reader) (err error) {
err = afs.WriteReader(f.getFileName(), fcontent)
if err != nil {
return
}
return keyvalue.IncrBy(metrics.FilesCountKey, 1)
}
+11 -10
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@@ -22,6 +22,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type testfile struct {
@@ -58,12 +59,12 @@ func TestCreate(t *testing.T) {
}
ta := &testauth{id: 1}
createdFile, err := Create(tf, "testfile", 100, ta)
assert.NoError(t, err)
require.NoError(t, err)
// Check the file was created correctly
file := &File{ID: createdFile.ID}
err = file.LoadFileMetaByID()
assert.NoError(t, err)
require.NoError(t, err)
assert.Equal(t, int64(1), file.CreatedByID)
assert.Equal(t, "testfile", file.Name)
assert.Equal(t, uint64(100), file.Size)
@@ -76,7 +77,7 @@ func TestCreate(t *testing.T) {
}
ta := &testauth{id: 1}
_, err := Create(tf, "testfile", 99999999999, ta)
assert.Error(t, err)
require.Error(t, err)
assert.True(t, IsErrFileIsTooLarge(err))
})
}
@@ -86,13 +87,13 @@ func TestFile_Delete(t *testing.T) {
initFixtures(t)
f := &File{ID: 1}
err := f.Delete()
assert.NoError(t, err)
require.NoError(t, err)
})
t.Run("Nonexisting", func(t *testing.T) {
initFixtures(t)
f := &File{ID: 9999}
err := f.Delete()
assert.Error(t, err)
require.Error(t, err)
assert.True(t, IsErrFileDoesNotExist(err))
})
}
@@ -102,13 +103,13 @@ func TestFile_LoadFileByID(t *testing.T) {
initFixtures(t)
f := &File{ID: 1}
err := f.LoadFileByID()
assert.NoError(t, err)
require.NoError(t, err)
})
t.Run("Nonexisting", func(t *testing.T) {
initFixtures(t)
f := &File{ID: 9999}
err := f.LoadFileByID()
assert.Error(t, err)
require.Error(t, err)
assert.True(t, os.IsNotExist(err))
})
}
@@ -118,14 +119,14 @@ func TestFile_LoadFileMetaByID(t *testing.T) {
initFixtures(t)
f := &File{ID: 1}
err := f.LoadFileMetaByID()
assert.NoError(t, err)
require.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)
require.Error(t, err)
assert.True(t, IsErrFileDoesNotExist(err))
})
}
+1 -1
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
+1 -1
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
+18 -7
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@@ -29,12 +29,16 @@ import (
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/modules/auth/openid"
"code.vikunja.io/api/pkg/modules/keyvalue"
migrationHandler "code.vikunja.io/api/pkg/modules/migration/handler"
"code.vikunja.io/api/pkg/red"
"code.vikunja.io/api/pkg/user"
)
// LightInit will only fullInit config, redis, logger but no db connection.
func LightInit() {
// Set logger
log.InitLogger()
// Init the config
config.InitConfig()
@@ -43,9 +47,6 @@ func LightInit() {
// Init keyvalue store
keyvalue.InitStorage()
// Set logger
log.InitLogger()
}
// InitEngines intializes all db connections
@@ -60,9 +61,8 @@ func InitEngines() {
}
}
// FullInit initializes all kinds of things in the right order
func FullInit() {
// FullInitWithoutAsync does a full init without any async handlers (cron or events)
func FullInitWithoutAsync() {
LightInit()
// Initialize the files handler
@@ -74,8 +74,17 @@ func FullInit() {
// Set Engine
InitEngines()
// Init Typesense
models.InitTypesense()
// Start the mail daemon
mail.StartMailDaemon()
}
// FullInit initializes all kinds of things in the right order
func FullInit() {
FullInitWithoutAsync()
// Start the cron
cron.Init()
@@ -86,11 +95,13 @@ func FullInit() {
models.RegisterUserDeletionCron()
models.RegisterOldExportCleanupCron()
openid.CleanupSavedOpenIDProviders()
models.RegisterPeriodicTypesenseResyncCron()
// Start processing events
go func() {
models.RegisterListeners()
user.RegisterListeners()
migrationHandler.RegisterListeners()
err := events.InitEvents()
if err != nil {
log.Fatal(err.Error())
+1 -1
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
+114
View File
@@ -0,0 +1,114 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public Licensee for more details.
//
// You should have received a copy of the GNU Affero General Public Licensee
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package integrations
import (
"net/http"
"net/http/httptest"
"testing"
"code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/modules/auth"
"code.vikunja.io/api/pkg/routes"
"code.vikunja.io/api/pkg/user"
"github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestAPIToken(t *testing.T) {
t.Run("valid token", func(t *testing.T) {
e, err := setupTestEnv()
require.NoError(t, err)
req := httptest.NewRequest(http.MethodGet, "/api/v1/tasks/all", nil)
res := httptest.NewRecorder()
c := e.NewContext(req, res)
h := routes.SetupTokenMiddleware()(func(c echo.Context) error {
u, err := auth.GetAuthFromClaims(c)
if err != nil {
return c.String(http.StatusInternalServerError, err.Error())
}
return c.JSON(http.StatusOK, u)
})
req.Header.Set(echo.HeaderAuthorization, "Bearer tk_2eef46f40ebab3304919ab2e7e39993f75f29d2e") // Token 1
require.NoError(t, h(c))
// check if the request handlers "see" the request as if it came directly from that user
assert.Contains(t, res.Body.String(), `"username":"user1"`)
})
t.Run("invalid token", func(t *testing.T) {
e, err := setupTestEnv()
require.NoError(t, err)
req := httptest.NewRequest(http.MethodGet, "/api/v1/tasks/all", nil)
res := httptest.NewRecorder()
c := e.NewContext(req, res)
h := routes.SetupTokenMiddleware()(func(c echo.Context) error {
return c.String(http.StatusOK, "test")
})
req.Header.Set(echo.HeaderAuthorization, "Bearer tk_loremipsumdolorsitamet")
require.Error(t, h(c))
})
t.Run("expired token", func(t *testing.T) {
e, err := setupTestEnv()
require.NoError(t, err)
req := httptest.NewRequest(http.MethodGet, "/api/v1/tasks/all", nil)
res := httptest.NewRecorder()
c := e.NewContext(req, res)
h := routes.SetupTokenMiddleware()(func(c echo.Context) error {
return c.String(http.StatusOK, "test")
})
req.Header.Set(echo.HeaderAuthorization, "Bearer tk_a5e6f92ddbad68f49ee2c63e52174db0235008c8") // Token 2
require.Error(t, h(c))
})
t.Run("valid token, invalid scope", func(t *testing.T) {
e, err := setupTestEnv()
require.NoError(t, err)
req := httptest.NewRequest(http.MethodGet, "/api/v1/projects", nil)
res := httptest.NewRecorder()
c := e.NewContext(req, res)
h := routes.SetupTokenMiddleware()(func(c echo.Context) error {
return c.String(http.StatusOK, "test")
})
req.Header.Set(echo.HeaderAuthorization, "Bearer tk_2eef46f40ebab3304919ab2e7e39993f75f29d2e")
require.Error(t, h(c))
})
t.Run("jwt", func(t *testing.T) {
e, err := setupTestEnv()
require.NoError(t, err)
req := httptest.NewRequest(http.MethodGet, "/api/v1/tasks/all", nil)
res := httptest.NewRecorder()
c := e.NewContext(req, res)
h := routes.SetupTokenMiddleware()(func(c echo.Context) error {
return c.String(http.StatusOK, "test")
})
s := db.NewSession()
defer s.Close()
u, err := user.GetUserByID(s, 1)
require.NoError(t, err)
jwt, err := auth.NewUserJWTAuthtoken(u, false)
require.NoError(t, err)
req.Header.Set(echo.HeaderAuthorization, "Bearer "+jwt)
require.NoError(t, h(c))
})
}
+19 -17
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@@ -21,7 +21,9 @@ import (
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/web/handler"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// This tests the following behaviour:
@@ -96,47 +98,47 @@ func TestArchived(t *testing.T) {
t.Run("task", func(t *testing.T) {
t.Run("edit task", func(t *testing.T) {
_, err := testTaskHandler.testUpdateWithUser(nil, map[string]string{"projecttask": taskID}, `{"title":"TestIpsum"}`)
assert.Error(t, err)
require.Error(t, err)
assertHandlerErrorCode(t, err, errCode)
})
t.Run("delete", func(t *testing.T) {
_, err := testTaskHandler.testDeleteWithUser(nil, map[string]string{"projecttask": taskID})
assert.Error(t, err)
require.Error(t, err)
assertHandlerErrorCode(t, err, errCode)
})
t.Run("add new labels", func(t *testing.T) {
_, err := testLabelHandler.testCreateWithUser(nil, map[string]string{"projecttask": taskID}, `{"label_id":1}`)
assert.Error(t, err)
require.Error(t, err)
assertHandlerErrorCode(t, err, errCode)
})
t.Run("remove lables", func(t *testing.T) {
_, err := testLabelHandler.testDeleteWithUser(nil, map[string]string{"projecttask": taskID, "label": "4"})
assert.Error(t, err)
require.Error(t, err)
assertHandlerErrorCode(t, err, errCode)
})
t.Run("add assignees", func(t *testing.T) {
_, err := testAssigneeHandler.testCreateWithUser(nil, map[string]string{"projecttask": taskID}, `{"user_id":3}`)
assert.Error(t, err)
require.Error(t, err)
assertHandlerErrorCode(t, err, errCode)
})
t.Run("remove assignees", func(t *testing.T) {
_, err := testAssigneeHandler.testDeleteWithUser(nil, map[string]string{"projecttask": taskID, "user": "2"})
assert.Error(t, err)
require.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)
require.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)
require.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)
require.Error(t, err)
assertHandlerErrorCode(t, err, errCode)
})
t.Run("remove comment", func(t *testing.T) {
@@ -145,7 +147,7 @@ func TestArchived(t *testing.T) {
commentID = "16"
}
_, err := testCommentHandler.testDeleteWithUser(nil, map[string]string{"task": taskID, "commentid": commentID})
assert.Error(t, err)
require.Error(t, err)
assertHandlerErrorCode(t, err, errCode)
})
})
@@ -155,17 +157,17 @@ func TestArchived(t *testing.T) {
t.Run("archived parent project", func(t *testing.T) {
t.Run("not editable", func(t *testing.T) {
_, err := testProjectHandler.testUpdateWithUser(nil, map[string]string{"project": "21"}, `{"title":"TestIpsum","is_archived":true}`)
assert.Error(t, err)
require.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeProjectIsArchived)
})
t.Run("no new tasks", func(t *testing.T) {
_, err := testTaskHandler.testCreateWithUser(nil, map[string]string{"project": "21"}, `{"title":"Lorem"}`)
assert.Error(t, err)
require.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeProjectIsArchived)
})
t.Run("not unarchivable", func(t *testing.T) {
_, err := testProjectHandler.testUpdateWithUser(nil, map[string]string{"project": "21"}, `{"title":"LoremIpsum","is_archived":false}`)
assert.Error(t, err)
require.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeProjectIsArchived)
})
@@ -175,17 +177,17 @@ func TestArchived(t *testing.T) {
t.Run("archived individually", func(t *testing.T) {
t.Run("not editable", func(t *testing.T) {
_, err := testProjectHandler.testUpdateWithUser(nil, map[string]string{"project": "22"}, `{"title":"TestIpsum","is_archived":true}`)
assert.Error(t, err)
require.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeProjectIsArchived)
})
t.Run("no new tasks", func(t *testing.T) {
_, err := testTaskHandler.testCreateWithUser(nil, map[string]string{"project": "22"}, `{"title":"Lorem"}`)
assert.Error(t, err)
require.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeProjectIsArchived)
})
t.Run("unarchivable", func(t *testing.T) {
rec, err := testProjectHandler.testUpdateWithUser(nil, map[string]string{"project": "22"}, `{"title":"LoremIpsum","is_archived":false}`)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"is_archived":false`)
})
+261 -18
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@@ -21,10 +21,25 @@ import (
"testing"
"code.vikunja.io/api/pkg/routes/caldav"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
const vtodo = `BEGIN:VCALENDAR
func TestCaldav(t *testing.T) {
t.Run("Delivers VTODO for project", func(t *testing.T) {
e, _ := setupTestEnv()
rec, err := newCaldavTestRequestWithUser(t, e, http.MethodGet, caldav.ProjectHandler, &testuser15, ``, nil, map[string]string{"project": "36"})
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), "BEGIN:VCALENDAR")
assert.Contains(t, rec.Body.String(), "PRODID:-//Vikunja Todo App//EN")
assert.Contains(t, rec.Body.String(), "X-WR-CALNAME:Project 36 for Caldav tests")
assert.Contains(t, rec.Body.String(), "BEGIN:VTODO")
assert.Contains(t, rec.Body.String(), "END:VTODO")
assert.Contains(t, rec.Body.String(), "END:VCALENDAR")
})
t.Run("Import VTODO", func(t *testing.T) {
const vtodo = `BEGIN:VCALENDAR
VERSION:2.0
METHOD:PUBLISH
X-PUBLISHED-TTL:PT4H
@@ -44,25 +59,15 @@ END:VALARM
END:VTODO
END:VCALENDAR`
func TestCaldav(t *testing.T) {
t.Run("Delivers VTODO for project", func(t *testing.T) {
rec, err := newCaldavTestRequestWithUser(t, http.MethodGet, caldav.ProjectHandler, &testuser15, ``, nil, map[string]string{"project": "36"})
assert.NoError(t, err)
assert.Contains(t, rec.Body.String(), "BEGIN:VCALENDAR")
assert.Contains(t, rec.Body.String(), "PRODID:-//Vikunja Todo App//EN")
assert.Contains(t, rec.Body.String(), "X-WR-CALNAME:Project 36 for Caldav tests")
assert.Contains(t, rec.Body.String(), "BEGIN:VTODO")
assert.Contains(t, rec.Body.String(), "END:VTODO")
assert.Contains(t, rec.Body.String(), "END:VCALENDAR")
})
t.Run("Import VTODO", func(t *testing.T) {
rec, err := newCaldavTestRequestWithUser(t, http.MethodPut, caldav.TaskHandler, &testuser15, vtodo, nil, map[string]string{"project": "36", "task": "uid"})
assert.NoError(t, err)
e, _ := setupTestEnv()
rec, err := newCaldavTestRequestWithUser(t, e, http.MethodPut, caldav.TaskHandler, &testuser15, vtodo, nil, map[string]string{"project": "36", "task": "uid"})
require.NoError(t, err)
assert.Equal(t, 201, rec.Result().StatusCode)
})
t.Run("Export VTODO", func(t *testing.T) {
rec, err := newCaldavTestRequestWithUser(t, http.MethodGet, caldav.TaskHandler, &testuser15, ``, nil, map[string]string{"project": "36", "task": "uid-caldav-test"})
assert.NoError(t, err)
e, _ := setupTestEnv()
rec, err := newCaldavTestRequestWithUser(t, e, http.MethodGet, caldav.TaskHandler, &testuser15, ``, nil, map[string]string{"project": "36", "task": "uid-caldav-test"})
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), "BEGIN:VCALENDAR")
assert.Contains(t, rec.Body.String(), "SUMMARY:Title Caldav Test")
assert.Contains(t, rec.Body.String(), "DESCRIPTION:Description Caldav Test")
@@ -75,3 +80,241 @@ func TestCaldav(t *testing.T) {
assert.Contains(t, rec.Body.String(), "END:VALARM")
})
}
func TestCaldavSubtasks(t *testing.T) {
const vtodoHeader = `BEGIN:VCALENDAR
VERSION:2.0
METHOD:PUBLISH
X-PUBLISHED-TTL:PT4H
X-WR-CALNAME:Project 36 for Caldav tests
PRODID:-//Vikunja Todo App//EN
`
const vtodoFooter = `
END:VCALENDAR`
t.Run("Import Task & Subtask", func(t *testing.T) {
const vtodoParentTaskStub = `BEGIN:VTODO
UID:uid_parent_import
DTSTAMP:20230301T073337Z
SUMMARY:Caldav parent task
CREATED:20230301T073337Z
LAST-MODIFIED:20230301T073337Z
END:VTODO`
const vtodoChildTaskStub = `BEGIN:VTODO
UID:uid_child_import
DTSTAMP:20230301T073337Z
SUMMARY:Caldav child task
CREATED:20230301T073337Z
LAST-MODIFIED:20230301T073337Z
RELATED-TO;RELTYPE=PARENT:uid_parent_import
END:VTODO`
const vtodoGrandChildTaskStub = `
BEGIN:VTODO
UID:uid_grand_child_import
DTSTAMP:20230301T073337Z
SUMMARY:Caldav grand child task
CREATED:20230301T073337Z
LAST-MODIFIED:20230301T073337Z
RELATED-TO;RELTYPE=PARENT:uid_child_import
END:VTODO`
e, _ := setupTestEnv()
const parentVTODO = vtodoHeader + vtodoParentTaskStub + vtodoFooter
rec, err := newCaldavTestRequestWithUser(t, e, http.MethodPut, caldav.TaskHandler, &testuser15, parentVTODO, nil, map[string]string{"project": "36", "task": "uid_parent_import"})
require.NoError(t, err)
assert.Equal(t, 201, rec.Result().StatusCode)
const childVTODO = vtodoHeader + vtodoChildTaskStub + vtodoFooter
rec, err = newCaldavTestRequestWithUser(t, e, http.MethodPut, caldav.TaskHandler, &testuser15, childVTODO, nil, map[string]string{"project": "36", "task": "uid_child_import"})
require.NoError(t, err)
assert.Equal(t, 201, rec.Result().StatusCode)
const grandChildVTODO = vtodoHeader + vtodoGrandChildTaskStub + vtodoFooter
rec, err = newCaldavTestRequestWithUser(t, e, http.MethodPut, caldav.TaskHandler, &testuser15, grandChildVTODO, nil, map[string]string{"project": "36", "task": "uid_grand_child_import"})
require.NoError(t, err)
assert.Equal(t, 201, rec.Result().StatusCode)
rec, err = newCaldavTestRequestWithUser(t, e, http.MethodGet, caldav.ProjectHandler, &testuser15, ``, nil, map[string]string{"project": "36"})
require.NoError(t, err)
assert.Equal(t, 200, rec.Result().StatusCode)
assert.Contains(t, rec.Body.String(), "UID:uid_parent_import")
assert.Contains(t, rec.Body.String(), "RELATED-TO;RELTYPE=CHILD:uid_child_import")
assert.Contains(t, rec.Body.String(), "UID:uid_child_import")
assert.Contains(t, rec.Body.String(), "RELATED-TO;RELTYPE=PARENT:uid_parent_import")
assert.Contains(t, rec.Body.String(), "RELATED-TO;RELTYPE=CHILD:uid_grand_child_import")
assert.Contains(t, rec.Body.String(), "UID:uid_grand_child_import")
assert.Contains(t, rec.Body.String(), "RELATED-TO;RELTYPE=PARENT:uid_child_import")
})
t.Run("Import Task & Subtask (Reverse - Subtask first)", func(t *testing.T) {
e, _ := setupTestEnv()
const vtodoGrandChildTaskStub = `
BEGIN:VTODO
UID:uid_grand_child_import
DTSTAMP:20230301T073337Z
SUMMARY:Caldav grand child task
CREATED:20230301T073337Z
LAST-MODIFIED:20230301T073337Z
RELATED-TO;RELTYPE=PARENT:uid_child_import
END:VTODO`
const grandChildVTODO = vtodoHeader + vtodoGrandChildTaskStub + vtodoFooter
rec, err := newCaldavTestRequestWithUser(t, e, http.MethodPut, caldav.TaskHandler, &testuser15, grandChildVTODO, nil, map[string]string{"project": "36", "task": "uid_grand_child_import"})
require.NoError(t, err)
assert.Equal(t, 201, rec.Result().StatusCode)
const vtodoChildTaskStub = `BEGIN:VTODO
UID:uid_child_import
DTSTAMP:20230301T073337Z
SUMMARY:Caldav child task
CREATED:20230301T073337Z
LAST-MODIFIED:20230301T073337Z
RELATED-TO;RELTYPE=PARENT:uid_parent_import
RELATED-TO;RELTYPE=CHILD:uid_grand_child_import
END:VTODO`
const childVTODO = vtodoHeader + vtodoChildTaskStub + vtodoFooter
rec, err = newCaldavTestRequestWithUser(t, e, http.MethodPut, caldav.TaskHandler, &testuser15, childVTODO, nil, map[string]string{"project": "36", "task": "uid_child_import"})
require.NoError(t, err)
assert.Equal(t, 201, rec.Result().StatusCode)
const vtodoParentTaskStub = `BEGIN:VTODO
UID:uid_parent_import
DTSTAMP:20230301T073337Z
SUMMARY:Caldav parent task
CREATED:20230301T073337Z
LAST-MODIFIED:20230301T073337Z
RELATED-TO;RELTYPE=CHILD:uid_child_import
END:VTODO`
const parentVTODO = vtodoHeader + vtodoParentTaskStub + vtodoFooter
rec, err = newCaldavTestRequestWithUser(t, e, http.MethodPut, caldav.TaskHandler, &testuser15, parentVTODO, nil, map[string]string{"project": "36", "task": "uid_parent_import"})
require.NoError(t, err)
assert.Equal(t, 201, rec.Result().StatusCode)
rec, err = newCaldavTestRequestWithUser(t, e, http.MethodGet, caldav.ProjectHandler, &testuser15, ``, nil, map[string]string{"project": "36"})
require.NoError(t, err)
assert.Equal(t, 200, rec.Result().StatusCode)
assert.Contains(t, rec.Body.String(), "UID:uid_parent_import")
assert.Contains(t, rec.Body.String(), "RELATED-TO;RELTYPE=CHILD:uid_child_import")
assert.Contains(t, rec.Body.String(), "UID:uid_child_import")
assert.Contains(t, rec.Body.String(), "RELATED-TO;RELTYPE=PARENT:uid_parent_import")
assert.Contains(t, rec.Body.String(), "RELATED-TO;RELTYPE=CHILD:uid_grand_child_import")
assert.Contains(t, rec.Body.String(), "UID:uid_grand_child_import")
assert.Contains(t, rec.Body.String(), "RELATED-TO;RELTYPE=PARENT:uid_child_import")
})
t.Run("Delete Subtask", func(t *testing.T) {
e, _ := setupTestEnv()
rec, err := newCaldavTestRequestWithUser(t, e, http.MethodDelete, caldav.TaskHandler, &testuser15, ``, nil, map[string]string{"project": "36", "task": "uid-caldav-test-child-task"})
require.NoError(t, err)
assert.Equal(t, 204, rec.Result().StatusCode)
rec, err = newCaldavTestRequestWithUser(t, e, http.MethodDelete, caldav.TaskHandler, &testuser15, ``, nil, map[string]string{"project": "36", "task": "uid-caldav-test-child-task-2"})
require.NoError(t, err)
assert.Equal(t, 204, rec.Result().StatusCode)
rec, err = newCaldavTestRequestWithUser(t, e, http.MethodGet, caldav.TaskHandler, &testuser15, ``, nil, map[string]string{"project": "36", "task": "uid-caldav-test-parent-task"})
require.NoError(t, err)
assert.Equal(t, 200, rec.Result().StatusCode)
assert.NotContains(t, rec.Body.String(), "RELATED-TO;RELTYPE=CHILD:uid-caldav-test-child-task")
assert.NotContains(t, rec.Body.String(), "RELATED-TO;RELTYPE=CHILD:uid-caldav-test-child-task-2")
})
t.Run("Delete Parent Task", func(t *testing.T) {
e, _ := setupTestEnv()
rec, err := newCaldavTestRequestWithUser(t, e, http.MethodDelete, caldav.TaskHandler, &testuser15, ``, nil, map[string]string{"project": "36", "task": "uid-caldav-test-parent-task"})
require.NoError(t, err)
assert.Equal(t, 204, rec.Result().StatusCode)
rec, err = newCaldavTestRequestWithUser(t, e, http.MethodGet, caldav.TaskHandler, &testuser15, ``, nil, map[string]string{"project": "36", "task": "uid-caldav-test-child-task"})
require.NoError(t, err)
assert.Equal(t, 200, rec.Result().StatusCode)
assert.NotContains(t, rec.Body.String(), "RELATED-TO;RELTYPE=PARENT:uid-caldav-test-parent-task")
})
}
func TestCaldavSubtasksDifferentLists(t *testing.T) {
t.Run("Import Parent Task & Child Task Different Lists", func(t *testing.T) {
const vtodoParentTask = `BEGIN:VCALENDAR
VERSION:2.0
METHOD:PUBLISH
X-PUBLISHED-TTL:PT4H
X-WR-CALNAME:Project 36 for Caldav tests
PRODID:-//Vikunja Todo App//EN
BEGIN:VTODO
UID:uid_parent_import
DTSTAMP:20230301T073337Z
SUMMARY:Caldav parent task
CREATED:20230301T073337Z
LAST-MODIFIED:20230301T073337Z
END:VTODO
END:VCALENDAR`
const vtodoChildTask = `BEGIN:VCALENDAR
VERSION:2.0
METHOD:PUBLISH
X-PUBLISHED-TTL:PT4H
X-WR-CALNAME:Project 38 for Caldav tests
PRODID:-//Vikunja Todo App//EN
BEGIN:VTODO
UID:uid_child_import
DTSTAMP:20230301T073337Z
SUMMARY:Caldav child task
CREATED:20230301T073337Z
LAST-MODIFIED:20230301T073337Z
RELATED-TO;RELTYPE=PARENT:uid_parent_import
END:VTODO
END:VCALENDAR`
e, _ := setupTestEnv()
rec, err := newCaldavTestRequestWithUser(t, e, http.MethodPut, caldav.TaskHandler, &testuser15, vtodoParentTask, nil, map[string]string{"project": "36", "task": "uid_parent_import"})
require.NoError(t, err)
assert.Equal(t, 201, rec.Result().StatusCode)
rec, err = newCaldavTestRequestWithUser(t, e, http.MethodPut, caldav.TaskHandler, &testuser15, vtodoChildTask, nil, map[string]string{"project": "38", "task": "uid_child_import"})
require.NoError(t, err)
assert.Equal(t, 201, rec.Result().StatusCode)
rec, err = newCaldavTestRequestWithUser(t, e, http.MethodGet, caldav.TaskHandler, &testuser15, ``, nil, map[string]string{"project": "36", "task": "uid_parent_import"})
require.NoError(t, err)
assert.Equal(t, 200, rec.Result().StatusCode)
assert.Contains(t, rec.Body.String(), "UID:uid_parent_import")
assert.Contains(t, rec.Body.String(), "RELATED-TO;RELTYPE=CHILD:uid_child_import")
rec, err = newCaldavTestRequestWithUser(t, e, http.MethodGet, caldav.TaskHandler, &testuser15, ``, nil, map[string]string{"project": "38", "task": "uid_child_import"})
require.NoError(t, err)
assert.Equal(t, 200, rec.Result().StatusCode)
assert.Contains(t, rec.Body.String(), "UID:uid_child_import")
assert.Contains(t, rec.Body.String(), "RELATED-TO;RELTYPE=PARENT:uid_parent_import")
})
t.Run("Check relationships across lists", func(t *testing.T) {
e, _ := setupTestEnv()
rec, err := newCaldavTestRequestWithUser(t, e, http.MethodGet, caldav.TaskHandler, &testuser15, ``, nil, map[string]string{"project": "36", "task": "uid-caldav-test-parent-task-another-list"})
require.NoError(t, err)
assert.Equal(t, 200, rec.Result().StatusCode)
assert.Contains(t, rec.Body.String(), "UID:uid-caldav-test-parent-task-another-list")
assert.Contains(t, rec.Body.String(), "RELATED-TO;RELTYPE=CHILD:uid-caldav-test-child-task-another-list")
rec, err = newCaldavTestRequestWithUser(t, e, http.MethodGet, caldav.TaskHandler, &testuser15, ``, nil, map[string]string{"project": "38", "task": "uid-caldav-test-child-task-another-list"})
require.NoError(t, err)
assert.Equal(t, 200, rec.Result().StatusCode)
assert.Contains(t, rec.Body.String(), "UID:uid-caldav-test-child-task-another-list")
assert.Contains(t, rec.Body.String(), "RELATED-TO;RELTYPE=PARENT:uid-caldav-test-parent-task-another-list")
})
}
+4 -2
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@@ -21,13 +21,15 @@ import (
"testing"
"code.vikunja.io/api/pkg/routes"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestHealthcheck(t *testing.T) {
t.Run("healthcheck", func(t *testing.T) {
rec, err := newTestRequest(t, http.MethodGet, routes.HealthcheckHandler, ``, nil, nil)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), "OK")
})
}
+51 -47
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@@ -37,9 +37,11 @@ import (
"code.vikunja.io/api/pkg/user"
"code.vikunja.io/web"
"code.vikunja.io/web/handler"
"github.com/golang-jwt/jwt/v5"
"github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// These are the test users, the same way they are in the test database
@@ -79,54 +81,13 @@ func setupTestEnv() (e *echo.Echo, err error) {
return
}
func bootstrapTestRequest(t *testing.T, method string, payload string, queryParam url.Values) (c echo.Context, rec *httptest.ResponseRecorder) {
// Setup
e, err := setupTestEnv()
assert.NoError(t, err)
// Do the actual request
func createRequest(e *echo.Echo, method string, payload string, queryParam url.Values, urlParams map[string]string) (c echo.Context, rec *httptest.ResponseRecorder) {
req := httptest.NewRequest(method, "/", strings.NewReader(payload))
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
req.URL.RawQuery = queryParam.Encode()
rec = httptest.NewRecorder()
c = e.NewContext(req, rec)
return
}
func newTestRequest(t *testing.T, method string, handler func(ctx echo.Context) error, payload string, queryParams url.Values, urlParams map[string]string) (rec *httptest.ResponseRecorder, err error) {
rec, c := testRequestSetup(t, method, payload, queryParams, urlParams)
err = handler(c)
return
}
func addUserTokenToContext(t *testing.T, user *user.User, c echo.Context) {
// Get the token as a string
token, err := auth.NewUserJWTAuthtoken(user, false)
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 addLinkShareTokenToContext(t *testing.T, share *models.LinkSharing, c echo.Context) {
// Get the token as a string
token, err := auth.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
for name, value := range urlParams {
@@ -138,22 +99,65 @@ func testRequestSetup(t *testing.T, method string, payload string, queryParams u
return
}
func bootstrapTestRequest(t *testing.T, method string, payload string, queryParam url.Values, urlParams map[string]string) (c echo.Context, rec *httptest.ResponseRecorder) {
// Setup
e, err := setupTestEnv()
require.NoError(t, err)
c, rec = createRequest(e, method, payload, queryParam, urlParams)
return
}
func newTestRequest(t *testing.T, method string, handler func(ctx echo.Context) error, payload string, queryParams url.Values, urlParams map[string]string) (rec *httptest.ResponseRecorder, err error) {
var c echo.Context
c, rec = bootstrapTestRequest(t, method, payload, queryParams, urlParams)
err = handler(c)
return
}
func addUserTokenToContext(t *testing.T, user *user.User, c echo.Context) {
// Get the token as a string
token, err := auth.NewUserJWTAuthtoken(user, false)
require.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
})
require.NoError(t, err)
c.Set("user", tken)
}
func addLinkShareTokenToContext(t *testing.T, share *models.LinkSharing, c echo.Context) {
// Get the token as a string
token, err := auth.NewLinkShareJWTAuthtoken(share)
require.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
})
require.NoError(t, err)
c.Set("user", tken)
}
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)
var c echo.Context
c, rec = bootstrapTestRequest(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)
var c echo.Context
c, rec = bootstrapTestRequest(t, method, payload, queryParams, urlParams)
addLinkShareTokenToContext(t, share, c)
err = handler(c)
return
}
func newCaldavTestRequestWithUser(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)
func newCaldavTestRequestWithUser(t *testing.T, e *echo.Echo, method string, handler echo.HandlerFunc, user *user.User, payload string, queryParams url.Values, urlParams map[string]string) (rec *httptest.ResponseRecorder, err error) {
var c echo.Context
c, rec = createRequest(e, method, payload, queryParams, urlParams)
c.Request().Header.Set(echo.HeaderContentType, echo.MIMETextPlain)
result, _ := caldav.BasicAuth(user.Username, "1234", c)
+51 -50
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@@ -20,11 +20,12 @@ import (
"testing"
"code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/web/handler"
"github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestBucket(t *testing.T) {
@@ -52,7 +53,7 @@ func TestBucket(t *testing.T) {
t.Run("ReadAll", func(t *testing.T) {
t.Run("Normal", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(nil, map[string]string{"project": "1"})
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `testbucket1`)
assert.Contains(t, rec.Body.String(), `testbucket2`)
assert.Contains(t, rec.Body.String(), `testbucket3`)
@@ -63,87 +64,87 @@ func TestBucket(t *testing.T) {
t.Run("Normal", func(t *testing.T) {
// Check the project was loaded successfully afterwards, see testReadOneWithUser
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"bucket": "1"}, `{"title":"TestLoremIpsum"}`)
assert.NoError(t, err)
require.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)
require.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)
require.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)
require.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)
require.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)
require.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)
require.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)
require.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)
require.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)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
})
t.Run("Shared Via Parent Project User readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"bucket": "12"}, `{"title":"TestLoremIpsum"}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project User write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"bucket": "13"}, `{"title":"TestLoremIpsum"}`)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
})
t.Run("Shared Via Parent Project User admin", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"bucket": "14"}, `{"title":"TestLoremIpsum"}`)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
})
t.Run("Shared Via Parent Project Team readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"bucket": "15"}, `{"title":"TestLoremIpsum"}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project Team write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"bucket": "16"}, `{"title":"TestLoremIpsum"}`)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
})
t.Run("Shared Via Parent Project Team admin", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"bucket": "17"}, `{"title":"TestLoremIpsum"}`)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
})
})
@@ -151,82 +152,82 @@ func TestBucket(t *testing.T) {
t.Run("Delete", func(t *testing.T) {
t.Run("Normal", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "1", "bucket": "1"})
assert.NoError(t, err)
require.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)
require.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{"project": "20", "bucket": "5"})
assert.Error(t, err)
require.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{"project": "6", "bucket": "6"})
assert.Error(t, err)
require.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{"project": "7", "bucket": "7"})
assert.NoError(t, err)
require.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{"project": "8", "bucket": "8"})
assert.NoError(t, err)
require.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{"project": "9", "bucket": "9"})
assert.Error(t, err)
require.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{"project": "10", "bucket": "10"})
assert.NoError(t, err)
require.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{"project": "11", "bucket": "11"})
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
})
t.Run("Shared Via Parent Project Team readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "12", "bucket": "12"})
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project Team write", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "13", "bucket": "13"})
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
})
t.Run("Shared Via Parent Project Team admin", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "14", "bucket": "14"})
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
})
t.Run("Shared Via Parent Project User readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "15", "bucket": "15"})
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project User write", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "16", "bucket": "16"})
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
})
t.Run("Shared Via Parent Project User admin", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "17", "bucket": "17"})
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
})
})
@@ -234,88 +235,88 @@ func TestBucket(t *testing.T) {
t.Run("Create", func(t *testing.T) {
t.Run("Normal", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "1"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
require.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{"project": "9999"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err)
require.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeProjectDoesNotExist)
})
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{"project": "20"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err)
require.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{"project": "6"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err)
require.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{"project": "7"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
require.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{"project": "8"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
require.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{"project": "9"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err)
require.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{"project": "10"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
require.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{"project": "11"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
t.Run("Shared Via Parent Project Team readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "12"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project Team write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "13"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
t.Run("Shared Via Parent Project Team admin", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "14"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
t.Run("Shared Via Parent Project User readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "15"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project User write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "16"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
t.Run("Shared Via Parent Project User admin", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "17"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
})
t.Run("Link Share", func(t *testing.T) {
rec, err := testHandlerLinkShareWrite.testCreateWithLinkShare(nil, map[string]string{"project": "2"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
db.AssertExists(t, "buckets", map[string]interface{}{
"project_id": 2,
+8 -6
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@@ -22,39 +22,41 @@ import (
"code.vikunja.io/api/pkg/models"
apiv1 "code.vikunja.io/api/pkg/routes/api/v1"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestLinkSharingAuth(t *testing.T) {
t.Run("Without Password", func(t *testing.T) {
rec, err := newTestRequest(t, http.MethodPost, apiv1.AuthenticateLinkShare, ``, nil, map[string]string{"share": "test"})
assert.NoError(t, err)
require.NoError(t, err)
assert.Equal(t, http.StatusOK, rec.Code)
assert.Contains(t, rec.Body.String(), `"token":"`)
assert.Contains(t, rec.Body.String(), `"project_id":1`)
})
t.Run("Without Password, Password Provided", func(t *testing.T) {
rec, err := newTestRequest(t, http.MethodPost, apiv1.AuthenticateLinkShare, `{"password":"somethingsomething"}`, nil, map[string]string{"share": "test"})
assert.NoError(t, err)
require.NoError(t, err)
assert.Equal(t, http.StatusOK, rec.Code)
assert.Contains(t, rec.Body.String(), `"token":"`)
assert.Contains(t, rec.Body.String(), `"project_id":1`)
})
t.Run("With Password, No Password Provided", func(t *testing.T) {
_, err := newTestRequest(t, http.MethodPost, apiv1.AuthenticateLinkShare, ``, nil, map[string]string{"share": "testWithPassword"})
assert.Error(t, err)
require.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeLinkSharePasswordRequired)
})
t.Run("With Password, Password Provided", func(t *testing.T) {
rec, err := newTestRequest(t, http.MethodPost, apiv1.AuthenticateLinkShare, `{"password":"1234"}`, nil, map[string]string{"share": "testWithPassword"})
assert.NoError(t, err)
require.NoError(t, err)
assert.Equal(t, http.StatusOK, rec.Code)
assert.Contains(t, rec.Body.String(), `"token":"`)
assert.Contains(t, rec.Body.String(), `"project_id":1`)
})
t.Run("With Wrong Password", func(t *testing.T) {
_, err := newTestRequest(t, http.MethodPost, apiv1.AuthenticateLinkShare, `{"password":"someWrongPassword"}`, nil, map[string]string{"share": "testWithPassword"})
assert.Error(t, err)
require.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeLinkSharePasswordInvalid)
})
}
+94 -92
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@@ -22,8 +22,10 @@ import (
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/web/handler"
"github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestLinkSharing(t *testing.T) {
@@ -66,68 +68,68 @@ func TestLinkSharing(t *testing.T) {
t.Run("Forbidden", func(t *testing.T) {
t.Run("read only", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "20"}, `{"right":0}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("write", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "20"}, `{"right":1}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("admin", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "20"}, `{"right":2}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
})
t.Run("Read only access", func(t *testing.T) {
t.Run("read only", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "9"}, `{"right":0}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("write", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "9"}, `{"right":1}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("admin", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "9"}, `{"right":2}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
})
t.Run("Write access", func(t *testing.T) {
t.Run("read only", func(t *testing.T) {
req, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "10"}, `{"right":0}`)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, req.Body.String(), `"hash":`)
})
t.Run("write", func(t *testing.T) {
req, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "10"}, `{"right":1}`)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, req.Body.String(), `"hash":`)
})
t.Run("admin", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "10"}, `{"right":2}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
})
t.Run("Admin access", func(t *testing.T) {
t.Run("read only", func(t *testing.T) {
req, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "11"}, `{"right":0}`)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, req.Body.String(), `"hash":`)
})
t.Run("write", func(t *testing.T) {
req, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "11"}, `{"right":1}`)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, req.Body.String(), `"hash":`)
})
t.Run("admin", func(t *testing.T) {
req, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "11"}, `{"right":2}`)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, req.Body.String(), `"hash":`)
})
})
@@ -159,7 +161,7 @@ func TestLinkSharing(t *testing.T) {
t.Run("ReadAll", func(t *testing.T) {
t.Run("Normal", func(t *testing.T) {
rec, err := testHandlerProjectReadOnly.testReadAllWithLinkShare(nil, nil)
assert.NoError(t, err)
require.NoError(t, err)
// Should only return the shared project, nothing else
assert.Contains(t, rec.Body.String(), `Test1`)
assert.NotContains(t, rec.Body.String(), `Test2`)
@@ -169,7 +171,7 @@ func TestLinkSharing(t *testing.T) {
})
t.Run("Search", func(t *testing.T) {
rec, err := testHandlerProjectReadOnly.testReadAllWithLinkShare(url.Values{"s": []string{"est1"}}, nil)
assert.NoError(t, err)
require.NoError(t, err)
// Should only return the shared project, nothing else
assert.Contains(t, rec.Body.String(), `Test1`)
assert.NotContains(t, rec.Body.String(), `Test2`)
@@ -181,35 +183,35 @@ func TestLinkSharing(t *testing.T) {
t.Run("ReadOne", func(t *testing.T) {
t.Run("Normal", func(t *testing.T) {
rec, err := testHandlerProjectReadOnly.testReadOneWithLinkShare(nil, map[string]string{"project": "1"})
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Test1"`)
assert.NotContains(t, rec.Body.String(), `"title":"Test2"`)
})
t.Run("Nonexisting", func(t *testing.T) {
_, err := testHandlerProjectReadOnly.testReadOneWithLinkShare(nil, map[string]string{"project": "9999999"})
assert.Error(t, err)
require.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeProjectDoesNotExist)
})
t.Run("Rights check", func(t *testing.T) {
t.Run("Forbidden", func(t *testing.T) {
// Project 2, not shared with this token
_, err := testHandlerProjectReadOnly.testReadOneWithLinkShare(nil, map[string]string{"project": "2"})
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `You don't have the right to see this`)
})
t.Run("Shared readonly", func(t *testing.T) {
rec, err := testHandlerProjectReadOnly.testReadOneWithLinkShare(nil, map[string]string{"project": "1"})
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Test1"`)
})
t.Run("Shared write", func(t *testing.T) {
rec, err := testHandlerProjectWrite.testReadOneWithLinkShare(nil, map[string]string{"project": "2"})
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Test2"`)
})
t.Run("Shared admin", func(t *testing.T) {
rec, err := testHandlerProjectAdmin.testReadOneWithLinkShare(nil, map[string]string{"project": "3"})
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Test3"`)
})
})
@@ -217,28 +219,28 @@ func TestLinkSharing(t *testing.T) {
t.Run("Update", func(t *testing.T) {
t.Run("Nonexisting", func(t *testing.T) {
_, err := testHandlerProjectReadOnly.testUpdateWithLinkShare(nil, map[string]string{"project": "9999999"}, `{"title":"TestLoremIpsum"}`)
assert.Error(t, err)
require.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeProjectDoesNotExist)
})
t.Run("Rights check", func(t *testing.T) {
t.Run("Forbidden", func(t *testing.T) {
_, err := testHandlerProjectReadOnly.testUpdateWithLinkShare(nil, map[string]string{"project": "2"}, `{"title":"TestLoremIpsum"}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerProjectReadOnly.testUpdateWithLinkShare(nil, map[string]string{"project": "1"}, `{"title":"TestLoremIpsum"}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared write", func(t *testing.T) {
rec, err := testHandlerProjectWrite.testUpdateWithLinkShare(nil, map[string]string{"project": "2"}, `{"title":"TestLoremIpsum","namespace_id":1}`)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
})
t.Run("Shared admin", func(t *testing.T) {
rec, err := testHandlerProjectAdmin.testUpdateWithLinkShare(nil, map[string]string{"project": "3"}, `{"title":"TestLoremIpsum","namespace_id":2}`)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
})
})
@@ -246,28 +248,28 @@ func TestLinkSharing(t *testing.T) {
t.Run("Delete", func(t *testing.T) {
t.Run("Nonexisting", func(t *testing.T) {
_, err := testHandlerProjectReadOnly.testDeleteWithLinkShare(nil, map[string]string{"project": "9999999"})
assert.Error(t, err)
require.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeProjectDoesNotExist)
})
t.Run("Rights check", func(t *testing.T) {
t.Run("Forbidden", func(t *testing.T) {
_, err := testHandlerProjectReadOnly.testDeleteWithLinkShare(nil, map[string]string{"project": "1"})
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerProjectReadOnly.testDeleteWithLinkShare(nil, map[string]string{"project": "1"})
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerProjectWrite.testDeleteWithLinkShare(nil, map[string]string{"project": "2"})
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared admin", func(t *testing.T) {
rec, err := testHandlerProjectAdmin.testDeleteWithLinkShare(nil, map[string]string{"project": "3"})
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
})
})
@@ -277,23 +279,23 @@ func TestLinkSharing(t *testing.T) {
t.Run("Create", func(t *testing.T) {
t.Run("Nonexisting", func(t *testing.T) {
_, err := testHandlerProjectReadOnly.testCreateWithLinkShare(nil, nil, `{"title":"Lorem"}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Rights check", func(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerProjectReadOnly.testCreateWithLinkShare(nil, map[string]string{"namespace": "1"}, `{"title":"Lorem","description":"Lorem Ipsum"}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerProjectWrite.testCreateWithLinkShare(nil, map[string]string{"namespace": "2"}, `{"title":"Lorem","description":"Lorem Ipsum"}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared admin", func(t *testing.T) {
_, err := testHandlerProjectAdmin.testCreateWithLinkShare(nil, map[string]string{"namespace": "3"}, `{"title":"Lorem","description":"Lorem Ipsum"}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
})
@@ -325,51 +327,51 @@ func TestLinkSharing(t *testing.T) {
t.Run("ReadAll", func(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) {
rec, err := testHandlerProjectUserReadOnly.testReadAllWithLinkShare(nil, map[string]string{"project": "1"})
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[]`)
})
t.Run("Shared write", func(t *testing.T) {
rec, err := testHandlerProjectUserWrite.testReadAllWithLinkShare(nil, map[string]string{"project": "2"})
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[]`)
})
t.Run("Shared admin", func(t *testing.T) {
rec, err := testHandlerProjectUserAdmin.testReadAllWithLinkShare(nil, map[string]string{"project": "3"})
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"username":"user1"`)
})
})
t.Run("Create", func(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerProjectUserReadOnly.testCreateWithLinkShare(nil, map[string]string{"project": "1"}, `{"user_id":"user1"}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerProjectUserWrite.testCreateWithLinkShare(nil, map[string]string{"project": "2"}, `{"user_id":"user1"}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared admin", func(t *testing.T) {
_, err := testHandlerProjectUserAdmin.testCreateWithLinkShare(nil, map[string]string{"project": "3"}, `{"user_id":"user1"}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
})
t.Run("Update", func(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerProjectUserReadOnly.testUpdateWithLinkShare(nil, map[string]string{"project": "1"}, `{"user_id":"user1"}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerProjectUserWrite.testUpdateWithLinkShare(nil, map[string]string{"project": "2"}, `{"user_id":"user1"}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared admin", func(t *testing.T) {
_, err := testHandlerProjectUserAdmin.testUpdateWithLinkShare(nil, map[string]string{"project": "3"}, `{"user_id":"user1"}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
@@ -377,17 +379,17 @@ func TestLinkSharing(t *testing.T) {
t.Run("Delete", func(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerProjectUserReadOnly.testDeleteWithLinkShare(nil, map[string]string{"project": "1"})
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerProjectUserWrite.testDeleteWithLinkShare(nil, map[string]string{"project": "2"})
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared admin", func(t *testing.T) {
_, err := testHandlerProjectUserAdmin.testDeleteWithLinkShare(nil, map[string]string{"project": "3"})
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
})
@@ -417,51 +419,51 @@ func TestLinkSharing(t *testing.T) {
t.Run("ReadAll", func(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) {
rec, err := testHandlerProjectTeamReadOnly.testReadAllWithLinkShare(nil, map[string]string{"project": "1"})
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[]`)
})
t.Run("Shared write", func(t *testing.T) {
rec, err := testHandlerProjectTeamWrite.testReadAllWithLinkShare(nil, map[string]string{"project": "2"})
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[]`)
})
t.Run("Shared admin", func(t *testing.T) {
rec, err := testHandlerProjectTeamAdmin.testReadAllWithLinkShare(nil, map[string]string{"project": "3"})
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"name":"testteam1"`)
})
})
t.Run("Create", func(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerProjectTeamReadOnly.testCreateWithLinkShare(nil, map[string]string{"project": "1"}, `{"team_id":1}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerProjectTeamWrite.testCreateWithLinkShare(nil, map[string]string{"project": "2"}, `{"team_id":1}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared admin", func(t *testing.T) {
_, err := testHandlerProjectTeamAdmin.testCreateWithLinkShare(nil, map[string]string{"project": "3"}, `{"team_id":1}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
})
t.Run("Update", func(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerProjectTeamReadOnly.testUpdateWithLinkShare(nil, map[string]string{"project": "1"}, `{"team_id":1}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerProjectTeamWrite.testUpdateWithLinkShare(nil, map[string]string{"project": "2"}, `{"team_id":1}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared admin", func(t *testing.T) {
_, err := testHandlerProjectTeamAdmin.testUpdateWithLinkShare(nil, map[string]string{"project": "3"}, `{"team_id":1}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
@@ -469,17 +471,17 @@ func TestLinkSharing(t *testing.T) {
t.Run("Delete", func(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerProjectTeamReadOnly.testDeleteWithLinkShare(nil, map[string]string{"project": "1"})
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerProjectTeamWrite.testDeleteWithLinkShare(nil, map[string]string{"project": "2"})
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared admin", func(t *testing.T) {
_, err := testHandlerProjectTeamAdmin.testDeleteWithLinkShare(nil, map[string]string{"project": "3"})
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
})
@@ -533,7 +535,7 @@ func TestLinkSharing(t *testing.T) {
t.Run("ReadAll", func(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) {
rec, err := testHandlerTaskReadOnlyCollection.testReadAllWithLinkShare(nil, nil)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `task #1`)
assert.Contains(t, rec.Body.String(), `task #2`)
assert.Contains(t, rec.Body.String(), `task #3`)
@@ -551,7 +553,7 @@ func TestLinkSharing(t *testing.T) {
})
t.Run("Shared write", func(t *testing.T) {
rec, err := testHandlerTaskWriteCollection.testReadAllWithLinkShare(nil, nil)
assert.NoError(t, err)
require.NoError(t, err)
assert.NotContains(t, rec.Body.String(), `task #2`)
assert.NotContains(t, rec.Body.String(), `task #3"`)
assert.NotContains(t, rec.Body.String(), `task #4`)
@@ -568,7 +570,7 @@ func TestLinkSharing(t *testing.T) {
})
t.Run("Shared admin", func(t *testing.T) {
rec, err := testHandlerTaskAdminCollection.testReadAllWithLinkShare(nil, nil)
assert.NoError(t, err)
require.NoError(t, err)
assert.NotContains(t, rec.Body.String(), `task #2`)
assert.NotContains(t, rec.Body.String(), `task #4`)
assert.NotContains(t, rec.Body.String(), `task #5`)
@@ -587,34 +589,34 @@ func TestLinkSharing(t *testing.T) {
t.Run("Create", func(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerTaskReadOnly.testCreateWithLinkShare(nil, map[string]string{"project": "1"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared write", func(t *testing.T) {
rec, err := testHandlerTaskWrite.testCreateWithLinkShare(nil, map[string]string{"project": "2"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
t.Run("Shared admin", func(t *testing.T) {
rec, err := testHandlerTaskAdmin.testCreateWithLinkShare(nil, map[string]string{"project": "3"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
})
t.Run("Update", func(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerTaskReadOnly.testUpdateWithLinkShare(nil, map[string]string{"projecttask": "1"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared write", func(t *testing.T) {
rec, err := testHandlerTaskWrite.testUpdateWithLinkShare(nil, map[string]string{"projecttask": "13"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
t.Run("Shared admin", func(t *testing.T) {
rec, err := testHandlerTaskAdmin.testUpdateWithLinkShare(nil, map[string]string{"projecttask": "32"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
@@ -622,17 +624,17 @@ func TestLinkSharing(t *testing.T) {
t.Run("Delete", func(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerTaskReadOnly.testDeleteWithLinkShare(nil, map[string]string{"projecttask": "1"})
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared write", func(t *testing.T) {
rec, err := testHandlerTaskWrite.testDeleteWithLinkShare(nil, map[string]string{"projecttask": "13"})
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
})
t.Run("Shared admin", func(t *testing.T) {
rec, err := testHandlerTaskAdmin.testDeleteWithLinkShare(nil, map[string]string{"projecttask": "32"})
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
})
})
@@ -663,34 +665,34 @@ func TestLinkSharing(t *testing.T) {
t.Run("ReadAll", func(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerTeamReadOnly.testReadAllWithLinkShare(nil, nil)
assert.Error(t, err)
require.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrorCodeGenericForbidden)
})
t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerTeamWrite.testReadAllWithLinkShare(nil, nil)
assert.Error(t, err)
require.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrorCodeGenericForbidden)
})
t.Run("Shared admin", func(t *testing.T) {
_, err := testHandlerTeamAdmin.testReadAllWithLinkShare(nil, nil)
assert.Error(t, err)
require.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrorCodeGenericForbidden)
})
})
t.Run("Update", func(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerTeamReadOnly.testUpdateWithLinkShare(nil, map[string]string{"team": "1"}, `{"name":"Lorem Ipsum"}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerTeamWrite.testUpdateWithLinkShare(nil, map[string]string{"team": "2"}, `{"name":"Lorem Ipsum"}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared admin", func(t *testing.T) {
_, err := testHandlerTeamAdmin.testUpdateWithLinkShare(nil, map[string]string{"team": "3"}, `{"name":"Lorem Ipsum"}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
@@ -698,17 +700,17 @@ func TestLinkSharing(t *testing.T) {
t.Run("Delete", func(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerTeamReadOnly.testDeleteWithLinkShare(nil, map[string]string{"team": "1"})
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerTeamWrite.testDeleteWithLinkShare(nil, map[string]string{"team": "2"})
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared admin", func(t *testing.T) {
_, err := testHandlerTeamAdmin.testDeleteWithLinkShare(nil, map[string]string{"team": "3"})
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
})
@@ -739,51 +741,51 @@ func TestLinkSharing(t *testing.T) {
t.Run("ReadAll", func(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) {
rec, err := testHandlerLinkShareReadOnly.testReadAllWithLinkShare(nil, map[string]string{"project": "1"})
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"hash":"test"`)
})
t.Run("Shared write", func(t *testing.T) {
rec, err := testHandlerLinkShareWrite.testReadAllWithLinkShare(nil, map[string]string{"project": "2"})
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"hash":"test2"`)
})
t.Run("Shared admin", func(t *testing.T) {
rec, err := testHandlerLinkShareAdmin.testReadAllWithLinkShare(nil, map[string]string{"project": "3"})
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"hash":"test3"`)
})
})
t.Run("Create", func(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerLinkShareReadOnly.testCreateWithLinkShare(nil, map[string]string{"project": "1"}, `{}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerLinkShareWrite.testCreateWithLinkShare(nil, map[string]string{"project": "2"}, `{}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared admin", func(t *testing.T) {
_, err := testHandlerLinkShareAdmin.testCreateWithLinkShare(nil, map[string]string{"project": "3"}, `{}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
})
t.Run("Update", func(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerLinkShareReadOnly.testUpdateWithLinkShare(nil, map[string]string{"share": "1"}, `{}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerLinkShareWrite.testUpdateWithLinkShare(nil, map[string]string{"share": "2"}, `{}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared admin", func(t *testing.T) {
_, err := testHandlerLinkShareAdmin.testUpdateWithLinkShare(nil, map[string]string{"share": "3"}, `{}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
@@ -791,17 +793,17 @@ func TestLinkSharing(t *testing.T) {
t.Run("Delete", func(t *testing.T) {
t.Run("Shared readonly", func(t *testing.T) {
_, err := testHandlerLinkShareReadOnly.testDeleteWithLinkShare(nil, map[string]string{"share": "1"})
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared write", func(t *testing.T) {
_, err := testHandlerLinkShareWrite.testDeleteWithLinkShare(nil, map[string]string{"share": "2"})
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared admin", func(t *testing.T) {
_, err := testHandlerLinkShareAdmin.testDeleteWithLinkShare(nil, map[string]string{"share": "3"})
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
})
+8 -6
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@@ -22,7 +22,9 @@ import (
apiv1 "code.vikunja.io/api/pkg/routes/api/v1"
"code.vikunja.io/api/pkg/user"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestLogin(t *testing.T) {
@@ -31,12 +33,12 @@ func TestLogin(t *testing.T) {
"username": "user1",
"password": "1234"
}`, nil, nil)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), "token")
})
t.Run("Empty payload", func(t *testing.T) {
_, err := newTestRequest(t, http.MethodPost, apiv1.Login, `{}`, nil, nil)
assert.Error(t, err)
require.Error(t, err)
assertHandlerErrorCode(t, err, user.ErrCodeNoUsernamePassword)
})
t.Run("Not existing user", func(t *testing.T) {
@@ -44,7 +46,7 @@ func TestLogin(t *testing.T) {
"username": "userWichDoesNotExist",
"password": "1234"
}`, nil, nil)
assert.Error(t, err)
require.Error(t, err)
assertHandlerErrorCode(t, err, user.ErrCodeWrongUsernameOrPassword)
})
t.Run("Wrong password", func(t *testing.T) {
@@ -52,7 +54,7 @@ func TestLogin(t *testing.T) {
"username": "user1",
"password": "wrong"
}`, nil, nil)
assert.Error(t, err)
require.Error(t, err)
assertHandlerErrorCode(t, err, user.ErrCodeWrongUsernameOrPassword)
})
t.Run("user with unconfirmed email", func(t *testing.T) {
@@ -60,7 +62,7 @@ func TestLogin(t *testing.T) {
"username": "user5",
"password": "1234"
}`, nil, nil)
assert.Error(t, err)
require.Error(t, err)
assertHandlerErrorCode(t, err, user.ErrCodeEmailNotConfirmed)
})
}
+66 -64
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@@ -22,8 +22,10 @@ import (
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/web/handler"
"github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestProject(t *testing.T) {
@@ -37,7 +39,7 @@ func TestProject(t *testing.T) {
t.Run("ReadAll", func(t *testing.T) {
t.Run("Normal", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(nil, nil)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `Test1`)
assert.NotContains(t, rec.Body.String(), `Test2"`)
assert.Contains(t, rec.Body.String(), `Test3`) // Shared directly via users_project
@@ -47,7 +49,7 @@ func TestProject(t *testing.T) {
})
t.Run("Search", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"s": []string{"Test1"}}, nil)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `Test1`)
assert.NotContains(t, rec.Body.String(), `Test2`)
assert.NotContains(t, rec.Body.String(), `Test3`)
@@ -56,7 +58,7 @@ func TestProject(t *testing.T) {
})
t.Run("Normal with archived projects", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"is_archived": []string{"true"}}, nil)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `Test1`)
assert.NotContains(t, rec.Body.String(), `Test2"`)
assert.Contains(t, rec.Body.String(), `Test3`) // Shared directly via users_project
@@ -69,7 +71,7 @@ func TestProject(t *testing.T) {
t.Run("ReadOne", func(t *testing.T) {
t.Run("Normal", func(t *testing.T) {
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "1"})
assert.NoError(t, err)
require.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,"name":"","username":"user1",`)
@@ -79,89 +81,89 @@ func TestProject(t *testing.T) {
})
t.Run("Nonexisting", func(t *testing.T) {
_, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "9999"})
assert.Error(t, err)
require.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeProjectDoesNotExist)
})
t.Run("Rights check", func(t *testing.T) {
t.Run("Forbidden", func(t *testing.T) {
// Owned by user13
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "20"})
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `You don't have the right to see this`)
assert.Empty(t, rec.Result().Header.Get("x-max-rights"))
})
t.Run("Shared Via Team readonly", func(t *testing.T) {
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "6"})
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Test6"`)
assert.Equal(t, "0", rec.Result().Header.Get("x-max-right"))
})
t.Run("Shared Via Team write", func(t *testing.T) {
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "7"})
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Test7"`)
assert.Equal(t, "1", rec.Result().Header.Get("x-max-right"))
})
t.Run("Shared Via Team admin", func(t *testing.T) {
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "8"})
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Test8"`)
assert.Equal(t, "2", rec.Result().Header.Get("x-max-right"))
})
t.Run("Shared Via User readonly", func(t *testing.T) {
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "9"})
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Test9"`)
assert.Equal(t, "0", rec.Result().Header.Get("x-max-right"))
})
t.Run("Shared Via User write", func(t *testing.T) {
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "10"})
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Test10"`)
assert.Equal(t, "1", rec.Result().Header.Get("x-max-right"))
})
t.Run("Shared Via User admin", func(t *testing.T) {
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "11"})
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Test11"`)
assert.Equal(t, "2", rec.Result().Header.Get("x-max-right"))
})
t.Run("Shared Via Parent Project Team readonly", func(t *testing.T) {
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "12"})
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Test12"`)
assert.Equal(t, "0", rec.Result().Header.Get("x-max-right"))
})
t.Run("Shared Via Parent Project Team write", func(t *testing.T) {
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "13"})
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Test13"`)
assert.Equal(t, "1", rec.Result().Header.Get("x-max-right"))
})
t.Run("Shared Via Parent Project Team admin", func(t *testing.T) {
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "14"})
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Test14"`)
assert.Equal(t, "2", rec.Result().Header.Get("x-max-right"))
})
t.Run("Shared Via Parent Project User readonly", func(t *testing.T) {
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "15"})
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Test15"`)
assert.Equal(t, "0", rec.Result().Header.Get("x-max-right"))
})
t.Run("Shared Via Parent Project User write", func(t *testing.T) {
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "16"})
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Test16"`)
assert.Equal(t, "1", rec.Result().Header.Get("x-max-right"))
})
t.Run("Shared Via Parent Project User admin", func(t *testing.T) {
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "17"})
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Test17"`)
assert.Equal(t, "2", rec.Result().Header.Get("x-max-right"))
})
@@ -171,100 +173,100 @@ func TestProject(t *testing.T) {
t.Run("Normal", func(t *testing.T) {
// Check the project was loaded successfully afterwards, see testReadOneWithUser
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "1"}, `{"title":"TestLoremIpsum"}`)
assert.NoError(t, err)
require.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.testUpdateWithUser(nil, map[string]string{"project": "9999"}, `{"title":"TestLoremIpsum"}`)
assert.Error(t, err)
require.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeProjectDoesNotExist)
})
t.Run("Normal with updating the description", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "1"}, `{"title":"TestLoremIpsum","description":"Lorem Ipsum dolor sit amet"}`)
assert.NoError(t, err)
require.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.testUpdateWithUser(nil, map[string]string{"project": "1"}, `{"title":""}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message.(models.ValidationHTTPError).InvalidFields, "title: non zero value required")
})
t.Run("Title too long", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "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)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message.(models.ValidationHTTPError).InvalidFields[0], "does not validate as runelength(1|250)")
})
t.Run("Rights check", func(t *testing.T) {
t.Run("Forbidden", func(t *testing.T) {
// Owned by user13
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "20"}, `{"title":"TestLoremIpsum"}`)
assert.Error(t, err)
require.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{"project": "6"}, `{"title":"TestLoremIpsum"}`)
assert.Error(t, err)
require.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{"project": "7"}, `{"title":"TestLoremIpsum"}`)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
})
t.Run("Shared Via Team admin", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "8"}, `{"title":"TestLoremIpsum"}`)
assert.NoError(t, err)
require.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{"project": "9"}, `{"title":"TestLoremIpsum"}`)
assert.Error(t, err)
require.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{"project": "10"}, `{"title":"TestLoremIpsum"}`)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
})
t.Run("Shared Via User admin", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "11"}, `{"title":"TestLoremIpsum"}`)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
})
t.Run("Shared Via Parent Project Team readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "12"}, `{"title":"TestLoremIpsum"}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project Team write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "13"}, `{"title":"TestLoremIpsum"}`)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
})
t.Run("Shared Via Parent Project Team admin", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "14"}, `{"title":"TestLoremIpsum"}`)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
})
t.Run("Shared Via Parent Project User readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "15"}, `{"title":"TestLoremIpsum"}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project User write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "16"}, `{"title":"TestLoremIpsum"}`)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
})
t.Run("Shared Via Parent Project User admin", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "17"}, `{"title":"TestLoremIpsum"}`)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
})
})
@@ -272,82 +274,82 @@ func TestProject(t *testing.T) {
t.Run("Delete", func(t *testing.T) {
t.Run("Normal", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "1"})
assert.NoError(t, err)
require.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{"project": "999"})
assert.Error(t, err)
require.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeProjectDoesNotExist)
})
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{"project": "20"})
assert.Error(t, err)
require.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{"project": "6"})
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Team write", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "7"})
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Team admin", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "8"})
assert.NoError(t, err)
require.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{"project": "9"})
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via User write", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "10"})
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via User admin", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "11"})
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
})
t.Run("Shared Via Parent Project Team readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "12"})
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project Team write", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "13"})
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project Team admin", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "14"})
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
})
t.Run("Shared Via Parent Project User readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "15"})
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project User write", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "16"})
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project User admin", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "17"})
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
})
})
@@ -356,7 +358,7 @@ func TestProject(t *testing.T) {
t.Run("Normal", func(t *testing.T) {
// Check the project was loaded successfully after update, see testReadOneWithUser
rec, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem"}`)
assert.NoError(t, err)
require.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`)
@@ -364,7 +366,7 @@ func TestProject(t *testing.T) {
})
t.Run("Normal with description", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem","description":"Lorem Ipsum"}`)
assert.NoError(t, err)
require.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`)
@@ -372,34 +374,34 @@ func TestProject(t *testing.T) {
})
t.Run("Nonexisting parent project", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem","parent_project_id":99999}`)
assert.Error(t, err)
require.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeProjectDoesNotExist)
})
t.Run("Empty title", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, nil, `{"title":""}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message.(models.ValidationHTTPError).InvalidFields, "title: non zero value required")
})
t.Run("Title too long", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea taki"}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message.(models.ValidationHTTPError).InvalidFields[0], "does not validate as runelength(1|250)")
})
t.Run("Rights check", func(t *testing.T) {
t.Run("Forbidden", func(t *testing.T) {
// Owned by user13
_, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem","parent_project_id":20}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project Team readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem","parent_project_id":32}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project Team write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem","parent_project_id":33}`)
assert.NoError(t, err)
require.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`)
@@ -407,7 +409,7 @@ func TestProject(t *testing.T) {
})
t.Run("Shared Via Parent Project Team admin", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem","parent_project_id":34}`)
assert.NoError(t, err)
require.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`)
@@ -416,12 +418,12 @@ func TestProject(t *testing.T) {
t.Run("Shared Via Parent Project User readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem","parent_project_id":9}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project User write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, nil, `{"title":"Lorem","parent_project_id":10}`)
assert.NoError(t, err)
require.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`)
@@ -429,7 +431,7 @@ func TestProject(t *testing.T) {
})
t.Run("Shared Via Parent Project User admin", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"namespace": "12"}, `{"title":"Lorem","parent_project_id":11}`)
assert.NoError(t, err)
require.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`)
+15 -13
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@@ -22,31 +22,33 @@ import (
apiv1 "code.vikunja.io/api/pkg/routes/api/v1"
"code.vikunja.io/api/pkg/user"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestRegister(t *testing.T) {
t.Run("normal register", func(t *testing.T) {
rec, err := newTestRequest(t, http.MethodPost, apiv1.RegisterUser, `{
"username": "newUser",
"password": "1234",
"password": "12345678",
"email": "email@example.com"
}`, nil, nil)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"username":"newUser"`)
})
t.Run("Empty payload", func(t *testing.T) {
_, err := newTestRequest(t, http.MethodPost, apiv1.RegisterUser, `{}`, nil, nil)
assert.Error(t, err)
require.Error(t, err)
assertHandlerErrorCode(t, err, user.ErrCodeNoUsernamePassword)
})
t.Run("Empty username", func(t *testing.T) {
_, err := newTestRequest(t, http.MethodPost, apiv1.RegisterUser, `{
"username": "",
"password": "1234",
"password": "12345678",
"email": "email@example.com"
}`, nil, nil)
assert.Error(t, err)
require.Error(t, err)
assertHandlerErrorCode(t, err, user.ErrCodeNoUsernamePassword)
})
t.Run("Empty password", func(t *testing.T) {
@@ -55,34 +57,34 @@ func TestRegister(t *testing.T) {
"password": "",
"email": "email@example.com"
}`, nil, nil)
assert.Error(t, err)
require.Error(t, err)
assertHandlerErrorCode(t, err, user.ErrCodeNoUsernamePassword)
})
t.Run("Empty email", func(t *testing.T) {
_, err := newTestRequest(t, http.MethodPost, apiv1.RegisterUser, `{
"username": "newUser",
"password": "1234",
"password": "12345678",
"email": ""
}`, nil, nil)
assert.Error(t, err)
require.Error(t, err)
assertHandlerErrorCode(t, err, user.ErrCodeNoUsernamePassword)
})
t.Run("Already existing username", func(t *testing.T) {
_, err := newTestRequest(t, http.MethodPost, apiv1.RegisterUser, `{
"username": "user1",
"password": "1234",
"password": "12345678",
"email": "email@example.com"
}`, nil, nil)
assert.Error(t, err)
require.Error(t, err)
assertHandlerErrorCode(t, err, user.ErrorCodeUsernameExists)
})
t.Run("Already existing email", func(t *testing.T) {
_, err := newTestRequest(t, http.MethodPost, apiv1.RegisterUser, `{
"username": "newUser",
"password": "1234",
"password": "12345678",
"email": "user1@example.com"
}`, nil, nil)
assert.Error(t, err)
require.Error(t, err)
assertHandlerErrorCode(t, err, user.ErrorCodeUserEmailExists)
})
}
+37 -35
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@@ -22,7 +22,9 @@ import (
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/web/handler"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestTaskCollection(t *testing.T) {
@@ -39,7 +41,7 @@ func TestTaskCollection(t *testing.T) {
t.Run("Normal", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(nil, urlParams)
assert.NoError(t, err)
require.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 `)
@@ -73,7 +75,7 @@ func TestTaskCollection(t *testing.T) {
})
t.Run("Search", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"s": []string{"task #6"}}, urlParams)
assert.NoError(t, err)
require.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 `)
@@ -91,7 +93,7 @@ func TestTaskCollection(t *testing.T) {
})
t.Run("Search case insensitive", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"s": []string{"tASk #6"}}, urlParams)
assert.NoError(t, err)
require.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 `)
@@ -112,65 +114,65 @@ func TestTaskCollection(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"}}, urlParams)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`)
})
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)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":3,"title":"task #3 high prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":4,"title":"task #4 low prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":1`)
})
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)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`)
})
// should equal duedate asc
t.Run("by due_date", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}}, urlParams)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
})
t.Run("by duedate desc", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"desc"}}, urlParams)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":6,"title":"task #6 lower due date`)
})
// Due date without unix suffix
t.Run("by duedate asc without suffix", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"asc"}}, urlParams)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
})
t.Run("by due_date without suffix", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}}, urlParams)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
})
t.Run("by duedate desc without suffix", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"desc"}}, urlParams)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":6,"title":"task #6 lower due date`)
})
t.Run("by duedate asc", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"asc"}}, urlParams)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
})
t.Run("invalid sort parameter", func(t *testing.T) {
_, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"loremipsum"}}, urlParams)
assert.Error(t, err)
require.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)
require.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)
require.NoError(t, err)
assert.NotContains(t, rec.Body.String(), `[{"id":3,"title":"task #3 high prio","description":"","done":false,"due_date":0,"reminders":null,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":0,"end_date":0,"assignees":null,"labels":null,"hex_color":"","created":1543626724,"updated":1543626724,"created_by":{"id":0,"name":"","username":"","email":"","created":0,"updated":0}},{"id":4,"title":"task #4 low prio","description":"","done":false,"due_date":0,"repeat_after":0,"repeat_mode":0,"priority":1`)
assert.NotContains(t, rec.Body.String(), `{"id":4,"title":"task #4 low prio","description":"","done":false,"due_date":0,"reminders":null,"repeat_after":0,"repeat_mode":0,"priority":1,"start_date":0,"end_date":0,"assignees":null,"labels":null,"hex_color":"","created":1543626724,"updated":1543626724,"created_by":{"id":0,"name":"","username":"","email":"","created":0,"updated":0}},{"id":3,"title":"task #3 high prio","description":"","done":false,"due_date":0,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":0,"end_date":0,"assignees":null,"labels":null,"created":1543626724,"updated":1543626724,"created_by":{"id":0,"name":"","username":"","email":"","created":0,"updated":0}}]`)
assert.NotContains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"due_date":1543636724,"reminders":null,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":0,"end_date":0,"assignees":null,"labels":null,"hex_color":"","created":1543626724,"updated":1543626724,"created_by":{"id":0,"name":"","username":"","email":"","created":0,"updated":0}},{"id":6,"title":"task #6 lower due date"`)
@@ -188,7 +190,7 @@ func TestTaskCollection(t *testing.T) {
},
urlParams,
)
assert.NoError(t, err)
require.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 `)
@@ -213,7 +215,7 @@ func TestTaskCollection(t *testing.T) {
},
urlParams,
)
assert.NoError(t, err)
require.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 `)
@@ -238,7 +240,7 @@ func TestTaskCollection(t *testing.T) {
},
urlParams,
)
assert.NoError(t, err)
require.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.
@@ -253,7 +255,7 @@ func TestTaskCollection(t *testing.T) {
},
urlParams,
)
assert.NoError(t, err)
require.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 `)
@@ -279,7 +281,7 @@ func TestTaskCollection(t *testing.T) {
},
nil,
)
assert.Error(t, err)
require.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeInvalidTaskFilterValue)
})
})
@@ -289,7 +291,7 @@ func TestTaskCollection(t *testing.T) {
nil,
map[string]string{"project": "-2"}, // Actually a saved filter - contains the same filter arguments as the start and end date filter from above
)
assert.NoError(t, err)
require.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 `)
@@ -311,7 +313,7 @@ func TestTaskCollection(t *testing.T) {
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)
require.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 `)
@@ -345,7 +347,7 @@ func TestTaskCollection(t *testing.T) {
})
t.Run("Search", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"s": []string{"task #6"}}, nil)
assert.NoError(t, err)
require.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 `)
@@ -365,39 +367,39 @@ func TestTaskCollection(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)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":35,"title":"task #35","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":21,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":[{"id":2,"name":"","username":"user2","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}],"labels":[{"id":4,"title":"Label #4 - visible via other task","description":"","hex_color":"","created_by":{"id":2,"name":"","username":"user2","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"},"created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}],"hex_color":"","percent_done":0,"identifier":"test21-1","index":1,"related_tasks":{"related":[{"id":1,"title":"task #1","description":"Lorem Ipsum","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"","index":1,"related_tasks":null,"attachments":null,"cover_image_attachment_id":0,"is_favorite":true,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":2,"kanban_position":0,"created_by":null},{"id":1,"title":"task #1","description":"Lorem Ipsum","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"","index":1,"related_tasks":null,"attachments":null,"cover_image_attachment_id":0,"is_favorite":true,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":2,"kanban_position":0,"created_by":null}]},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":19,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`)
})
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)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":3,"title":"task #3 high prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":4,"title":"task #4 low prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":1`)
})
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)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":35,"title":"task #35","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":21,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":[{"id":2,"name":"","username":"user2","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}],"labels":[{"id":4,"title":"Label #4 - visible via other task","description":"","hex_color":"","created_by":{"id":2,"name":"","username":"user2","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"},"created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}],"hex_color":"","percent_done":0,"identifier":"test21-1","index":1,"related_tasks":{"related":[{"id":1,"title":"task #1","description":"Lorem Ipsum","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"","index":1,"related_tasks":null,"attachments":null,"cover_image_attachment_id":0,"is_favorite":true,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":2,"kanban_position":0,"created_by":null},{"id":1,"title":"task #1","description":"Lorem Ipsum","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"","index":1,"related_tasks":null,"attachments":null,"cover_image_attachment_id":0,"is_favorite":true,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":2,"kanban_position":0,"created_by":null}]},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":19,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`)
})
// should equal duedate asc
t.Run("by due_date", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}}, nil)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
})
t.Run("by duedate desc", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"desc"}}, nil)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":6,"title":"task #6 lower due date`)
})
t.Run("by duedate asc", func(t *testing.T) {
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"asc"}}, nil)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminders":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
})
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)
require.NoError(t, err)
assert.NotContains(t, rec.Body.String(), `[{"id":3,"title":"task #3 high prio","description":"","done":false,"due_date":0,"reminders":null,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":0,"end_date":0,"assignees":null,"labels":null,"hex_color":"","created":1543626724,"updated":1543626724,"created_by":{"id":0,"name":"","username":"","email":"","created":0,"updated":0}},{"id":4,"title":"task #4 low prio","description":"","done":false,"due_date":0,"repeat_after":0,"repeat_mode":0,"priority":1`)
assert.NotContains(t, rec.Body.String(), `{"id":4,"title":"task #4 low prio","description":"","done":false,"due_date":0,"reminders":null,"repeat_after":0,"repeat_mode":0,"priority":1,"start_date":0,"end_date":0,"assignees":null,"labels":null,"hex_color":"","created":1543626724,"updated":1543626724,"created_by":{"id":0,"name":"","username":"","email":"","created":0,"updated":0}},{"id":3,"title":"task #3 high prio","description":"","done":false,"due_date":0,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":0,"end_date":0,"assignees":null,"labels":null,"created":1543626724,"updated":1543626724,"created_by":{"id":0,"name":"","username":"","email":"","created":0,"updated":0}}]`)
assert.NotContains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"due_date":1543636724,"reminders":null,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":0,"end_date":0,"assignees":null,"labels":null,"hex_color":"","created":1543626724,"updated":1543626724,"created_by":{"id":0,"name":"","username":"","email":"","created":0,"updated":0}},{"id":6,"title":"task #6 lower due date"`)
@@ -415,7 +417,7 @@ func TestTaskCollection(t *testing.T) {
},
nil,
)
assert.NoError(t, err)
require.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 `)
@@ -440,7 +442,7 @@ func TestTaskCollection(t *testing.T) {
},
nil,
)
assert.NoError(t, err)
require.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 `)
@@ -465,7 +467,7 @@ func TestTaskCollection(t *testing.T) {
},
nil,
)
assert.NoError(t, err)
require.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.
@@ -481,7 +483,7 @@ func TestTaskCollection(t *testing.T) {
},
nil,
)
assert.Error(t, err)
require.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeInvalidTaskFilterValue)
})
})
+49 -48
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@@ -20,11 +20,12 @@ import (
"testing"
"code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/web/handler"
"github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestTaskComments(t *testing.T) {
@@ -54,82 +55,82 @@ func TestTaskComments(t *testing.T) {
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)
require.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)
require.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeTaskDoesNotExist)
})
t.Run("Rights check", func(t *testing.T) {
// Only the own comments can be updated
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)
require.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)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Team write", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "16", "commentid": "4"}, `{"comment":"Lorem Ipsum"}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Team admin", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "17", "commentid": "5"}, `{"comment":"Lorem Ipsum"}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
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)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via User write", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "19", "commentid": "7"}, `{"comment":"Lorem Ipsum"}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via User admin", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "20", "commentid": "8"}, `{"comment":"Lorem Ipsum"}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project Team readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "21", "commentid": "9"}, `{"comment":"Lorem Ipsum"}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project Team write", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "22", "commentid": "10"}, `{"comment":"Lorem Ipsum"}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project Team admin", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "23", "commentid": "11"}, `{"comment":"Lorem Ipsum"}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project User readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "24", "commentid": "12"}, `{"comment":"Lorem Ipsum"}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project User write", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "25", "commentid": "13"}, `{"comment":"Lorem Ipsum"}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project User admin", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"task": "26", "commentid": "14"}, `{"comment":"Lorem Ipsum"}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
})
@@ -137,82 +138,82 @@ func TestTaskComments(t *testing.T) {
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)
require.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)
require.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeTaskDoesNotExist)
})
t.Run("Rights check", func(t *testing.T) {
// Only the own comments can be deleted
t.Run("Forbidden", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "14", "commentid": "2"})
assert.Error(t, err)
require.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)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Team write", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "16", "commentid": "4"})
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Team admin", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "17", "commentid": "5"})
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
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)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via User write", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "19", "commentid": "7"})
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via User admin", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "20", "commentid": "8"})
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project Team readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "21", "commentid": "9"})
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project Team write", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "22", "commentid": "10"})
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project Team admin", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "23", "commentid": "11"})
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project User readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "24", "commentid": "12"})
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project User write", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "25", "commentid": "13"})
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project User admin", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"task": "26", "commentid": "14"})
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
})
@@ -220,88 +221,88 @@ func TestTaskComments(t *testing.T) {
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)
require.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)
require.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)
require.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)
require.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)
require.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)
require.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)
require.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)
require.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)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"comment":"Lorem Ipsum"`)
})
t.Run("Shared Via Parent Project Team readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "21"}, `{"comment":"Lorem Ipsum"}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project Team write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "22"}, `{"comment":"Lorem Ipsum"}`)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"comment":"Lorem Ipsum"`)
})
t.Run("Shared Via Parent Project Team admin", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "23"}, `{"comment":"Lorem Ipsum"}`)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"comment":"Lorem Ipsum"`)
})
t.Run("Shared Via Parent Project User readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "24"}, `{"comment":"Lorem Ipsum"}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project User write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "25"}, `{"comment":"Lorem Ipsum"}`)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"comment":"Lorem Ipsum"`)
})
t.Run("Shared Via Parent Project User admin", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"task": "26"}, `{"comment":"Lorem Ipsum"}`)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"comment":"Lorem Ipsum"`)
})
})
t.Run("Link Share", func(t *testing.T) {
rec, err := testHandlerLinkShareWrite.testCreateWithLinkShare(nil, map[string]string{"task": "13"}, `{"comment":"Lorem Ipsum"}`)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"comment":"Lorem Ipsum"`)
db.AssertExists(t, "task_comments", map[string]interface{}{
"task_id": 13,
+83 -82
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@@ -20,11 +20,12 @@ import (
"testing"
"code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/web/handler"
"github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestTask(t *testing.T) {
@@ -55,49 +56,49 @@ func TestTask(t *testing.T) {
t.Run("Update task items", func(t *testing.T) {
t.Run("Title", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
require.NoError(t, err)
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.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"description":"Dolor sit amet"}`)
assert.NoError(t, err)
require.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.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"description":""}`)
assert.NoError(t, err)
require.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.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"done":true}`)
assert.NoError(t, err)
require.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.testUpdateWithUser(nil, map[string]string{"projecttask": "2"}, `{"done":false}`)
assert.NoError(t, err)
require.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.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"due_date": "2020-02-10T10:00:00Z"}`)
assert.NoError(t, err)
require.NoError(t, err)
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.testUpdateWithUser(nil, map[string]string{"projecttask": "5"}, `{"due_date": null}`)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"due_date":"0001-01-01T00:00:00Z"`)
assert.NotContains(t, rec.Body.String(), `"due_date":"2020-02-10T10:00:00Z"`)
})
t.Run("Reminders", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"reminders": [{"reminder": "2020-02-10T10:00:00Z"},{"reminder": "2020-02-11T10:00:00Z"}]}`)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"reminders":[`)
assert.Contains(t, rec.Body.String(), `{"reminder":"2020-02-10T10:00:00Z"`)
assert.Contains(t, rec.Body.String(), `{"reminder":"2020-02-11T10:00:00Z"`)
@@ -105,109 +106,109 @@ func TestTask(t *testing.T) {
})
t.Run("Reminders unset to empty array", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "27"}, `{"reminders": []}`)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"reminders":null`)
assert.NotContains(t, rec.Body.String(), `{"Reminder":"2020-02-10T10:00:00Z"`)
})
t.Run("Reminders unset to null", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "27"}, `{"reminders": null}`)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"reminders":null`)
assert.NotContains(t, rec.Body.String(), `{"Reminder":"2020-02-10T10:00:00Z"`)
})
t.Run("Repeat after", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"repeat_after":3600}`)
assert.NoError(t, err)
require.NoError(t, err)
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.testUpdateWithUser(nil, map[string]string{"projecttask": "28"}, `{"repeat_after":0}`)
assert.NoError(t, err)
require.NoError(t, err)
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.testUpdateWithUser(nil, map[string]string{"projecttask": "28"}, `{"done":true}`)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"done":false`)
assert.NotContains(t, rec.Body.String(), `"done":true`)
})
t.Run("Assignees", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"assignees":[{"id":1}]}`)
assert.NoError(t, err)
require.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.testUpdateWithUser(nil, map[string]string{"projecttask": "30"}, `{"assignees":[]}`)
assert.NoError(t, err)
require.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.testUpdateWithUser(nil, map[string]string{"projecttask": "30"}, `{"assignees":null}`)
assert.NoError(t, err)
require.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.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"priority":100}`)
assert.NoError(t, err)
require.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.testUpdateWithUser(nil, map[string]string{"projecttask": "3"}, `{"priority":0}`)
assert.NoError(t, err)
require.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.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"start_date":"2020-02-10T10:00:00Z"}`)
assert.NoError(t, err)
require.NoError(t, err)
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.testUpdateWithUser(nil, map[string]string{"projecttask": "7"}, `{"start_date":"0001-01-01T00:00:00Z"}`)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"start_date":"0001-01-01T00:00:00Z"`)
assert.NotContains(t, rec.Body.String(), `"start_date":"2020-02-10T10:00:00Z"`)
})
t.Run("End date", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"end_date":"2020-02-10T12:00:00Z"}`)
assert.NoError(t, err)
require.NoError(t, err)
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.testUpdateWithUser(nil, map[string]string{"projecttask": "8"}, `{"end_date":"0001-01-01T00:00:00Z"}`)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"end_date":"0001-01-01T00:00:00Z"`)
assert.NotContains(t, rec.Body.String(), `"end_date":"2020-02-10T10:00:00Z"`)
})
t.Run("Color", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"hex_color":"f0f0f0"}`)
assert.NoError(t, err)
require.NoError(t, err)
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.testUpdateWithUser(nil, map[string]string{"projecttask": "31"}, `{"hex_color":""}`)
assert.NoError(t, err)
require.NoError(t, err)
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{"projecttask": "1"}, `{"percent_done":0.1}`)
assert.NoError(t, err)
require.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{"projecttask": "33"}, `{"percent_done":0}`)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"percent_done":0,`)
assert.NotContains(t, rec.Body.String(), `"percent_done":0.1`)
})
@@ -215,112 +216,112 @@ func TestTask(t *testing.T) {
t.Run("Nonexisting", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "99999"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err)
require.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{"projecttask": "14"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err)
require.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{"projecttask": "15"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err)
require.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{"projecttask": "16"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
require.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.testUpdateWithUser(nil, map[string]string{"projecttask": "17"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
t.Run("Shared Via User readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "18"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err)
require.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{"projecttask": "19"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
require.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.testUpdateWithUser(nil, map[string]string{"projecttask": "20"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
t.Run("Shared Via Parent Project Team readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "21"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project Team write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "22"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
t.Run("Shared Via Parent Project Team admin", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "23"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
t.Run("Shared Via Parent Project User readonly", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "24"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project User write", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "25"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
t.Run("Shared Via Parent Project User admin", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "26"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
})
t.Run("Move to other project", func(t *testing.T) {
t.Run("normal", func(t *testing.T) {
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"project_id":7}`)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"project_id":7`)
assert.NotContains(t, rec.Body.String(), `"project_id":1`)
})
t.Run("Forbidden", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"project_id":20}`)
assert.Error(t, err)
require.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrorCodeGenericForbidden)
})
t.Run("Read Only", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"project_id":6}`)
assert.Error(t, err)
require.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{"projecttask": "1"}, `{"bucket_id":3}`)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"bucket_id":3`)
assert.NotContains(t, rec.Body.String(), `"bucket_id":1`)
})
t.Run("Different Project", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"bucket_id":4}`)
assert.Error(t, err)
require.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeBucketDoesNotBelongToProject)
})
t.Run("Nonexisting Bucket", func(t *testing.T) {
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"bucket_id":9999}`)
assert.Error(t, err)
require.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeBucketDoesNotExist)
})
})
@@ -328,81 +329,81 @@ func TestTask(t *testing.T) {
t.Run("Delete", func(t *testing.T) {
t.Run("Normal", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "1"})
assert.NoError(t, err)
require.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{"projecttask": "99999"})
assert.Error(t, err)
require.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{"projecttask": "14"})
assert.Error(t, err)
require.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{"projecttask": "15"})
assert.Error(t, err)
require.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{"projecttask": "16"})
assert.NoError(t, err)
require.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{"projecttask": "17"})
assert.NoError(t, err)
require.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{"projecttask": "18"})
assert.Error(t, err)
require.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{"projecttask": "19"})
assert.NoError(t, err)
require.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{"projecttask": "20"})
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
})
t.Run("Shared Via Parent Project Team readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "21"})
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project Team write", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "22"})
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
})
t.Run("Shared Via Parent Project Team admin", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "23"})
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
})
t.Run("Shared Via Parent Project User readonly", func(t *testing.T) {
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "24"})
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project User write", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "25"})
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
})
t.Run("Shared Via Parent Project User admin", func(t *testing.T) {
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "26"})
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
})
})
@@ -410,106 +411,106 @@ func TestTask(t *testing.T) {
t.Run("Create", func(t *testing.T) {
t.Run("Normal", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "1"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
require.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{"project": "9999"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err)
require.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeProjectDoesNotExist)
})
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{"project": "20"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err)
require.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{"project": "6"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err)
require.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{"project": "7"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
require.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{"project": "8"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
require.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{"project": "9"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err)
require.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{"project": "10"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
require.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{"project": "11"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
t.Run("Shared Via Parent Project Team readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "12"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project Team write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "13"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
t.Run("Shared Via Parent Project Team admin", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "14"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
t.Run("Shared Via Parent Project User readonly", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "15"}, `{"title":"Lorem Ipsum"}`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
})
t.Run("Shared Via Parent Project User write", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "16"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
})
t.Run("Shared Via Parent Project User admin", func(t *testing.T) {
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "17"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
require.NoError(t, err)
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{"project": "1"}, `{"title":"Lorem Ipsum","bucket_id":3}`)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"bucket_id":3`)
assert.NotContains(t, rec.Body.String(), `"bucket_id":1`)
})
t.Run("Different Project", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "1"}, `{"title":"Lorem Ipsum","bucket_id":4}`)
assert.Error(t, err)
require.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeBucketDoesNotBelongToProject)
})
t.Run("Nonexisting Bucket", func(t *testing.T) {
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "1"}, `{"title":"Lorem Ipsum","bucket_id":9999}`)
assert.Error(t, err)
require.Error(t, err)
assertHandlerErrorCode(t, err, models.ErrCodeBucketDoesNotExist)
})
})
t.Run("Link Share", func(t *testing.T) {
rec, err := testHandlerLinkShareWrite.testCreateWithLinkShare(nil, map[string]string{"project": "2"}, `{"title":"Lorem Ipsum"}`)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
db.AssertExists(t, "tasks", map[string]interface{}{
"project_id": 2,
+3 -2
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@@ -22,12 +22,13 @@ import (
apiv1 "code.vikunja.io/api/pkg/routes/api/v1"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestCheckToken(t *testing.T) {
t.Run("Normal test", func(t *testing.T) {
rec, err := newTestRequestWithUser(t, http.MethodPost, apiv1.CheckToken, &testuser1, "", nil, nil)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `🍵`)
})
}
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@@ -22,7 +22,9 @@ import (
apiv1 "code.vikunja.io/api/pkg/routes/api/v1"
"code.vikunja.io/api/pkg/user"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestUserChangePassword(t *testing.T) {
@@ -31,7 +33,7 @@ func TestUserChangePassword(t *testing.T) {
"new_password": "12345",
"old_password": "1234"
}`, nil, nil)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `The password was updated successfully.`)
})
t.Run("Wrong old password", func(t *testing.T) {
@@ -39,7 +41,7 @@ func TestUserChangePassword(t *testing.T) {
"new_password": "12345",
"old_password": "invalid"
}`, nil, nil)
assert.Error(t, err)
require.Error(t, err)
assertHandlerErrorCode(t, err, user.ErrCodeWrongUsernameOrPassword)
})
t.Run("Empty old password", func(t *testing.T) {
@@ -47,7 +49,7 @@ func TestUserChangePassword(t *testing.T) {
"new_password": "12345",
"old_password": ""
}`, nil, nil)
assert.Error(t, err)
require.Error(t, err)
assertHandlerErrorCode(t, err, user.ErrCodeEmptyOldPassword)
})
t.Run("Empty new password", func(t *testing.T) {
@@ -55,7 +57,7 @@ func TestUserChangePassword(t *testing.T) {
"new_password": "",
"old_password": "1234"
}`, nil, nil)
assert.Error(t, err)
require.Error(t, err)
assertHandlerErrorCode(t, err, user.ErrCodeEmptyNewPassword)
})
}
+7 -5
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@@ -22,30 +22,32 @@ import (
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"
"github.com/stretchr/testify/require"
)
func TestUserConfirmEmail(t *testing.T) {
t.Run("Normal test", func(t *testing.T) {
rec, err := newTestRequest(t, http.MethodPost, apiv1.UserConfirmEmail, `{"token": "tiepiQueed8ahc7zeeFe1eveiy4Ein8osooxegiephauph2Ael"}`, nil, nil)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `The email was confirmed successfully.`)
})
t.Run("Empty payload", func(t *testing.T) {
_, err := newTestRequest(t, http.MethodPost, apiv1.UserConfirmEmail, `{}`, nil, nil)
assert.Error(t, err)
require.Error(t, err)
assert.Equal(t, http.StatusPreconditionFailed, err.(*echo.HTTPError).Code)
assertHandlerErrorCode(t, err, user.ErrCodeInvalidEmailConfirmToken)
})
t.Run("Empty token", func(t *testing.T) {
_, err := newTestRequest(t, http.MethodPost, apiv1.UserConfirmEmail, `{"token": ""}`, nil, nil)
assert.Error(t, err)
require.Error(t, err)
assertHandlerErrorCode(t, err, user.ErrCodeInvalidEmailConfirmToken)
})
t.Run("Invalid token", func(t *testing.T) {
_, err := newTestRequest(t, http.MethodPost, apiv1.UserConfirmEmail, `{"token": "invalidToken"}`, nil, nil)
assert.Error(t, err)
require.Error(t, err)
assertHandlerErrorCode(t, err, user.ErrCodeInvalidEmailConfirmToken)
})
}
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@@ -24,27 +24,28 @@ import (
"code.vikunja.io/api/pkg/user"
"github.com/labstack/echo/v4"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestUserRequestResetPasswordToken(t *testing.T) {
t.Run("Normal requesting a password reset token", func(t *testing.T) {
rec, err := newTestRequest(t, http.MethodPost, apiv1.UserRequestResetPasswordToken, `{"email": "user1@example.com"}`, nil, nil)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `Token was sent.`)
})
t.Run("Empty payload", func(t *testing.T) {
_, err := newTestRequest(t, http.MethodPost, apiv1.UserRequestResetPasswordToken, `{}`, nil, nil)
assert.Error(t, err)
require.Error(t, err)
assertHandlerErrorCode(t, err, user.ErrCodeNoUsernamePassword)
})
t.Run("Invalid email address", func(t *testing.T) {
_, err := newTestRequest(t, http.MethodPost, apiv1.UserRequestResetPasswordToken, `{"email": "user1example.com"}`, nil, nil)
assert.Error(t, err)
require.Error(t, err)
assert.Equal(t, http.StatusBadRequest, err.(*echo.HTTPError).Code)
})
t.Run("No user with that email address", func(t *testing.T) {
_, err := newTestRequest(t, http.MethodPost, apiv1.UserRequestResetPasswordToken, `{"email": "user1000@example.com"}`, nil, nil)
assert.Error(t, err)
require.Error(t, err)
assertHandlerErrorCode(t, err, user.ErrCodeUserDoesNotExist)
})
}
+7 -5
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@@ -22,8 +22,10 @@ import (
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"
"github.com/stretchr/testify/require"
)
func TestUserPasswordReset(t *testing.T) {
@@ -32,12 +34,12 @@ func TestUserPasswordReset(t *testing.T) {
"new_password": "1234",
"token": "passwordresettesttoken"
}`, nil, nil)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `The password was updated successfully.`)
})
t.Run("Empty payload", func(t *testing.T) {
_, err := newTestRequest(t, http.MethodPost, apiv1.UserResetPassword, `{}`, nil, nil)
assert.Error(t, err)
require.Error(t, err)
assert.Equal(t, http.StatusBadRequest, err.(*echo.HTTPError).Code)
})
t.Run("No new password", func(t *testing.T) {
@@ -45,7 +47,7 @@ func TestUserPasswordReset(t *testing.T) {
"new_password": "",
"token": "passwordresettesttoken"
}`, nil, nil)
assert.Error(t, err)
require.Error(t, err)
assertHandlerErrorCode(t, err, user.ErrCodeNoUsernamePassword)
})
t.Run("Invalid password reset token", func(t *testing.T) {
@@ -53,7 +55,7 @@ func TestUserPasswordReset(t *testing.T) {
"new_password": "1234",
"token": "invalidtoken"
}`, nil, nil)
assert.Error(t, err)
require.Error(t, err)
assertHandlerErrorCode(t, err, user.ErrCodeInvalidPasswordResetToken)
})
}
+5 -3
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@@ -21,18 +21,20 @@ import (
"testing"
apiv1 "code.vikunja.io/api/pkg/routes/api/v1"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestUserProject(t *testing.T) {
t.Run("Normal test", func(t *testing.T) {
rec, err := newTestRequestWithUser(t, http.MethodPost, apiv1.UserList, &testuser1, "", nil, nil)
assert.NoError(t, err)
require.NoError(t, err)
assert.Equal(t, "null\n", rec.Body.String())
})
t.Run("Search for user3", func(t *testing.T) {
rec, err := newTestRequestWithUser(t, http.MethodPost, apiv1.UserList, &testuser1, "", map[string][]string{"s": {"user3"}}, nil)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `user3`)
assert.NotContains(t, rec.Body.String(), `user1`)
assert.NotContains(t, rec.Body.String(), `user2`)
+4 -2
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@@ -21,13 +21,15 @@ import (
"testing"
apiv1 "code.vikunja.io/api/pkg/routes/api/v1"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestUserShow(t *testing.T) {
t.Run("Normal test", func(t *testing.T) {
rec, err := newTestRequestWithUser(t, http.MethodPost, apiv1.UserShow, &testuser1, "", nil, nil)
assert.NoError(t, err)
require.NoError(t, err)
assert.Contains(t, rec.Body.String(), `"id":1`)
assert.Contains(t, rec.Body.String(), `"username":"user1"`)
assert.NotContains(t, rec.Body.String(), `"email":""`)
+35 -31
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@@ -22,9 +22,7 @@ import (
"strings"
"time"
"code.vikunja.io/api/pkg/config"
"github.com/op/go-logging"
"github.com/spf13/viper"
)
// ErrFmt holds the format for all the console logging
@@ -41,42 +39,45 @@ const logModule = `vikunja`
// loginstance is the instance of the logger which is used under the hood to log
var logInstance = logging.MustGetLogger(logModule)
// logpath is the path in which log files will be written.
// This value is a mere fallback for other modules that could but shouldn't be used before calling ConfigLogger
var logPath = "."
// InitLogger initializes the global log handler
func InitLogger() {
if !config.LogEnabled.GetBool() {
// Disable all logging when loggin in general is disabled, overwriting everything a user might have set.
config.LogStandard.Set("off")
config.LogDatabase.Set("off")
config.LogHTTP.Set("off")
config.LogEcho.Set("off")
config.LogEvents.Set("off")
return
}
// 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) {
Fatalf("Could not create log folder: %s", err.Error())
}
// Init with stdout and INFO as default format and level
logBackend := logging.NewLogBackend(os.Stdout, "", 0)
backend := logging.NewBackendFormatter(logBackend, logging.MustStringFormatter(Fmt+"\n"))
backendLeveled := logging.AddModuleLevel(backend)
backendLeveled.SetLevel(logging.INFO, logModule)
logInstance.SetBackend(backendLeveled)
}
// ConfigLogger configures the global log handler
func ConfigLogger(configLogEnabled bool, configLogStandard string, configLogPath string, configLogLevel string) {
lvl := strings.ToUpper(configLogLevel)
level, err := logging.LogLevel(lvl)
if err != nil {
Fatalf("Error setting standard log level %s: %s", lvl, err.Error())
}
logPath = configLogPath
// The backend is the part which actually handles logging the log entries somewhere.
cf := config.LogStandard.GetString()
var backend logging.Backend
backend = &NoopBackend{}
if cf != "off" && cf != "false" {
stdWriter := GetLogWriter("standard")
logBackend := logging.NewLogBackend(stdWriter, "", 0)
backend = logging.NewBackendFormatter(logBackend, logging.MustStringFormatter(Fmt+"\n"))
if configLogStandard == "false" {
configLogStandard = "off"
Warning("log.standard value 'false' is deprecated and will be removed in a future release. Please use the value 'off'.")
}
level, err := logging.LogLevel(strings.ToUpper(config.LogLevel.GetString()))
if err != nil {
Fatalf("Error setting database log level: %s", err.Error())
if configLogEnabled && configLogStandard != "off" {
logBackend := logging.NewLogBackend(GetLogWriter(configLogStandard, "standard"), "", 0)
backend = logging.NewBackendFormatter(logBackend, logging.MustStringFormatter(Fmt+"\n"))
}
backendLeveled := logging.AddModuleLevel(backend)
@@ -86,11 +87,14 @@ func InitLogger() {
}
// GetLogWriter returns the writer to where the normal log goes, depending on the config
func GetLogWriter(logfile string) (writer io.Writer) {
func GetLogWriter(logfmt string, logfile string) (writer io.Writer) {
writer = os.Stdout // Set the default case to prevent nil pointer panics
switch viper.GetString("log." + logfile) {
switch logfmt {
case "file":
fullLogFilePath := config.LogPath.GetString() + "/" + logfile + ".log"
if err := os.MkdirAll(logPath, 0744); err != nil {
Fatalf("Could not create log path: %s", err.Error())
}
fullLogFilePath := logPath + "/" + logfile + ".log"
f, err := os.OpenFile(fullLogFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0600)
if err != nil {
Fatalf("Could not create logfile %s: %s", fullLogFilePath, err.Error())
+11 -7
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@@ -20,7 +20,6 @@ import (
"strings"
"time"
"code.vikunja.io/api/pkg/config"
"github.com/op/go-logging"
"xorm.io/xorm/log"
)
@@ -33,19 +32,24 @@ type MailLogger struct {
const mailFormat = `%{color}%{time:` + time.RFC3339Nano + `}: %{level}` + "\t" + `▶ [MAIL] %{id:03x}%{color:reset} %{message}`
const mailLogModule = `vikunja_mail`
func NewMailLogger() *MailLogger {
lvl := strings.ToUpper(config.LogMailLevel.GetString())
// NewMailLogger creates and initializes a new mail logger
func NewMailLogger(configLogEnabled bool, configLogMail string, configLogMailLevel string) *MailLogger {
lvl := strings.ToUpper(configLogMailLevel)
level, err := logging.LogLevel(lvl)
if err != nil {
Criticalf("Error setting database log level: %s", err.Error())
Criticalf("Error setting mail log level %s: %s", lvl, err.Error())
}
mailLogger := &MailLogger{
logger: logging.MustGetLogger(mailLogModule),
}
logBackend := logging.NewLogBackend(GetLogWriter("mail"), "", 0)
backend := logging.NewBackendFormatter(logBackend, logging.MustStringFormatter(mailFormat+"\n"))
var backend logging.Backend
backend = &NoopBackend{}
if configLogEnabled && configLogMail != "off" {
logBackend := logging.NewLogBackend(GetLogWriter(configLogMail, "mail"), "", 0)
backend = logging.NewBackendFormatter(logBackend, logging.MustStringFormatter(mailFormat+"\n"))
}
backendLeveled := logging.AddModuleLevel(backend)
backendLeveled.SetLevel(level, mailLogModule)
+1 -1
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
+10 -7
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@@ -21,7 +21,6 @@ import (
"strings"
"time"
"code.vikunja.io/api/pkg/config"
"github.com/ThreeDotsLabs/watermill"
"github.com/op/go-logging"
)
@@ -34,8 +33,9 @@ type WatermillLogger struct {
logger *logging.Logger
}
func NewWatermillLogger() *WatermillLogger {
lvl := strings.ToUpper(config.LogEventsLevel.GetString())
// NewXormLogger creates and initializes a new watermill logger
func NewWatermillLogger(configLogEnabled bool, configLogEvents string, configLogEventsLevel string) *WatermillLogger {
lvl := strings.ToUpper(configLogEventsLevel)
level, err := logging.LogLevel(lvl)
if err != nil {
Criticalf("Error setting events log level %s: %s", lvl, err.Error())
@@ -45,11 +45,14 @@ func NewWatermillLogger() *WatermillLogger {
logger: logging.MustGetLogger(watermillLogModule),
}
cf := config.LogEvents.GetString()
var backend logging.Backend
backend = &NoopBackend{}
if cf != "off" && cf != "false" {
logBackend := logging.NewLogBackend(GetLogWriter("events"), "", 0)
if configLogEvents == "false" {
configLogEvents = "off"
Warning("log.events value 'false' is deprecated and will be removed in a future release. Please use the value 'off'.")
}
if configLogEnabled && configLogEvents != "off" {
logBackend := logging.NewLogBackend(GetLogWriter(configLogEvents, "events"), "", 0)
backend = logging.NewBackendFormatter(logBackend, logging.MustStringFormatter(watermillFmt+"\n"))
}
+11 -10
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@@ -20,7 +20,6 @@ import (
"strings"
"time"
"code.vikunja.io/api/pkg/config"
"github.com/op/go-logging"
"xorm.io/xorm/log"
)
@@ -38,21 +37,23 @@ type XormLogger struct {
}
// NewXormLogger creates and initializes a new xorm logger
func NewXormLogger(lvl string) *XormLogger {
if lvl == "" {
lvl = strings.ToUpper(config.LogDatabaseLevel.GetString())
}
func NewXormLogger(configLogEnabled bool, configLogDatabase string, configLogDatabaseLevel string) *XormLogger {
lvl := strings.ToUpper(configLogDatabaseLevel)
level, err := logging.LogLevel(lvl)
if err != nil {
Criticalf("Error setting database log level: %s", err.Error())
Criticalf("Error setting database log level %s: %s", lvl, err.Error())
}
xormLogger := &XormLogger{
logger: logging.MustGetLogger(xormLogModule),
}
logBackend := logging.NewLogBackend(GetLogWriter("database"), "", 0)
backend := logging.NewBackendFormatter(logBackend, logging.MustStringFormatter(XormFmt+"\n"))
var backend logging.Backend
backend = &NoopBackend{}
if configLogEnabled && configLogDatabase != "off" {
logBackend := logging.NewLogBackend(GetLogWriter(configLogDatabase, "database"), "", 0)
backend = logging.NewBackendFormatter(logBackend, logging.MustStringFormatter(XormFmt+"\n"))
}
backendLeveled := logging.AddModuleLevel(backend)
backendLeveled.SetLevel(level, xormLogModule)
@@ -64,8 +65,8 @@ func NewXormLogger(lvl string) *XormLogger {
case logging.ERROR:
xormLogger.level = log.LOG_ERR
case logging.WARNING:
case logging.NOTICE:
xormLogger.level = log.LOG_WARNING
case logging.NOTICE:
case logging.INFO:
xormLogger.level = log.LOG_INFO
case logging.DEBUG:
+2 -2
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
@@ -56,7 +56,7 @@ func getClient() (*mail.Client, error) {
ServerName: config.MailerHost.GetString(),
}),
mail.WithTimeout((config.MailerQueueTimeout.GetDuration() + 3) * time.Second), // 3s more for us to close before mail server timeout
mail.WithLogger(log.NewMailLogger()),
mail.WithLogger(log.NewMailLogger(config.LogEnabled.GetBool(), config.LogMail.GetString(), config.LogMailLevel.GetString())),
mail.WithDebugLog(),
}
+1 -1
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by
+1 -1
View File
@@ -1,5 +1,5 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
// Copyright 2018-present Vikunja and contributors. All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public Licensee as published by

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