Compare commits
131 Commits
7f82c5e51a
...
4323803fd6
Author | SHA1 | Date | |
---|---|---|---|
4323803fd6 | |||
903b8ff438 | |||
b1fd13bbcb | |||
878d19beb8 | |||
96ed1e33e3 | |||
374a0f9ce3 | |||
580bd5aeaa | |||
c359d6a97d | |||
038702a2a0 | |||
6426d40825 | |||
bbe102dd57 | |||
65484bc432 | |||
54f6cc7a64 | |||
f1b2338227 | |||
45defebcf4 | |||
86ee8273bc | |||
3adfeb3b34 | |||
|
9bb8a26706 | ||
54b7f7127c | |||
25609db567 | |||
2e3603507c | |||
2efc1b5a87 | |||
|
090c67138a | ||
d8f387f796 | |||
aaeffe925e | |||
f814dd03eb | |||
2369ce5554 | |||
c19479757a | |||
8fddbf43ba | |||
beb4d07cf9 | |||
10ded56f66 | |||
d709db4e18 | |||
0c8bed4054 | |||
9ddd7f4889 | |||
3047ccfd4a | |||
7f28865903 | |||
a273d1ae76 | |||
c9e044b3ad | |||
8bf0f8bb57 | |||
3ccc6365a6 | |||
8d10130d4c | |||
51314f269d | |||
9eefb2bea9 | |||
2e5c91efdf | |||
dbb0f54732 | |||
6e639d9ccb | |||
a9a8bd54ee | |||
d3a655c75b | |||
e0dc3807f6 | |||
4e7510995c | |||
f8300c9e1b | |||
ef3f07b677 | |||
ea66875310 | |||
850ac0c601 | |||
8ebb642d55 | |||
2a569488d7 | |||
49b3ae82e4 | |||
b71e6f8049 | |||
fa82c71f8c | |||
8f473481ac | |||
51cd2830dd | |||
430057a404 | |||
7ffe9b625e | |||
d47edac376 | |||
aed1ad6d96 | |||
84bcdbf937 | |||
280ac1164b | |||
b6d7323cdf | |||
59796fd490 | |||
26e2d0bdde | |||
251b877015 | |||
b460fa8c82 | |||
77fafd5dc3 | |||
3688bbde20 | |||
c51ee94ad1 | |||
8f27e7e619 | |||
382a7884be | |||
cd345b62c2 | |||
dc2285bcc9 | |||
1feb62cc45 | |||
117f6b38e1 | |||
dd461746a6 | |||
0f555b7ec7 | |||
f93b68819d | |||
79b31673e2 | |||
f8cc67d37f | |||
6c92859f8c | |||
ef6fe9500e | |||
8578f3a927 | |||
bfcebc63b7 | |||
8cafe84170 | |||
f3319e837a | |||
7c70b5d4b3 | |||
1eceecf3ab | |||
76fa841e9a | |||
2f601052fd | |||
8023674adf | |||
560fa187e0 | |||
a321c3cfb9 | |||
6e15d46a93 | |||
54348c5891 | |||
596d2bf676 | |||
ac92499b7d | |||
b1892eaf63 | |||
b9793a267b | |||
c906fc2b07 | |||
4bb77b5539 | |||
5743a4afe5 | |||
62325de9cd | |||
8759937e3c | |||
5cc4927b9e | |||
2b074c60a7 | |||
f5a4c136fb | |||
230478aae9 | |||
7e99618319 | |||
73c4c399e5 | |||
25ffa1bc2e | |||
4429ba2da1 | |||
db1ccff0de | |||
2c9ab3d86f | |||
951d74b272 | |||
a38efef734 | |||
a060cbe820 | |||
ad17ff5c32 | |||
d0e09d69d0 | |||
7a30294407 | |||
bc7f6a8586 | |||
f30a9d1038 | |||
c62e26b6fe | |||
f4f8450d16 | |||
30e0e98f77 |
6
.dockerignore
Normal file
6
.dockerignore
Normal file
|
@ -0,0 +1,6 @@
|
|||
files/
|
||||
Dockerfile
|
||||
docker-manifest.tmpl
|
||||
docker-manifest-unstable.tmpl
|
||||
*.db
|
||||
*.zip
|
18
.drone.yml
18
.drone.yml
|
@ -132,13 +132,14 @@ steps:
|
|||
event: [ push, tag, pull_request ]
|
||||
|
||||
- name: lint
|
||||
image: vikunja/golang-build:latest
|
||||
image: golang:1.17-alpine
|
||||
pull: true
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
depends_on: [ build ]
|
||||
commands:
|
||||
- wget -O - -q https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.45.2
|
||||
- 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.47.3
|
||||
- ./mage-static check:all
|
||||
when:
|
||||
event: [ push, tag, pull_request ]
|
||||
|
@ -152,7 +153,7 @@ steps:
|
|||
- unzip vikunja-latest.zip vikunja-unstable-linux-amd64
|
||||
|
||||
- name: test-migration-sqlite
|
||||
image: kolaente/toolbox:latest
|
||||
image: vikunja/golang-build:latest
|
||||
pull: true
|
||||
depends_on: [ test-migration-prepare, build ]
|
||||
environment:
|
||||
|
@ -171,7 +172,7 @@ steps:
|
|||
event: [ push, tag, pull_request ]
|
||||
|
||||
- name: test-migration-mysql
|
||||
image: kolaente/toolbox:latest
|
||||
image: vikunja/golang-build:latest
|
||||
pull: true
|
||||
depends_on: [ test-migration-prepare, build ]
|
||||
environment:
|
||||
|
@ -190,7 +191,7 @@ steps:
|
|||
event: [ push, tag, pull_request ]
|
||||
|
||||
- name: test-migration-psql
|
||||
image: kolaente/toolbox:latest
|
||||
image: vikunja/golang-build:latest
|
||||
pull: true
|
||||
depends_on: [ test-migration-prepare, build ]
|
||||
environment:
|
||||
|
@ -621,7 +622,7 @@ steps:
|
|||
- tar -xzf vikunja-theme.tar.gz
|
||||
|
||||
- name: build
|
||||
image: klakegg/hugo:0.93.3
|
||||
image: klakegg/hugo:0.101.0
|
||||
pull: true
|
||||
commands:
|
||||
- cd docs
|
||||
|
@ -662,6 +663,7 @@ steps:
|
|||
image: docker:git
|
||||
commands:
|
||||
- git fetch --tags
|
||||
|
||||
- name: docker-arm-unstable
|
||||
image: plugins/docker:linux-arm
|
||||
pull: true
|
||||
|
@ -672,6 +674,7 @@ steps:
|
|||
from_secret: docker_password
|
||||
repo: vikunja/api
|
||||
tags: unstable-linux-arm
|
||||
dockerfile: Dockerfile.arm32
|
||||
depends_on: [ fetch-tags ]
|
||||
when:
|
||||
ref:
|
||||
|
@ -688,6 +691,7 @@ steps:
|
|||
repo: vikunja/api
|
||||
auto_tag: true
|
||||
auto_tag_suffix: linux-arm
|
||||
dockerfile: Dockerfile.arm32
|
||||
depends_on: [ fetch-tags ]
|
||||
when:
|
||||
ref:
|
||||
|
@ -874,6 +878,6 @@ steps:
|
|||
- failure
|
||||
---
|
||||
kind: signature
|
||||
hmac: 1c4c211e66e4b6eddd2a1c1bad31e5c960d4f67d6033f4d5c4de7896dfae6c30
|
||||
hmac: d95e5d4b31e22079ce6360f31ec9257b26cd206b614cb1ec660290c061eced8f
|
||||
|
||||
...
|
||||
|
|
44
.gitea/issue_template.md
Normal file
44
.gitea/issue_template.md
Normal file
|
@ -0,0 +1,44 @@
|
|||
<!--
|
||||
|
||||
Please fill out this issue template to report a bug.
|
||||
If you want to propose a new feature, please open a discussion thread in the forum: https://community.vikunja.io
|
||||
|
||||
-->
|
||||
|
||||
**Version information:**
|
||||
|
||||
Frontend Version:
|
||||
API Version:
|
||||
Browser and OS Version:
|
||||
|
||||
**Steps to reproduce:**
|
||||
|
||||
<!--
|
||||
Add clear steps to reproduce the bug. Provide screenshots where applicable.
|
||||
-->
|
||||
|
||||
1.
|
||||
2.
|
||||
...
|
||||
|
||||
**Expected behavior:**
|
||||
|
||||
<!--
|
||||
Describe what happened.
|
||||
-->
|
||||
|
||||
|
||||
|
||||
**Actual behavior:**
|
||||
|
||||
<!--
|
||||
Describe what happened instead.
|
||||
-->
|
||||
|
||||
|
||||
|
||||
**Checklist:**
|
||||
|
||||
* [ ] I have provided all required information
|
||||
* [ ] I am using the latest release or the latest unstable build
|
||||
* [ ] I was able to reproduce the bug on [try](https://try.vikunja.io)
|
382
CHANGELOG.md
382
CHANGELOG.md
|
@ -7,6 +7,388 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|||
|
||||
All releases can be found on https://code.vikunja.io/api/releases.
|
||||
|
||||
## [0.19.2] - 2022-08-17
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Don't fail a migration if there is no filter saved ([10ded56](10ded56f6697ef47910ec68d37f26ed47cbe9180))
|
||||
* Don't override saved filters ([beb4d07](beb4d07cf95fc25f7cc5f7471b46bdab49f95fe0))
|
||||
|
||||
## [0.19.1] - 2022-08-17
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Prevent moving a list into a pseudo namespace ([3ccc636](3ccc6365a6892f37ee54b0750a34a61e52f6dba1))
|
||||
* Make sure generating blur hashes for bmp, tiff and webp images works ([8bf0f8b](8bf0f8bb571ddff69a7142be1acaa2e4e0c38e3b))
|
||||
* Add debian-based docker image for arm 32 builds ([c9e044b](c9e044b3ad60d25e9641d22d84571a7db83a26ac))
|
||||
* Only list all users when allowed ([9ddd7f4](9ddd7f48895f508539d591aeebde450a86987024))
|
||||
* Lint ([0c8bed4](0c8bed4054649de8510e5a636d1a14b65d52c402))
|
||||
|
||||
### Dependencies
|
||||
|
||||
* *(deps)* Update golang.org/x/sys digest to 6e608f9 (#1229)
|
||||
* *(deps)* Update golang.org/x/sync digest to 886fb93 (#1221)
|
||||
* *(deps)* Update golang.org/x/sys digest to 8e32c04 (#1230)
|
||||
* *(deps)* Update golang.org/x/term digest to a9ba230 (#1222)
|
||||
* *(deps)* Update module github.com/prometheus/client_golang to v1.13.0
|
||||
* *(deps)* Update module github.com/prometheus/client_golang to v1.13.0 (#1231)
|
||||
* *(deps)* Update golang.org/x/sys digest to 1c4a2a7
|
||||
* *(deps)* Update golang.org/x/oauth2 digest to 128564f (#1220)
|
||||
* *(deps)* Update golang.org/x/image digest to 062f8c9 (#1219)
|
||||
* *(deps)* Update golang.org/x/crypto digest to 630584e (#1218)
|
||||
* *(deps)* Update module github.com/labstack/echo/v4 to v4.8.0 (#1233)
|
||||
* *(deps)* Update golang.org/x/sys digest to fbc7d0a (#1234)
|
||||
* *(deps)* Update module github.com/wneessen/go-mail to v0.2.6 (#1235)
|
||||
* *(deps)* Update module github.com/mattn/go-sqlite3 to v1.14.15 (#1238)
|
||||
|
||||
### Features
|
||||
|
||||
* *(docs)* Add k8s docs* Add openid examples ([dbb0f54](dbb0f5473269fb29c4a484cd233a5b76484c4ca7))
|
||||
* Search by assignee username instead of id ([7f28865](7f28865903740d6dde15ee005323fbdee3072166))
|
||||
* Add migration to change user ids to usernames in saved filters ([3047ccf](3047ccfd4af8fee55d9ebff49138911ab80cb3d2))
|
||||
|
||||
## [0.19.0] - 2022-08-03
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* *(caldav)* Make sure the caldav tokens of non-local accounts are properly checked
|
||||
* *(caldav)* Properly parse durations when returning VTODOs
|
||||
* *(caldav)* Make sure description is parsed correctly when multiline
|
||||
* *(ci)* Sign drone config
|
||||
* *(ci)* Make sure the linter actually runs
|
||||
* *(ci)* Install git in lint step
|
||||
* *(docker)* Switch to debian base image
|
||||
* *(docker)* Use official go image instead of our own to build
|
||||
* *(docs)* Update minimum required go version
|
||||
* *(docs)* Use up-to-date hugo image for building
|
||||
* *(docs)* Don't use cannonify url
|
||||
* *(docs)* Image urls in synology setup explanation
|
||||
* *(docs)* Clarify frontend requirements to use Vikunja
|
||||
* *(dump)* Don't try to save a config file if none was provided and dump vikunja env variables
|
||||
* *(mage)* Handle different types of errors
|
||||
* *(mail)* Don't set a username by default
|
||||
* *(mail)* Don't try to authenticate against the mail server when no credentials are provided
|
||||
* *(mail)* Set server name in tls config so that sending mail works with skipTlsVerify set to false
|
||||
* *(restore)* Properly decode notifications json data
|
||||
* *(restore)* Make sure to reset sequences after importing a dump when using postgres
|
||||
* *(restore)* Use the correct initial migration* Generate swagger docs ([4de8ec5](4de8ec56a62caef22c2061376383de1fe53ca4c3))
|
||||
* Make sure the full task is available in notifications ([c2b6119](c2b6119434e6e806785d2c259c3ca3d25496ec75))
|
||||
* Don't try to load the namespace of a list if it is a shared list ([d7e47a2](d7e47a28d4bb04d4c7c3ed85a263134180da447a))
|
||||
* Correctly load and pass the user when deleting it ([50b65a5](50b65a517da6869dc6a48fec40323e254ba4c032))
|
||||
* Updating a list might remove its background ([cf05de1](cf05de19b317bd99c30de4c6a149a0d8a4ff4f49))
|
||||
* Sorting for saved filters ([57e5d10](57e5d10eee4c45a04e9e1aaeaf41dd44eb8ce788))
|
||||
* Importing trello attachments ([c3e0e64](c3e0e6405a634894a30dbf9c0506d1691ae4d443))
|
||||
* Lint ([0b77625](0b7762590f6a0a82090ef74e9e7e32b37142d343))
|
||||
* Deleting users with no namespaces ([f8a0a7e](f8a0a7e9539a44b2f790a08eb1b03028b56eaac3))
|
||||
* Importing tasks from todoist without a due time set ([fd0d462](fd0d462bf4dd8225c67ba34958e5148f6167d264))
|
||||
* User deletion never happens ([72d3c54](72d3c54efd3dda6ae846a069415688391cb1c9ae))
|
||||
* User deletion reminder emails counting up ([f581885](f581885e65ada15439ec02f1d18d825b03581523))
|
||||
* User not actually deleted ([70e005e](70e005e7ce5cf1dd25ec9ddfde3cfbbd258fadb6))
|
||||
* User deletion schedule ([5c88dfe](5c88dfe88eab442724f22c3b29741e78939deae2))
|
||||
* Friendly name not getting synced on first login from openid ([190a9f2](190a9f2a4c1a59bc68b839c465bb2536532c0e96))
|
||||
* Importing archived lists or namespaces ([8bb3f8d](8bb3f8d37c78dc704ff4316c750e143528151b48))
|
||||
* Lint ([a31086a](a31086a7a9ca7723f61a826bccbea125243478f1))
|
||||
* Microsoft todo migration not importing all tasks ([43f1daf](43f1daf40c388a0aa40f7fd6a8db4c78308d4efd))
|
||||
* Clarify which config file is used on startup ([44aaf0a](44aaf0a4eccebb1d1a25f5563e928bd1bb82d351))
|
||||
* Disabling logging completely now works ([22e3f24](22e3f242a396aa9cf54e9426077816f97a0da36f))
|
||||
* Restoring dumps with no config file saved in them ([8bf2254](8bf2254f4b87446ab0a39080cb0b7d32ccec7c0a))
|
||||
* Validate email address when creating a user via cli ([75f74b4](75f74b429eea7ae3a75cb10def1ca658af35086a))
|
||||
* Checking for error types ([ac6818a](ac6818a4769a162c458553944509fe64357370f9))
|
||||
* Lint ([7fa0865](7fa086518800243385d8cc4696eeea9bf093e5b3))
|
||||
* Return BlurHash in unsplash search results ([6b51fae](6b51fae0931308464038f55b25e81e68d014c49c))
|
||||
* Go mod tidy ([e19ad11](e19ad1184662dc9ac9aa89a44abdffc091e2a1b8))
|
||||
* Decoding images for blurHash generation ([d3bdafb](d3bdafb717b1ad3e2165097ef0b0c2dd47e1502e))
|
||||
* Lint ([de97fcb](de97fcbd121b1d56b74175fd79ef594ef34e71c8))
|
||||
* Broken link (#27) ([96e519e](96e519ea96c9537222d0b455037e11fbe9660c31))
|
||||
* Add more methods to figure out the current binary location ([9845fcc](9845fcc1708431f8f736d36e7e19a1067b0e0e52))
|
||||
* Set derived default values only after reading config from file or env ([f5ebada](f5ebada91351faf1e5602f0260908defaaabd810))
|
||||
* Sort tasks logically and consistent across dbms (#1177) ([e52c45d](e52c45d5aabb74ea7b472e8d5b44491cdd7e9489))
|
||||
* VIKUNJA_SERVICE_JWT_SECRET should be VIKUNJA_SERVICE_JWTSECRET (#1184) ([172a621](172a6214d7c30278017129b950339c78a6ddb7bc))
|
||||
* Add missing migration ([d837f8a](d837f8a6248b5ff2700a4bfc300d7f9d466cb918))
|
||||
* Revert renaming Attachments to Embeds everywhere ([c62e26b](c62e26b6fe9d9f362fcfb1df2d5664d7f6854c31))
|
||||
* Set the correct go version in go.mod ([bc7f6a8](bc7f6a858693b0e61fff7d03b5c2b40b6ae1a55d))
|
||||
* Go mod tidy ([7a30294](7a30294407843693f6c3a7414b3b9d7093359194))
|
||||
* Tests ([d0e09d6](d0e09d69d048e62ee7c5b666c2f56761b03e68e6))
|
||||
* Go mod tidy ([951d74b](951d74b272b1e881faa10095f47b6598bb076273))
|
||||
* Prevent logging openid provider errors twice ([25ffa1b](25ffa1bc2e2f1108f20b0336708d2410bb61c9e1))
|
||||
* Remove credential escaping for postgres connections to allow for passwords with special characters ([230478a](230478aae947c86f4c6f1f251dcb30aeb1293283))
|
||||
* Cycles in tasks array when memory caching was enabled ([f5a4c13](f5a4c136fbca6fc5770476e6de8d81173f007df2))
|
||||
* Add missing error check ([5cc4927](5cc4927b9ef97667bf763772beb36225fdbeded8))
|
||||
* Properly set tls config for mailer ([5743a4a](5743a4afe51de221beeeabe66552ae4d92eed1a6))
|
||||
* Return 9:00 as default time for reminders if none was set ([79b3167](79b31673e2a79eaa124976840e85757d2bebb887))
|
||||
* Reset id sequence when importing a dump from postgres ([0f555b7](0f555b7ec74ad493d2f70a4f4040db333943dc1c))
|
||||
* Add validation for negative repeat after values ([dd46174](dd461746a655d716ef142d96a2bcef5615de3dd9))
|
||||
* Lint ([1feb62c](1feb62cc458e939d46d16d24347557e7959ddfb9))
|
||||
* Make sure to use user discoverability settings when searching list users ([382a788](382a7884be1f37da5c8f657c4b17316d8691dd59))
|
||||
* Properly decode params in url ([8f27e7e](8f27e7e619ac73716211d838f52c73d7d97aead5))
|
||||
* Return all users on a list when no search param was provided ([c51ee94](c51ee94ad1d552d69c71adfc2180c7ad0d23235d))
|
||||
* Don't return email addresses from user search results ([3688bbd](3688bbde20e989397353ea4f7e872b00a53099c2))
|
||||
* Lint ([77fafd5](77fafd5dc32aee464961be40d5d0ccf82490d02a))
|
||||
* Increase test timeout ([26e2d0b](26e2d0bddeaea902dba055baf7a4c866a44ba7f1))
|
||||
* Switch to buster for build image ([59796fd](59796fd4905fca74d26c5541878379cda143a30e))
|
||||
* Use our own build image as base build image ([b6d7323](b6d7323cdfac958c9740feba1342114ab13a0afd))
|
||||
* Use golang build image to test migrations ([84bcdbf](84bcdbf937c3be7823fcf8d5fef52e3cbb1c9bde))
|
||||
* Switch back to alpine for everything, disable arm 32 docker builds ([7ffe9b6](7ffe9b625e441202a704db2774dd66fc38244c6d))
|
||||
|
||||
|
||||
### Dependencies
|
||||
|
||||
* *(deps)* Update golang.org/x/sys commit hash to a851e7d (#972)
|
||||
* *(deps)* Update golang.org/x/sys commit hash to aa78b53 (#973)
|
||||
* *(deps)* Update golang.org/x/sys commit hash to 528a39c (#974)
|
||||
* *(deps)* Update golang.org/x/sys commit hash to 437939a (#975)
|
||||
* *(deps)* Update module github.com/yuin/goldmark to v1.4.1 (#976)
|
||||
* *(deps)* Update module github.com/coreos/go-oidc/v3 to v3.1.0 (#985)
|
||||
* *(deps)* Update module github.com/spf13/viper to v1.9.0 (#987)
|
||||
* *(deps)* Update golang.org/x/crypto commit hash to 089bfa5 (#979)
|
||||
* *(deps)* Update golang.org/x/term commit hash to 140adaa (#983)
|
||||
* *(deps)* Update module github.com/labstack/echo/v4 to v4.6.0 (#988)
|
||||
* *(deps)* Update golang.org/x/sys commit hash to b8560ed (#989)
|
||||
* *(deps)* Update module github.com/golang-jwt/jwt/v4 to v4.1.0 (#991)
|
||||
* *(deps)* Update golang.org/x/sys commit hash to 92d5a99 (#992)
|
||||
* *(deps)* Update module github.com/swaggo/swag to v1.7.3 (#990)
|
||||
* *(deps)* Update module github.com/labstack/echo/v4 to v4.6.1 (#993)
|
||||
* *(deps)* Update golang.org/x/sys commit hash to 1cf2251 (#994)
|
||||
* *(deps)* Update golang.org/x/sys commit hash to 39ccf1d (#995)
|
||||
* *(deps)* Update golang.org/x/term commit hash to 03fcf44 (#996)
|
||||
* *(deps)* Update golang.org/x/oauth2 commit hash to 6b3c2da (#1000)
|
||||
* *(deps)* Update golang.org/x/sys commit hash to 69063c4 (#1001)
|
||||
* *(deps)* Update module github.com/gabriel-vasile/mimetype to v1.4.0 (#1004)
|
||||
* *(deps)* Update postgres docker tag to v14 (#1005)
|
||||
* *(deps)* Update module github.com/go-redis/redis/v8 to v8.11.4 (#1003)
|
||||
* *(deps)* Update module github.com/mattn/go-sqlite3 to v1.14.9 (#1008)
|
||||
* *(deps)* Update golang.org/x/sys commit hash to 9d821ac (#1009)
|
||||
* *(deps)* Update golang.org/x/sys commit hash to 0ec99a6 (#1010)
|
||||
* *(deps)* Update golang.org/x/sys commit hash to 9d61738 (#1011)
|
||||
* *(deps)* Update module github.com/yuin/goldmark to v1.4.2 (#1012)
|
||||
* *(deps)* Update golang.org/x/sys commit hash to 8e51046 (#1016)
|
||||
* *(deps)* Update golang.org/x/sys commit hash to d6a326f (#1017)
|
||||
* *(deps)* Update module github.com/swaggo/swag to v1.7.4 (#1018)
|
||||
* *(deps)* Update golang.org/x/sys commit hash to 711f33c (#1019)
|
||||
* *(deps)* Update golang.org/x/sys commit hash to 69cdffd (#1020)
|
||||
* *(deps)* Update golang.org/x/oauth2 commit hash to ba495a6 (#1022)
|
||||
* *(deps)* Update golang.org/x/image commit hash to 6944b10 (#1023)
|
||||
* *(deps)* Update golang.org/x/sys commit hash to 6e78728 (#1024)
|
||||
* *(deps)* Update golang.org/x/sys commit hash to b3129d9 (#1025)
|
||||
* *(deps)* Update golang.org/x/sys commit hash to 611d5d6 (#1026)
|
||||
* *(deps)* Update golang.org/x/sys commit hash to 39c9dd3 (#1027)
|
||||
* *(deps)* Update golang.org/x/sys commit hash to a2f17f7 (#1028)
|
||||
* *(deps)* Update golang.org/x/sys commit hash to 4dd7244 (#1029)
|
||||
* *(deps)* Update golang.org/x/sys commit hash to ae416a5 (#1030)
|
||||
* *(deps)* Update golang.org/x/sys commit hash to 7861aae (#1031)
|
||||
* *(deps)* Update golang.org/x/oauth2 commit hash to d3ed0bb (#1032)
|
||||
* *(deps)* Update module github.com/labstack/gommon to v0.3.1 (#1033)
|
||||
* *(deps)* Update golang.org/x/sys commit hash to c75c477 (#1034)
|
||||
* *(deps)* Update golang.org/x/sys commit hash to ebca88c (#1035)
|
||||
* *(deps)* Update golang.org/x/sys commit hash to e0b2ad0 (#1037)
|
||||
* *(deps)* Update module github.com/yuin/goldmark to v1.4.3 (#1038)
|
||||
* *(deps)* Update golang.org/x/crypto commit hash to ceb1ce7 (#1041)
|
||||
* *(deps)* Update module github.com/lib/pq to v1.10.4 (#1040)
|
||||
* *(deps)* Update golang.org/x/sys commit hash to 51b60fd (#1042)
|
||||
* *(deps)* Update golang.org/x/sys commit hash to 99a5385 (#1043)
|
||||
* *(deps)* Update golang.org/x/sys commit hash to f221eed (#1044)
|
||||
* *(deps)* Update golang.org/x/sys commit hash to 0c823b9 (#1045)
|
||||
* *(deps)* Update module github.com/yuin/goldmark to v1.4.4 (#1046)
|
||||
* *(deps)* Update golang.org/x/sys commit hash to 0a5406a (#1048)
|
||||
* *(deps)* Update golang.org/x/crypto commit hash to b4de73f (#1047)
|
||||
* *(deps)* Update module github.com/ulule/limiter/v3 to v3.9.0 (#1049)
|
||||
* *(deps)* Update golang.org/x/crypto commit hash to ae814b3 (#1050)
|
||||
* *(deps)* Update golang.org/x/sys commit hash to dee7805 (#1051)
|
||||
* *(deps)* Update golang.org/x/sys commit hash to ef496fb (#1052)
|
||||
* *(deps)* Update golang.org/x/sys commit hash to fe61309 (#1054)
|
||||
* *(deps)* Update module github.com/swaggo/swag to v1.7.6 (#1055)
|
||||
* *(deps)* Update golang.org/x/crypto commit hash to 5770296 (#1056)
|
||||
* *(deps)* Update module github.com/golang-jwt/jwt/v4 to v4.2.0 (#1057)
|
||||
* *(deps)* Update golang.org/x/sys commit hash to 94396e4 (#1058)
|
||||
* *(deps)* Update golang.org/x/sys commit hash to 97ca703 (#1059)
|
||||
* *(deps)* Update golang.org/x/crypto commit hash to 4570a08 (#1062)
|
||||
* *(deps)* Update golang.org/x/sys commit hash to 798191b (#1061)
|
||||
* *(deps)* Update golang.org/x/sys commit hash to af8b642 (#1063)
|
||||
* *(deps)* Update module github.com/spf13/viper to v1.10.0 (#1064)
|
||||
* *(deps)* Update golang.org/x/sys commit hash to 03aa0b5 (#1067)
|
||||
* *(deps)* Update golang.org/x/sys commit hash to 3b038e5 (#1068)
|
||||
* *(deps)* Update module github.com/spf13/cobra to v1.3.0 (#1070)
|
||||
* *(deps)* Update golang.org/x/sys commit hash to 4825e8c (#1071)
|
||||
* *(deps)* Update module github.com/spf13/viper to v1.10.1 (#1072)
|
||||
* *(deps)* Update golang.org/x/crypto commit hash to e495a2d (#1073)
|
||||
* *(deps)* Update golang.org/x/sys commit hash to 4abf325 (#1074)
|
||||
* *(deps)* Update golang.org/x/sys commit hash to 1d35b9e (#1075)
|
||||
* *(deps)* Update module github.com/magefile/mage to v1.12.0 (#1076)
|
||||
* *(deps)* Update module github.com/magefile/mage to v1.12.1 (#1077)
|
||||
* *(deps)* Update module github.com/getsentry/sentry-go to v0.12.0 (#1079)
|
||||
* *(deps)* Update module github.com/swaggo/swag to v1.7.8 (#1080)
|
||||
* *(deps)* Update module github.com/spf13/afero to v1.7.0 (#1078)
|
||||
* *(deps)* Update module github.com/spf13/afero to v1.7.1 (#1081)
|
||||
* *(deps)* Update module github.com/mattn/go-sqlite3 to v1.14.10 (#1082)
|
||||
* *(deps)* Update module github.com/spf13/afero to v1.8.0 (#1083)
|
||||
* *(deps)* Update module github.com/labstack/echo/v4 to v4.6.2 (#1084)
|
||||
* *(deps)* Update module github.com/labstack/echo/v4 to v4.6.3 (#1089)
|
||||
* *(deps)* Update golang.org/x/sys commit hash to a018aaa (#1088)
|
||||
* *(deps)* Update golang.org/x/sys commit hash to 5a964db (#1090)
|
||||
* *(deps)* Update golang.org/x/crypto commit hash to 5e0467b (#1091)
|
||||
* *(deps)* Update golang.org/x/sys commit hash to da31bd3 (#1093)
|
||||
* *(deps)* Update module github.com/prometheus/client_golang to v1.12.0 (#1094)
|
||||
* *(deps)* Update golang.org/x/crypto commit hash to e04a857 (#1097)
|
||||
* *(deps)* Update golang.org/x/crypto commit hash to aa10faf (#1098)
|
||||
* *(deps)* Update module github.com/mattn/go-sqlite3 to v1.14.11 (#1099)
|
||||
* *(deps)* Update golang.org/x/crypto commit hash to 198e437 (#1100)
|
||||
* *(deps)* Update golang.org/x/sys commit hash to 99c3d69 (#1101)
|
||||
* *(deps)* Update module github.com/prometheus/client_golang to v1.12.1 (#1102)
|
||||
* *(deps)* Update klakegg/hugo docker tag to v0.92.0 (#1103)
|
||||
* *(deps)* Update klakegg/hugo docker tag to v0.92.1 (#1104)
|
||||
* *(deps)* Update golang.org/x/crypto commit hash to 30dcbda (#1105)
|
||||
* *(deps)* Update module github.com/swaggo/swag to v1.7.9 (#1106)
|
||||
* *(deps)* Update golang.org/x/sys commit hash to 1c1b9b1 (#1107)
|
||||
* *(deps)* Update module github.com/spf13/afero to v1.8.1 (#1108)
|
||||
* *(deps)* Update golang.org/x/sys commit hash to 5739886 (#1110)
|
||||
* *(deps)* Update golang.org/x/crypto commit hash to 20e1d8d (#1111)
|
||||
* *(deps)* Update module github.com/yuin/goldmark to v1.4.5 (#1112)
|
||||
* *(deps)* Update golang.org/x/crypto commit hash to bba287d (#1113)
|
||||
* *(deps)* Update golang.org/x/crypto commit hash to dad3315 (#1114)
|
||||
* *(deps)* Update module github.com/golang-jwt/jwt/v4 to v4.3.0 (#1117)
|
||||
* *(deps)* Update golang.org/x/sys commit hash to 3681064 (#1116)
|
||||
* *(deps)* Update golang.org/x/crypto commit hash to db63837 (#1115)
|
||||
* *(deps)* Update golang.org/x/crypto commit hash to f4118a5 (#1118)
|
||||
* *(deps)* Update golang.org/x/crypto commit hash to 8634188 (#1121)
|
||||
* *(deps)* Update module github.com/yuin/goldmark to v1.4.6 (#1122)
|
||||
* *(deps)* Update module github.com/yuin/goldmark to v1.4.7 (#1123)
|
||||
* *(deps)* Update module github.com/swaggo/swag to v1.8.0 (#1124)
|
||||
* *(deps)* Update golang.org/x/sys commit hash to 0005352 (#1125)
|
||||
* *(deps)* Update golang.org/x/sys commit hash to f242548 (#1126)
|
||||
* *(deps)* Update klakegg/hugo docker tag to v0.92.2 (#1127)
|
||||
* *(deps)* Update golang.org/x/sys commit hash to dbe011f (#1129)
|
||||
* *(deps)* Update golang.org/x/sys commit hash to 95c6836 (#1130)
|
||||
* *(deps)* Update golang.org/x/oauth2 commit hash to ee48083 (#1128)
|
||||
* *(deps)* Update module github.com/mattn/go-sqlite3 to v1.14.12 (#1132)
|
||||
* *(deps)* Update golang.org/x/sys commit hash to 4e6760a (#1131)
|
||||
* *(deps)* Update golang.org/x/image commit hash to 723b81c (#1133)
|
||||
* *(deps)* Update module github.com/labstack/echo/v4 to v4.7.0 (#1134)
|
||||
* *(deps)* Update klakegg/hugo docker tag to v0.93.0 (#1135)
|
||||
* *(deps)* Update module github.com/yuin/goldmark to v1.4.8 (#1136)
|
||||
* *(deps)* Update klakegg/hugo docker tag to v0.93.2 (#1137)
|
||||
* *(deps)* Update golang.org/x/sys commit hash to 22a9840 (#1138)
|
||||
* *(deps)* Update golang.org/x/crypto commit hash to efcb850 (#1139)
|
||||
* *(deps)* Update golang.org/x/oauth2 commit hash to 6242fa9 (#1140)
|
||||
* *(deps)* Update golang.org/x/sys commit hash to b874c99 (#1141)
|
||||
* *(deps)* Update klakegg/hugo docker tag to v0.93.3 (#1142)
|
||||
* *(deps)* Update module github.com/labstack/echo/v4 to v4.7.1 (#1146)
|
||||
* *(deps)* Update module github.com/stretchr/testify to v1.7.1 (#1148)
|
||||
* *(deps)* Update module github.com/swaggo/swag to v1.8.1 (#1156)
|
||||
* *(deps)* Update module github.com/yuin/goldmark to v1.4.11 (#1143)
|
||||
* *(deps)* Update module github.com/spf13/cobra to v1.4.0 (#1145)
|
||||
* *(deps)* Update module github.com/lib/pq to v1.10.5 (#1157)
|
||||
* *(deps)* Update module github.com/spf13/viper to v1.11.0 (#1159)
|
||||
* *(deps)* Update module github.com/yuin/goldmark to v1.4.12 (#1162)
|
||||
* *(deps)* Update module github.com/prometheus/client_golang to v1.12.2 (#1166)
|
||||
* *(deps)* Update module github.com/mattn/go-sqlite3 to v1.14.13 (#1165)
|
||||
* *(deps)* Update module github.com/coreos/go-oidc/v3 to v3.2.0 (#1164)
|
||||
* *(deps)* Update module github.com/swaggo/swag to v1.8.2 (#1167)
|
||||
* *(deps)* Update module github.com/lib/pq to v1.10.6 (#1169)
|
||||
* *(deps)* Update module gopkg.in/yaml.v3 to v3.0.1 (#1179)
|
||||
* *(deps)* Update module github.com/imdario/mergo to v0.3.13 (#1178)
|
||||
* *(deps)* Update module github.com/stretchr/testify to v1.7.2 (#1182)
|
||||
* *(deps)* Update module github.com/swaggo/swag to v1.8.3 (#1185)
|
||||
* *(deps)* Update module github.com/spf13/cobra to v1.5.0 (#1192)
|
||||
* *(deps)* Update module github.com/golang-jwt/jwt/v4 to v4.4.2 (#1193)
|
||||
* *(deps)* Update module github.com/stretchr/testify to v1.8.0 (#1191)
|
||||
* *(deps)* Update module github.com/go-testfixtures/testfixtures/v3 to v3.8.0 (#1168)
|
||||
* *(deps)* Update module github.com/mattn/go-sqlite3 to v1.14.14 (#1194)
|
||||
* *(deps)* Update golang.org/x/term digest to 065cf7b (#1207)
|
||||
* *(deps)* Update golang.org/x/image digest to 41969df (#1203)
|
||||
* *(deps)* Update module github.com/yuin/goldmark to v1.4.13 (#1209)
|
||||
* *(deps)* Update golang.org/x/crypto digest to 0559593 (#1202)
|
||||
* *(deps)* Update module github.com/spf13/afero to v1.9.0 (#1210)
|
||||
* *(deps)* Update module github.com/gabriel-vasile/mimetype to v1.4.1 (#1208)
|
||||
* *(deps)* Update golang.org/x/sync digest to 0de741c (#1205)
|
||||
* *(deps)* Update github.com/c2h5oh/datasize digest to 859f65c (#1201)
|
||||
* *(deps)* Update golang.org/x/oauth2 digest to 2104d58 (#1204)
|
||||
* *(deps)* Update golang.org/x/sys digest to c0bba94 (#1206)
|
||||
* *(deps)* Update golang.org/x/oauth2 digest to c8730f7 (#1214)
|
||||
* *(deps)* Update module github.com/spf13/afero to v1.9.2 (#1215)
|
||||
* *(deps)* Update module github.com/swaggo/swag to v1.8.4 (#1216)
|
||||
* *(deps)* Update module github.com/spf13/viper to v1.12.0 (#1180)
|
||||
* *(deps)* Update golang.org/x/sys digest to 1609e55 (#1217)
|
||||
* *(deps)* Update module github.com/go-testfixtures/testfixtures/v3 to v3.8.1 (#1226)
|
||||
* *(deps)* Update module go to 1.18 (#1225)
|
||||
|
||||
### Documentation
|
||||
* Add docker-compose example with no proxy ([4255bc3](4255bc3a945b6fe4314e3cd3f62908dd1be1ff4a))
|
||||
* Add another youtube tutorial ([dbd6f36](dbd6f36da6e56355993cc1411379997e26c88b36))
|
||||
* Fix api url in docker examples without a proxy ([68998e9](68998e90a446569869fb150bd5fc0739f496b066))
|
||||
* Make sure all links to vikunja pages are https ([cc612d5](cc612d505f22e5d895b6ebda61fe62498634cec5))
|
||||
* Update backup instructions ([4829c89](4829c899400544ad27cacfb7d19b40988302a413))
|
||||
* Add postgres to docker-compose examples ([2aea169](2aea1691cf33b7d9e03fbe2c711af7d8f76d9724))
|
||||
* Improve development docs ([9bf32aa](9bf32aae99a7e69cce0cd4477e8fc8ddcaea25ea))
|
||||
* Add another tutorial link ([1fa74cb](1fa74cba6407c2b694b14f8439f1492476433d62))
|
||||
* Improve wording for systemd ([13561f2](13561f211493903b17c856b3010345ea9df725d4))
|
||||
* Update testing ([da318e3](da318e3db15121ba864db8450a76ba9ed18b9fd5))
|
||||
* Add guide for Synology NAS ([049ae39](049ae39c62079f77921b7a9fad5023b2c1c0c1c5))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* *(docs)* Add details of using NGINX Proxy Manager to the Reverse Proxy docs (#13)
|
||||
* *(docs)* Add versions explanation
|
||||
* *(mail)* Don't try to authenticate when no username and password was provided* Add better error logs for mage commands ([bb086eb](bb086eb9f87669f844c283d42ea9ca9f3f5a7877))
|
||||
* Expose if task comments are enabled or not in /info ([ae8db17](ae8db176db57fa6176e00b87924f70352332ca66))
|
||||
* Improve account deletion email grammar (#1006) ([dcb52c0](dcb52c00f1c6b3217e2b508d7799fc83adb3b055))
|
||||
* Add more debug logging when deleting users ([8f55af0](8f55af07c936218487ec94e65c6673fbddd0cdb5))
|
||||
* Don't require a password for data export from users authenticated with third-party auth ([9eca971](9eca971c938699d481915fb6e14c765aea1fa3b5))
|
||||
* Expose if a user is a local user through its jwt token ([516c812](516c812043e77be7f834ae1326d13d39e156ef77))
|
||||
* Expose if a user is a local user through the /user endpoint ([2683ef2](2683ef23d538eb846d5d799798fa82cca70dc017))
|
||||
* Enable rate limit for unauthenticated routes ([093d0c6](093d0c65ca6338358dbd1df904daadd7808f2817))
|
||||
* Use wallpaper topic for default unsplash background list ([88a2ced](88a2cede19f1844814530af948c3cc5a0b026419))
|
||||
* Gravatar - Lowercase emails before MD5 hash (#10) ([36bf3d2](36bf3d216a7be28e917e2816a9e5da43439f2c20))
|
||||
* Add marble avatar (#1060) ([73ee696](73ee696fc3cf941af2d2c2cf81224aa01f93234e))
|
||||
* Save user language in the settings ([a98119f](a98119f2d670a11efab6008129b767f9208f8113))
|
||||
* Add time zone setting for reminders (#1092) ([61d49c3](61d49c3a56a59e52ce407b858ddd4aa573dbee9d))
|
||||
* Add long-lived api tokens (#1085) ([1322cb1](1322cb16d76a40ad90631e3e091da0f0d44957a9))
|
||||
* Upgrade golangci-lint to 1.45.2 ([5cf263a](5cf263a86f954a38cbfafb6b0857bf591f82a811))
|
||||
* Add date math for filters (#1086) ([0a1d8c9](0a1d8c940410b03a78016ac6110883ca05484816))
|
||||
* Add migration to create BlurHash strings for all list backgrounds ([362706b](362706b38d52720b5a1615e185a985b7708168f7))
|
||||
* Generate a BlurHash when uploading a new image ([f83b09a](f83b09af59ed25425a16824ccf48d903c81e861a))
|
||||
* Save BlurHash from unsplash when selecting a photo from unsplash ([2ec7d7a](2ec7d7a8a85cc12c07d20cfab9b90a78a7857eb6))
|
||||
* Return BlurHash for unsplash search results ([6df8658](6df865876df961f2bec476126bf6e7fbe5d43e0e))
|
||||
* Add caldav tokens (#1065) ([e4b50e8](e4b50e84a44f809cc829c2fdb6f52b03b40a367b))
|
||||
* Ability to serve static files (#1174) ([acaa850](acaa85083f2bebbc67608ae0f454ed5e9a3ef8a0))
|
||||
* Restrict max avatar size ([2f25b48](2f25b48869f59256bf7d692c4486c64c30b85e5e))
|
||||
* Send overdue tasks email notification at 9:00 in the user's time zone ([7eb3b96](7eb3b96a4465ca6648572b07c506c06f2c28c375))
|
||||
* Add setting to change overdue tasks reminder email time ([8869adf](8869adfc276f674b686bf68f949d7efbb417e55b))
|
||||
* Allow only the authors of task comments to edit them ([01271c4](01271c4c0111b3b040dcb9a0d502d31078ad6d4b))
|
||||
* Migrate away from gomail ([30e0e98](30e0e98f7738e36698990523377f47edcbf6806c))
|
||||
* Embed the vikunja logo as inline attachment ([f4f8450](f4f8450d166f1a836eea202dd0340d2156d3dfe9))
|
||||
* Use embed fs directly to embed the logo in mails ([73c4c39](73c4c399e5d610bb713f1e9feab543e0425ee959))
|
||||
* Use actual uuids for tasks ([62325de](62325de9cd5da5b70987081956a28e7baa907081))
|
||||
* Add issue template ([117f6b3](117f6b38e1d35c09f2657975ea75dcfedcd8425d))
|
||||
|
||||
|
||||
### Miscellaneous Tasks
|
||||
|
||||
* *(ci)* Use latest version of s3 plugin
|
||||
* *(ci)* Sign drone config
|
||||
* *(docs)* Update docs about compiling from source
|
||||
* *(docs)* Redirect properly from /docs/docs
|
||||
* *(docs)* Add new mailer option to docs
|
||||
* *(docs)* Clarify openid setup with environment variables
|
||||
* *(docs)* Add frontendurl to all example configs
|
||||
* *(mage)* Don't set api packages when they are not used* Sign drone config ([1d8d0f1](1d8d0f140e4f2a59947167bd597e5f12b84b009d))
|
||||
* Cleanup namespace creation ([b60c69c](b60c69c5a8c004a780b989cf0bb8ab6455086b0f))
|
||||
* Generate swagger docs ([ba2bdff](ba2bdff39109db9ecc4b525e39e2642b41ac03b8))
|
||||
* Go mod tidy ([726a517](726a517bec731f1af8e3186e280718fef02cadf7))
|
||||
* Upgrade trello api wrapper and remove fork ([7e99618](7e99618319547c7e7dfa2cc063f654300f7074fb))
|
||||
* Use our custom build image to build docker image ([251b877](251b877015761fdd2b8dbd18cd8ec696dc374103))
|
||||
* Update golangci-lint ([430057a](430057a404b04e75c62a15693f479c6fc8e63189))
|
||||
|
||||
|
||||
### Other
|
||||
|
||||
* *(other)* Healthcheck endpoint (#998)
|
||||
* *(other)* Added the ability to configure the JWT expiry date using a new server.jwtttl config parameter. (#999)
|
||||
* *(other)* Enable a list to be moved across namespaces (#1096)
|
||||
* *(other)* A bunch of dependency updates at once (#1155)
|
||||
* *(other)* Add client-cert parameters of the Go pq driver to the Vikunja config (#1161)
|
||||
* *(other)* Add exec to run script to run app as PID 1 (#1200)
|
||||
|
||||
## [0.18.1] - 2021-09-08
|
||||
|
||||
### Fixed
|
||||
|
|
20
Dockerfile
20
Dockerfile
|
@ -1,29 +1,27 @@
|
|||
|
||||
##############
|
||||
# Build stage
|
||||
FROM golang:1-alpine3.12 AS build-env
|
||||
FROM golang:1.18-alpine AS build-env
|
||||
|
||||
RUN apk --no-cache add build-base git && \
|
||||
go install github.com/magefile/mage@latest && \
|
||||
mv /go/bin/mage /usr/local/go/bin
|
||||
|
||||
ARG VIKUNJA_VERSION
|
||||
ENV TAGS "sqlite"
|
||||
ENV GO111MODULE=on
|
||||
|
||||
# Build deps
|
||||
RUN apk --no-cache add build-base git
|
||||
|
||||
# Setup repo
|
||||
COPY . ${GOPATH}/src/code.vikunja.io/api
|
||||
WORKDIR ${GOPATH}/src/code.vikunja.io/api
|
||||
COPY . /go/src/code.vikunja.io/api
|
||||
WORKDIR /go/src/code.vikunja.io/api
|
||||
|
||||
# Checkout version if set
|
||||
RUN if [ -n "${VIKUNJA_VERSION}" ]; then git checkout "${VIKUNJA_VERSION}"; fi \
|
||||
&& go install github.com/magefile/mage \
|
||||
&& mage build:clean build
|
||||
|
||||
###################
|
||||
# The actual image
|
||||
# Note: I wanted to use the scratch image here, but unfortunatly the go-sqlite bindings require cgo and
|
||||
# because of this, the container would not start when I compiled the image without cgo.
|
||||
FROM alpine:3.12
|
||||
FROM alpine:3.16
|
||||
LABEL maintainer="maintainers@vikunja.io"
|
||||
|
||||
WORKDIR /app/vikunja/
|
||||
|
@ -39,7 +37,7 @@ RUN apk --no-cache add shadow && \
|
|||
chown vikunja -R /app/vikunja
|
||||
COPY run.sh /run.sh
|
||||
|
||||
# Fix time zone settings not working
|
||||
# Add time zone data
|
||||
RUN apk --no-cache add tzdata
|
||||
|
||||
# Files permissions
|
||||
|
|
48
Dockerfile.arm32
Normal file
48
Dockerfile.arm32
Normal file
|
@ -0,0 +1,48 @@
|
|||
|
||||
##############
|
||||
# Build stage
|
||||
FROM golang:1.18-buster AS build-env
|
||||
|
||||
RUN go install github.com/magefile/mage@latest && \
|
||||
mv /go/bin/mage /usr/local/go/bin
|
||||
|
||||
ARG VIKUNJA_VERSION
|
||||
|
||||
# Setup repo
|
||||
COPY . /go/src/code.vikunja.io/api
|
||||
WORKDIR /go/src/code.vikunja.io/api
|
||||
|
||||
# Checkout version if set
|
||||
RUN if [ -n "${VIKUNJA_VERSION}" ]; then git checkout "${VIKUNJA_VERSION}"; fi \
|
||||
&& mage build:clean build
|
||||
|
||||
###################
|
||||
# The actual image
|
||||
# Note: I wanted to use the scratch image here, but unfortunatly the go-sqlite bindings require cgo and
|
||||
# because of this, the container would not start when I compiled the image without cgo.
|
||||
# We're using debian as a base image here because the latest alpine image does not work with arm.
|
||||
FROM debian:buster-slim
|
||||
LABEL maintainer="maintainers@vikunja.io"
|
||||
|
||||
WORKDIR /app/vikunja/
|
||||
COPY --from=build-env /go/src/code.vikunja.io/api/vikunja .
|
||||
ENV VIKUNJA_SERVICE_ROOTPATH=/app/vikunja/
|
||||
|
||||
# Dynamic permission changing stuff
|
||||
ENV PUID 1000
|
||||
ENV PGID 1000
|
||||
RUN addgroup --gid ${PGID} vikunja && \
|
||||
chown ${PUID} -R /app/vikunja && \
|
||||
useradd --shell /bin/sh --gid vikunja --uid ${PUID} --home-dir /app/vikunja vikunja
|
||||
COPY run.sh /run.sh
|
||||
|
||||
# Fix time zone settings not working
|
||||
RUN apt-get update && apt-get install -y tzdata && apt-get clean
|
||||
|
||||
# Files permissions
|
||||
RUN mkdir /app/vikunja/files && \
|
||||
chown -R vikunja /app/vikunja/files
|
||||
VOLUME /app/vikunja/files
|
||||
|
||||
CMD ["/run.sh"]
|
||||
EXPOSE 3456
|
|
@ -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.18.1-brightgreen.svg)](https://dl.vikunja.io)
|
||||
[![Download](https://img.shields.io/badge/download-v0.19.2-brightgreen.svg)](https://dl.vikunja.io)
|
||||
[![Docker Pulls](https://img.shields.io/docker/pulls/vikunja/api.svg)](https://hub.docker.com/r/vikunja/api/)
|
||||
[![Swagger Docs](https://img.shields.io/badge/swagger-docs-brightgreen.svg)](https://try.vikunja.io/api/v1/docs)
|
||||
[![Go Report Card](https://goreportcard.com/badge/kolaente.dev/vikunja/api)](https://goreportcard.com/report/kolaente.dev/vikunja/api)
|
||||
|
@ -56,6 +56,10 @@ See [the roadmap](https://my.vikunja.cloud/share/QFyzYEmEYfSyQfTOmIRSwLUpkFjboaB
|
|||
|
||||
Fork -> Push -> Pull-Request. Also see the [dev docs](https://vikunja.io/docs/development/) for more info.
|
||||
|
||||
## Sponsors
|
||||
|
||||
[![Relm](https://vikunja.io/images/sponsors/relm.png)](https://relm.us)
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the AGPLv3 License. See the [LICENSE](LICENSE) file for the full license text.
|
||||
|
|
59
cliff.toml
Normal file
59
cliff.toml
Normal file
|
@ -0,0 +1,59 @@
|
|||
[changelog]
|
||||
body = """
|
||||
{% if version %}\
|
||||
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
|
||||
{% else %}\
|
||||
## [unreleased]
|
||||
{% endif %}\
|
||||
|
||||
|
||||
{% for group, commits in commits | group_by(attribute="group") %}
|
||||
### {{ group | upper_first }}
|
||||
{% for commit in commits
|
||||
| filter(attribute="scope")
|
||||
| sort(attribute="scope") %}
|
||||
* *({{commit.scope}})* {{ commit.message | upper_first }}
|
||||
{%- if commit.breaking %}
|
||||
{% raw %} {% endraw %}- **BREAKING**: {{commit.breaking_description}}
|
||||
{%- endif -%}
|
||||
{%- endfor -%}
|
||||
{%- for commit in commits %}
|
||||
{%- if commit.scope -%}
|
||||
{% else -%}
|
||||
* {{ commit.message | upper_first }} ([{{ commit.id | truncate(length=7, end="") }}]({{ commit.id }}))
|
||||
{% if commit.breaking -%}
|
||||
{% raw %} {% endraw %}- **BREAKING**: {{commit.breaking_description}}
|
||||
{% endif -%}
|
||||
{% endif -%}
|
||||
{% endfor -%}
|
||||
{% raw %}\n{% endraw %}\
|
||||
{% endfor %}\n
|
||||
|
||||
"""
|
||||
#{% for group, commits in commits | group_by(attribute="group") %}
|
||||
# ### {{ group | upper_first }}
|
||||
# {% for commit in commits %}\
|
||||
# - {% if commit.breaking %}[**breaking**] {% endif %}{{ commit.message | upper_first }} ([{{ commit.id | truncate(length=7, end="") }}]({{ commit.id }}))
|
||||
# {% endfor %}\
|
||||
#{% endfor %}\n
|
||||
# remove the leading and trailing whitespace from the template
|
||||
trim = true
|
||||
|
||||
[git]
|
||||
conventional_commits = true
|
||||
filter_unconventional = false
|
||||
commit_parsers = [
|
||||
{ message = ".*(deps).*", group = "Dependencies"},
|
||||
{ message = "^feat", group = "Features"},
|
||||
{ message = "^fix", group = "Bug Fixes"},
|
||||
{ message = "^doc", group = "Documentation"},
|
||||
{ message = "^perf", group = "Performance"},
|
||||
{ message = "^refactor", group = "Refactor"},
|
||||
{ message = "^style", group = "Styling"},
|
||||
{ message = "^test", group = "Testing"},
|
||||
{ message = "^chore\\(release\\): prepare for", skip = true},
|
||||
{ message = "^chore", group = "Miscellaneous Tasks"},
|
||||
{ body = ".*security", group = "Security"},
|
||||
{ message = ".*", group = "Other", default_scope = "other"}, # Everything that's not a conventional commit goes into the "Other" category
|
||||
]
|
||||
|
|
@ -127,8 +127,11 @@ mailer:
|
|||
enabled: false
|
||||
# SMTP Host
|
||||
host: ""
|
||||
# SMTP Host port
|
||||
# SMTP Host port.
|
||||
# **NOTE:** If you're unable to send mail and the only error you see in the logs is an `EOF`, try setting the port to `25`.
|
||||
port: 587
|
||||
# SMTP Auth Type. Can be either `plain`, `login` or `cram-md5`.
|
||||
authtype: "plain"
|
||||
# SMTP username
|
||||
username: "user"
|
||||
# SMTP password
|
||||
|
@ -299,6 +302,8 @@ auth:
|
|||
enabled: false
|
||||
# The url to redirect clients to. Defaults to the configured frontend url. If you're using Vikunja with the official
|
||||
# frontend, you don't need to change this value.
|
||||
# **Note:** The redirect url must exactly match the configured redirect url with the third party provider.
|
||||
# This includes all slashes at the end or protocols.
|
||||
redirecturl: <frontend url>
|
||||
# A list of enabled providers
|
||||
providers:
|
||||
|
|
|
@ -36,8 +36,9 @@ Make sure to check the other doc articles for specific development tasks like [t
|
|||
## Frontend requirements
|
||||
|
||||
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 yarn v1 and nodejs in version 16 installed.
|
||||
You need to have [pnpm](https://pnpm.io/) and nodejs in version 16 or 18 installed.
|
||||
|
||||
## Git flow
|
||||
|
||||
|
|
|
@ -98,12 +98,12 @@ Check out the docs [in the frontend repo](https://kolaente.dev/vikunja/frontend/
|
|||
To run the frontend unit tests, run
|
||||
|
||||
{{< highlight bash >}}
|
||||
yarn test:unit
|
||||
pnpm test:unit
|
||||
{{< /highlight >}}
|
||||
|
||||
The frontend also has a watcher available that re-runs all unit tests every time you change something.
|
||||
To use it, simply run
|
||||
|
||||
{{< highlight bash >}}
|
||||
yarn test:unit-watch
|
||||
pnpm test:unit-watch
|
||||
{{< /highlight >}}
|
||||
|
|
|
@ -38,9 +38,7 @@ More options are available, please refer to the [magefile docs]({{< ref "../deve
|
|||
|
||||
The code for the frontend is located at [code.vikunja.io/frontend](https://code.vikunja.io/frontend).
|
||||
|
||||
You need to have yarn v1 and nodejs in version 16 installed.
|
||||
|
||||
1. Make sure [yarn v1](https://yarnpkg.com/getting-started/install) is properly installed on your system.
|
||||
3. Clone the repo with `git clone https://code.vikunja.io/frontend` and switch into the directory.
|
||||
3. Install all dependencies with `yarn install`
|
||||
4. Build the frontend with `yarn build`. This will result in a js bundle in the `dist/` folder which you can deploy.
|
||||
1. Make sure you have [pnpm](https://pnpm.io/) properly installed on your system.
|
||||
2. Clone the repo with `git clone https://code.vikunja.io/frontend` and switch into the directory.
|
||||
3. Install all dependencies with `pnpm install`
|
||||
4. Build the frontend with `pnpm build`. This will result in a static js bundle in the `dist/` folder which you can deploy.
|
||||
|
|
|
@ -10,8 +10,9 @@ menu:
|
|||
|
||||
# Configuration options
|
||||
|
||||
You can either use a `config.yml` file in the root directory of vikunja or set all config option with
|
||||
You can either use a `config.yml` file in the root directory of vikunja or set almost all config option with
|
||||
environment variables. If you have both, the value set in the config file is used.
|
||||
Right now it is not possible to configure openid authentication via environment variables.
|
||||
|
||||
Variables are nested in the `config.yml`, these nested variables become `VIKUNJA_FIRST_CHILD` when configuring via
|
||||
environment variables. So setting
|
||||
|
@ -656,7 +657,8 @@ Environment path: `VIKUNJA_MAILER_HOST`
|
|||
|
||||
### port
|
||||
|
||||
SMTP Host port
|
||||
SMTP Host port.
|
||||
**NOTE:** If you're unable to send mail and the only error you see in the logs is an `EOF`, try setting the port to `25`.
|
||||
|
||||
Default: `587`
|
||||
|
||||
|
@ -665,6 +667,17 @@ Full path: `mailer.port`
|
|||
Environment path: `VIKUNJA_MAILER_PORT`
|
||||
|
||||
|
||||
### authtype
|
||||
|
||||
SMTP Auth Type. Can be either `plain`, `login` or `cram-md5`.
|
||||
|
||||
Default: `plain`
|
||||
|
||||
Full path: `mailer.authtype`
|
||||
|
||||
Environment path: `VIKUNJA_MAILER_AUTHTYPE`
|
||||
|
||||
|
||||
### username
|
||||
|
||||
SMTP username
|
||||
|
|
|
@ -50,6 +50,8 @@ services:
|
|||
VIKUNJA_DATABASE_TYPE: mysql
|
||||
VIKUNJA_DATABASE_USER: vikunja
|
||||
VIKUNJA_DATABASE_DATABASE: vikunja
|
||||
VIKUNJA_SERVICE_JWTSECRET: <a super secure random secret>
|
||||
VIKUNJA_SERVICE_FRONTENDURL: https://<your public frontend url with slash>/
|
||||
volumes:
|
||||
- ./files:/app/vikunja/files
|
||||
depends_on:
|
||||
|
|
|
@ -103,6 +103,8 @@ services:
|
|||
VIKUNJA_DATABASE_TYPE: mysql
|
||||
VIKUNJA_DATABASE_USER: vikunja
|
||||
VIKUNJA_DATABASE_DATABASE: vikunja
|
||||
VIKUNJA_SERVICE_JWTSECRET: <a super secure random secret>
|
||||
VIKUNJA_SERVICE_FRONTENDURL: http://<your public frontend url with slash>/
|
||||
ports:
|
||||
- 3456:3456
|
||||
volumes:
|
||||
|
@ -141,6 +143,8 @@ services:
|
|||
VIKUNJA_DATABASE_TYPE: mysql
|
||||
VIKUNJA_DATABASE_USER: vikunja
|
||||
VIKUNJA_DATABASE_DATABASE: vikunja
|
||||
VIKUNJA_SERVICE_JWTSECRET: <a super secure random secret>
|
||||
VIKUNJA_SERVICE_FRONTENDURL: https://<your public frontend url with slash>/
|
||||
volumes:
|
||||
- ./files:/app/vikunja/files
|
||||
networks:
|
||||
|
@ -199,6 +203,8 @@ services:
|
|||
VIKUNJA_DATABASE_TYPE: mysql
|
||||
VIKUNJA_DATABASE_USER: vikunja
|
||||
VIKUNJA_DATABASE_DATABASE: vikunja
|
||||
VIKUNJA_SERVICE_JWTSECRET: <a super secure random secret>
|
||||
VIKUNJA_SERVICE_FRONTENDURL: https://<your public frontend url with slash>/
|
||||
volumes:
|
||||
- ./files:/app/vikunja/files
|
||||
networks:
|
||||
|
@ -292,6 +298,8 @@ services:
|
|||
VIKUNJA_DATABASE_TYPE: mysql
|
||||
VIKUNJA_DATABASE_USER: vikunja
|
||||
VIKUNJA_DATABASE_DATABASE: vikunja
|
||||
VIKUNJA_SERVICE_JWTSECRET: <a super secure random secret>
|
||||
VIKUNJA_SERVICE_FRONTENDURL: https://<your public frontend url with slash>/
|
||||
volumes:
|
||||
- ./files:/app/vikunja/files
|
||||
depends_on:
|
||||
|
@ -350,6 +358,8 @@ services:
|
|||
VIKUNJA_DATABASE_TYPE: mysql
|
||||
VIKUNJA_DATABASE_USER: vikunja
|
||||
VIKUNJA_DATABASE_DATABASE: vikunja
|
||||
VIKUNJA_SERVICE_JWTSECRET: <a super secure random secret>
|
||||
VIKUNJA_SERVICE_FRONTENDURL: https://<your public frontend url with slash>/
|
||||
volumes:
|
||||
- ./files:/app/vikunja/files
|
||||
depends_on:
|
||||
|
@ -379,7 +389,7 @@ you can prepare 2 proxy rules:
|
|||
* a redirection rule for vikunja's api (see example screenshot using port 3456)
|
||||
* a similar redirection rule for vikunja's frontend (using port 4321)
|
||||
|
||||
![Synology Proxy Settings](/synology-proxy-1.png)
|
||||
![Synology Proxy Settings](/docs/synology-proxy-1.png)
|
||||
|
||||
You should also add 2 empty folders for mariadb and vikunja inside Synology's
|
||||
docker main folders:
|
||||
|
@ -399,7 +409,7 @@ To do that, you can
|
|||
2. Give it the name Vikunja and paste the adapted docker compose file
|
||||
3. Deploy the Stack with the "Delpoy Stack" button:
|
||||
|
||||
![Portainer Stack deploy](/synology-proxy-2.png)
|
||||
![Portainer Stack deploy](/docs/synology-proxy-2.png)
|
||||
|
||||
The docker-compose file we're going to use is very similar to the [example without any proxy](#example-without-any-proxy) above:
|
||||
|
||||
|
@ -426,6 +436,8 @@ services:
|
|||
VIKUNJA_DATABASE_TYPE: mysql
|
||||
VIKUNJA_DATABASE_USER: vikunja
|
||||
VIKUNJA_DATABASE_DATABASE: vikunja
|
||||
VIKUNJA_SERVICE_JWTSECRET: <a super secure random secret>
|
||||
VIKUNJA_SERVICE_FRONTENDURL: https://<your public frontend url with slash>/
|
||||
ports:
|
||||
- 3456:3456
|
||||
volumes:
|
||||
|
|
|
@ -149,6 +149,7 @@ services:
|
|||
VIKUNJA_DATABASE_TYPE: mysql
|
||||
VIKUNJA_DATABASE_USER: vikunja
|
||||
VIKUNJA_SERVICE_JWTSECRET: <generated secret>
|
||||
VIKUNJA_SERVICE_FRONTENDURL: https://<your public frontend url with slash>/
|
||||
volumes:
|
||||
- ./files:/app/vikunja/files
|
||||
db:
|
||||
|
|
|
@ -11,16 +11,20 @@ menu:
|
|||
|
||||
# Installing
|
||||
|
||||
Vikunja consists of two parts: [Backend](https://code.vikunja.io/api) and [frontend](https://code.vikunja.io/frontend).
|
||||
While the backend is required, the frontend is not.
|
||||
You don't neccesarily need to have a web-frontend, using Vikunja via the [mobile app](https://code.vikunja.io/app) is totally fine.
|
||||
Vikunja consists of two parts: [API](https://code.vikunja.io/api) and [frontend](https://code.vikunja.io/frontend).
|
||||
|
||||
However, using the web frontend is highly reccommended.
|
||||
You will always need to install at least the API.
|
||||
To actually use Vikunja you'll also need to somehow install a frontend to use it.
|
||||
You can either:
|
||||
|
||||
Vikunja can be installed in various forms.
|
||||
* [Install the web frontend]({{< ref "install-frontend.md">}})
|
||||
* Use the desktop app, which is essentially a web frontend packaged for easy installation on desktop devices
|
||||
* Use the mobile app only, but as of right now it only supports the very basic features of Vikunja
|
||||
|
||||
Vikunja can be installed in various ways.
|
||||
This document provides an overview and instructions for the different methods.
|
||||
|
||||
* [Backend]({{< ref "install-backend.md">}})
|
||||
* [API]({{< ref "install-backend.md">}})
|
||||
* [Installing from binary]({{< ref "install-backend.md#install-from-binary">}})
|
||||
* [Verify the GPG signature]({{< ref "install-backend.md#verify-the-gpg-signature">}})
|
||||
* [Set it up]({{< ref "install-backend.md#set-it-up">}})
|
||||
|
|
15
docs/content/doc/setup/k8s.md
Normal file
15
docs/content/doc/setup/k8s.md
Normal file
|
@ -0,0 +1,15 @@
|
|||
---
|
||||
title: "Hosting Vikunja with k8s"
|
||||
date: 2022-08-12T13:41:48+02:00
|
||||
draft: false
|
||||
type: "doc"
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "setup"
|
||||
---
|
||||
|
||||
There are two third-party Helm-Charts which can be used to host Vikunja with k8s:
|
||||
|
||||
* [Truecharts](https://truecharts.org/docs/charts/stable/vikunja/)
|
||||
* [k8s at Home](https://github.com/k8s-at-home/charts)
|
||||
|
45
docs/content/doc/setup/openid-examples.md
Normal file
45
docs/content/doc/setup/openid-examples.md
Normal file
|
@ -0,0 +1,45 @@
|
|||
---
|
||||
date: "2022-08-09:00:00+02:00"
|
||||
title: "OpenID example configurations"
|
||||
draft: false
|
||||
type: "doc"
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "setup"
|
||||
---
|
||||
|
||||
# OpenID example configurations
|
||||
|
||||
On this page you will find examples about how to set up Vikunja with a third-party OpenID provider.
|
||||
To add another example, please [edit this document](https://kolaente.dev/vikunja/api/src/branch/main/docs/content/doc/setup/openid-examples.md) and send a PR.
|
||||
|
||||
{{< table_of_contents >}}
|
||||
|
||||
## Authelia
|
||||
|
||||
Vikunja Config:
|
||||
|
||||
```yaml
|
||||
openid:
|
||||
enabled: true
|
||||
redirecturl: https://vikunja.mydomain.com/auth/openid/ <---- slash at the end is important
|
||||
providers:
|
||||
- name: Authelia
|
||||
authurl: https://login.mydomain.com
|
||||
clientid: <vikunja-id>
|
||||
clientsecret: <vikunja secret>
|
||||
```
|
||||
|
||||
Authelia config:
|
||||
|
||||
```yaml
|
||||
- id: <vikunja-id>
|
||||
description: Vikunja
|
||||
secret: <vikunja secret>
|
||||
redirect_uris:
|
||||
- https://vikunja.mydomain.com/auth/openid/authelia
|
||||
scopes:
|
||||
- openid
|
||||
- email
|
||||
- profile
|
||||
```
|
39
docs/content/doc/setup/subdirectory.md
Normal file
39
docs/content/doc/setup/subdirectory.md
Normal file
|
@ -0,0 +1,39 @@
|
|||
---
|
||||
title: "Running Vikunja in a subdirectory"
|
||||
date: 2022-09-23T12:15:04+02:00
|
||||
draft: false
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "setup"
|
||||
---
|
||||
|
||||
# Running Vikunja in a subdirectory
|
||||
|
||||
Running Vikunja in a subdirectory is not supported out of the box.
|
||||
However, you can still run it in a subdirectory but need to build the frontend yourself.
|
||||
|
||||
## Frontend
|
||||
|
||||
First, make sure you're able to build the frontend from source.
|
||||
Check [the guide about building from source]({{< ref "build-from-source.md">}}#frontend) about that.
|
||||
|
||||
Then, run
|
||||
|
||||
```
|
||||
pnpm vite build --base=/SUBPATH
|
||||
pnpm workbox copyLibraries dist/
|
||||
```
|
||||
|
||||
Where `SUBPATH` is the subdirectory you want to run Vikunja on.
|
||||
|
||||
Once you have the build files you can deploy them as usual.
|
||||
Note that when deploying in docker you'll need to put the files in a web container yourself, you
|
||||
can't use the `Dockerfile` in the repo without modifications.
|
||||
|
||||
## API
|
||||
|
||||
If you're not using a reverse proxy you're good to go.
|
||||
Simply configure the api url in the frontend as you normally would.
|
||||
|
||||
If you're using a reverse proxy you'll need to adjust the paths so that the api is available at `/SUBPATH/api/v1`.
|
||||
You can check if everything is working correctly by opening `/SUBPATH/api/v1/info` in a browser.
|
45
docs/content/doc/setup/versions.md
Normal file
45
docs/content/doc/setup/versions.md
Normal file
|
@ -0,0 +1,45 @@
|
|||
---
|
||||
date: "2022-07-07:00:00+02:00"
|
||||
title: "Versions"
|
||||
draft: false
|
||||
type: "doc"
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "setup"
|
||||
---
|
||||
|
||||
# Vikunja Versions
|
||||
|
||||
The Vikunja api and frontend are available in two different release flavors.
|
||||
|
||||
{{< table_of_contents >}}
|
||||
|
||||
## Stable
|
||||
|
||||
Stable releases have a fixed version number like `0.18.2` and are published at irregular intervals whenever a new version is ready.
|
||||
They receive few bugfixes and security patches.
|
||||
|
||||
We use [Semantic Versioning](#) for these releases.
|
||||
|
||||
## Unstable
|
||||
|
||||
Unstable versions are build every time a PR is merged or a commit to the main development branch is made.
|
||||
As such, they contain the current development code and are more likely to have bugs.
|
||||
There might be multiple new such builds a day.
|
||||
|
||||
Versions contain the last stable version, the number of commits since then and the commit the currently running binary was built from.
|
||||
They look like this: `v0.18.1+269-5cc4927b9e`
|
||||
|
||||
The demo instance at [try.vikunja.io](https://try.vikunja.io) automatically updates and always runs the last unstable build.
|
||||
|
||||
## Switching between versions
|
||||
|
||||
First you should create a backup of your current setup!
|
||||
|
||||
Switching between versions is the same process as [upgrading]({{< ref install-backend.md >}}#updating).
|
||||
Simply replace the stable binary with an unstable one or vice-versa.
|
||||
|
||||
For installations using docker, it is as simple as using the `unstable` or `latest` tag to switch between versions.
|
||||
|
||||
**Note:** While switching from stable to unstable should work without any problem, switching back might work but is not recommended and might break your instance.
|
||||
To switch from unstable back to stable the best way is to wait for the next stable release after the used unstable build and then upgrade to that.
|
|
@ -4,8 +4,8 @@ title: "Errors"
|
|||
draft: false
|
||||
type: "doc"
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "usage"
|
||||
sidebar:
|
||||
parent: "usage"
|
||||
---
|
||||
|
||||
# Errors
|
||||
|
@ -52,14 +52,16 @@ This document describes the different errors Vikunja can return.
|
|||
|
||||
## List
|
||||
|
||||
| ErrorCode | HTTP Status Code | Description |
|
||||
|-----------|------------------|-------------|
|
||||
| 3001 | 404 | The list does not exist. |
|
||||
| 3004 | 403 | The user needs to have read permissions on that list to perform that action. |
|
||||
| 3005 | 400 | The list title cannot be empty. |
|
||||
| 3006 | 404 | The list share does not exist. |
|
||||
| 3007 | 400 | A list with this identifier already exists. |
|
||||
| 3008 | 412 | The list is archived and can therefore only be accessed read only. This is also true for all tasks associated with this list. |
|
||||
| ErrorCode | HTTP Status Code | Description |
|
||||
|-----------|------------------|-------------------------------------------------------------------------------------------------------------------------------|
|
||||
| 3001 | 404 | The list does not exist. |
|
||||
| 3004 | 403 | The user needs to have read permissions on that list to perform that action. |
|
||||
| 3005 | 400 | The list title cannot be empty. |
|
||||
| 3006 | 404 | The list share does not exist. |
|
||||
| 3007 | 400 | A list with this identifier already exists. |
|
||||
| 3008 | 412 | The list is archived and can therefore only be accessed read only. This is also true for all tasks associated with this list. |
|
||||
| 3009 | 412 | The list cannot belong to a dynamically generated namespace like "Favorites". |
|
||||
| 3010 | 412 | The list must belong to a namespace. |
|
||||
|
||||
## Task
|
||||
|
||||
|
|
129
go.mod
129
go.mod
|
@ -20,66 +20,135 @@ require (
|
|||
code.vikunja.io/web v0.0.0-20210706160506-d85def955bd3
|
||||
gitea.com/xorm/xorm-redis-cache v0.2.0
|
||||
github.com/ThreeDotsLabs/watermill v1.1.1
|
||||
github.com/adlio/trello v1.9.0
|
||||
github.com/adlio/trello v1.10.0
|
||||
github.com/arran4/golang-ical v0.0.0-20220517104411-fd89fefb0182
|
||||
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef
|
||||
github.com/bbrks/go-blurhash v1.1.1
|
||||
github.com/beevik/etree v1.1.0 // indirect
|
||||
github.com/c2h5oh/datasize v0.0.0-20200825124411-48ed595a09d2
|
||||
github.com/coreos/go-oidc/v3 v3.2.0
|
||||
github.com/c2h5oh/datasize v0.0.0-20220606134207-859f65c6625b
|
||||
github.com/coreos/go-oidc/v3 v3.4.0
|
||||
github.com/cweill/gotests v1.6.0
|
||||
github.com/d4l3k/messagediff v1.2.1
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/dustinkirkland/golang-petname v0.0.0-20191129215211-8e5a1ed0cff0
|
||||
github.com/gabriel-vasile/mimetype v1.4.0
|
||||
github.com/gabriel-vasile/mimetype v1.4.1
|
||||
github.com/getsentry/sentry-go v0.13.0
|
||||
github.com/go-errors/errors v1.1.1 // indirect
|
||||
github.com/go-redis/redis/v8 v8.11.5
|
||||
github.com/go-sql-driver/mysql v1.6.0
|
||||
github.com/go-testfixtures/testfixtures/v3 v3.6.1
|
||||
github.com/go-testfixtures/testfixtures/v3 v3.8.1
|
||||
github.com/golang-jwt/jwt/v4 v4.4.2
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/iancoleman/strcase v0.2.0
|
||||
github.com/imdario/mergo v0.3.13
|
||||
github.com/labstack/echo/v4 v4.7.2
|
||||
github.com/jinzhu/copier v0.3.5
|
||||
github.com/labstack/echo/v4 v4.9.0
|
||||
github.com/labstack/gommon v0.3.1
|
||||
github.com/laurent22/ical-go v0.1.1-0.20181107184520-7e5d6ade8eef
|
||||
github.com/lib/pq v1.10.6
|
||||
github.com/lib/pq v1.10.7
|
||||
github.com/magefile/mage v1.13.0
|
||||
github.com/mattn/go-sqlite3 v1.14.13
|
||||
github.com/mattn/go-sqlite3 v1.14.15
|
||||
github.com/olekukonko/tablewriter v0.0.5
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||
github.com/pquerna/otp v1.3.0
|
||||
github.com/prometheus/client_golang v1.12.2
|
||||
github.com/prometheus/client_golang v1.13.0
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/samedi/caldav-go v3.0.0+incompatible
|
||||
github.com/spf13/afero v1.8.2
|
||||
github.com/spf13/afero v1.9.2
|
||||
github.com/spf13/cobra v1.5.0
|
||||
github.com/spf13/viper v1.11.0
|
||||
github.com/spf13/viper v1.13.0
|
||||
github.com/stretchr/testify v1.8.0
|
||||
github.com/swaggo/swag v1.8.3
|
||||
github.com/swaggo/swag v1.8.4
|
||||
github.com/tkuchiki/go-timezone v0.2.2
|
||||
github.com/ulule/limiter/v3 v3.10.0
|
||||
github.com/vectordotdev/go-datemath v0.1.1-0.20211214182920-0a4ac8742b93
|
||||
github.com/yuin/goldmark v1.4.12
|
||||
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4
|
||||
golang.org/x/image v0.0.0-20220302094943-723b81ca9867
|
||||
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
github.com/wneessen/go-mail v0.2.6
|
||||
github.com/yuin/goldmark v1.4.13
|
||||
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be
|
||||
golang.org/x/image v0.0.0-20220902085622-e7cb96979f69
|
||||
golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1
|
||||
golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0
|
||||
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec
|
||||
golang.org/x/term v0.0.0-20220919170432-7a66f970e087
|
||||
gopkg.in/d4l3k/messagediff.v1 v1.2.1
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
src.techknowlogick.com/xgo v1.4.1-0.20210311222705-d25c33fcd864
|
||||
src.techknowlogick.com/xgo v1.5.1-0.20220906164532-735bfdfb90d9
|
||||
src.techknowlogick.com/xormigrate v1.4.0
|
||||
xorm.io/builder v0.3.9
|
||||
xorm.io/xorm v1.1.2
|
||||
xorm.io/builder v0.3.12
|
||||
xorm.io/xorm v1.3.2
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // 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/cenkalti/backoff/v3 v3.0.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/fsnotify/fsnotify v1.5.4 // indirect
|
||||
github.com/garyburd/redigo v1.6.0 // indirect
|
||||
github.com/ghodss/yaml v1.0.0 // indirect
|
||||
github.com/go-chi/chi v4.0.2+incompatible // indirect
|
||||
github.com/go-errors/errors v1.1.1 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
github.com/go-openapi/jsonreference v0.19.6 // indirect
|
||||
github.com/go-openapi/spec v0.20.4 // indirect
|
||||
github.com/go-openapi/swag v0.19.15 // indirect
|
||||
github.com/goccy/go-json v0.9.11 // indirect
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/hashicorp/errwrap v1.0.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/laurent22/ical-go v0.1.1-0.20181107184520-7e5d6ade8eef // indirect
|
||||
github.com/lithammer/shortuuid/v3 v3.0.4 // indirect
|
||||
github.com/magiconair/properties v1.8.6 // indirect
|
||||
github.com/mailru/easyjson v0.7.6 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.16 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.9 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/oklog/ulid v1.3.1 // indirect
|
||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.5 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_model v0.2.0 // indirect
|
||||
github.com/prometheus/common v0.37.0 // indirect
|
||||
github.com/prometheus/procfs v0.8.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/spf13/cast v1.5.0 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/subosito/gotenv v1.4.1 // indirect
|
||||
github.com/syndtr/goleveldb v1.0.0 // indirect
|
||||
github.com/urfave/cli/v2 v2.3.0 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasttemplate v1.2.1 // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
|
||||
golang.org/x/net v0.0.0-20220927171203-f486391704dc // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/time v0.0.0-20220609170525-579cf78fd858 // indirect
|
||||
golang.org/x/tools v0.1.10 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
)
|
||||
|
||||
replace (
|
||||
github.com/adlio/trello => github.com/kolaente/trello v1.7.1-0.20201216234312-5c4ef79b531e
|
||||
github.com/coreos/bbolt => go.etcd.io/bbolt v1.3.4
|
||||
github.com/coreos/go-systemd => github.com/coreos/go-systemd/v22 v22.0.0
|
||||
github.com/hpcloud/tail => github.com/jeffbean/tail v1.0.1 // See https://github.com/hpcloud/tail/pull/159
|
||||
|
@ -87,4 +156,4 @@ replace (
|
|||
gopkg.in/fsnotify.v1 => github.com/kolaente/fsnotify v1.4.10-0.20200411160148-1bc3c8ff4048 // See https://github.com/fsnotify/fsnotify/issues/328 and https://github.com/golang/go/issues/26904
|
||||
)
|
||||
|
||||
go 1.15
|
||||
go 1.19
|
||||
|
|
16
magefile.go
16
magefile.go
|
@ -79,8 +79,10 @@ func runCmdWithOutput(name string, arg ...string) (output []byte, err error) {
|
|||
cmd := exec.Command(name, arg...)
|
||||
output, err = cmd.Output()
|
||||
if err != nil {
|
||||
ee := err.(*exec.ExitError)
|
||||
return nil, fmt.Errorf("error running command: %s, %s", string(ee.Stderr), err)
|
||||
if ee, is := err.(*exec.ExitError); is {
|
||||
return nil, fmt.Errorf("error running command: %s, %s", string(ee.Stderr), err)
|
||||
}
|
||||
return nil, fmt.Errorf("error running command: %s", err)
|
||||
}
|
||||
|
||||
return output, nil
|
||||
|
@ -350,7 +352,7 @@ func (Test) Unit() {
|
|||
mg.Deps(initVars)
|
||||
setApiPackages()
|
||||
// We run everything sequentially and not in parallel to prevent issues with real test databases
|
||||
args := append([]string{"test", Goflags[0], "-p", "1", "-coverprofile", "cover.out", "-timeout", "20m"}, ApiPackages...)
|
||||
args := append([]string{"test", Goflags[0], "-p", "1", "-coverprofile", "cover.out", "-timeout", "45m"}, ApiPackages...)
|
||||
runAndStreamOutput("go", args...)
|
||||
}
|
||||
|
||||
|
@ -365,7 +367,7 @@ func (Test) Coverage() {
|
|||
func (Test) Integration() {
|
||||
mg.Deps(initVars)
|
||||
// We run everything sequentially and not in parallel to prevent issues with real test databases
|
||||
runAndStreamOutput("go", "test", Goflags[0], "-p", "1", "-timeout", "20m", PACKAGE+"/pkg/integrations")
|
||||
runAndStreamOutput("go", "test", Goflags[0], "-p", "1", "-timeout", "45m", PACKAGE+"/pkg/integrations")
|
||||
}
|
||||
|
||||
type Check mg.Namespace
|
||||
|
@ -404,7 +406,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.31.0")
|
||||
fmt.Println("curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.47.3")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
@ -557,7 +559,9 @@ func (Release) Compress(ctx context.Context) error {
|
|||
return nil
|
||||
}
|
||||
// No mips or s390x for you today
|
||||
if strings.Contains(info.Name(), "mips") || strings.Contains(info.Name(), "s390x") {
|
||||
if strings.Contains(info.Name(), "mips") ||
|
||||
strings.Contains(info.Name(), "s390x") ||
|
||||
strings.Contains(info.Name(), "riscv64") { // not supported by upx
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
package caldav
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
@ -150,6 +149,15 @@ END:VCALENDAR` // Need a line break
|
|||
return
|
||||
}
|
||||
|
||||
func formatDuration(duration time.Duration) string {
|
||||
seconds := duration.Seconds() - duration.Minutes()*60
|
||||
minutes := duration.Minutes() - duration.Hours()*60
|
||||
|
||||
return strconv.FormatFloat(duration.Hours(), 'f', 0, 64) + `H` +
|
||||
strconv.FormatFloat(minutes, 'f', 0, 64) + `M` +
|
||||
strconv.FormatFloat(seconds, 'f', 0, 64) + `S`
|
||||
}
|
||||
|
||||
// ParseTodos returns a caldav vcalendar string with todos
|
||||
func ParseTodos(config *Config, todos []*Todo) (caldavtodos string) {
|
||||
caldavtodos = `BEGIN:VCALENDAR
|
||||
|
@ -172,11 +180,15 @@ SUMMARY:` + t.Summary + getCaldavColor(t.Color)
|
|||
|
||||
if t.Start.Unix() > 0 {
|
||||
caldavtodos += `
|
||||
DTSTART: ` + makeCalDavTimeFromTimeStamp(t.Start)
|
||||
DTSTART:` + makeCalDavTimeFromTimeStamp(t.Start)
|
||||
if t.Duration != 0 && t.DueDate.Unix() == 0 {
|
||||
caldavtodos += `
|
||||
DURATION:PT` + formatDuration(t.Duration)
|
||||
}
|
||||
}
|
||||
if t.End.Unix() > 0 {
|
||||
caldavtodos += `
|
||||
DTEND: ` + makeCalDavTimeFromTimeStamp(t.End)
|
||||
DTEND:` + makeCalDavTimeFromTimeStamp(t.End)
|
||||
}
|
||||
if t.Description != "" {
|
||||
re := regexp.MustCompile(`\r?\n`)
|
||||
|
@ -209,11 +221,6 @@ DUE:` + makeCalDavTimeFromTimeStamp(t.DueDate)
|
|||
CREATED:` + makeCalDavTimeFromTimeStamp(t.Created)
|
||||
}
|
||||
|
||||
if t.Duration != 0 {
|
||||
caldavtodos += `
|
||||
DURATION:PT` + fmt.Sprintf("%.6f", t.Duration.Hours()) + `H` + fmt.Sprintf("%.6f", t.Duration.Minutes()) + `M` + fmt.Sprintf("%.6f", t.Duration.Seconds()) + `S`
|
||||
}
|
||||
|
||||
if t.Priority != 0 {
|
||||
caldavtodos += `
|
||||
PRIORITY:` + strconv.Itoa(mapPriorityToCaldav(t.Priority))
|
||||
|
|
|
@ -23,7 +23,8 @@ import (
|
|||
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"github.com/laurent22/ical-go"
|
||||
|
||||
ics "github.com/arran4/golang-ical"
|
||||
)
|
||||
|
||||
func GetCaldavTodosForTasks(list *models.ListWithTasksAndBuckets, listTasks []*models.TaskWithComments) string {
|
||||
|
@ -60,21 +61,15 @@ func GetCaldavTodosForTasks(list *models.ListWithTasksAndBuckets, listTasks []*m
|
|||
}
|
||||
|
||||
func ParseTaskFromVTODO(content string) (vTask *models.Task, err error) {
|
||||
parsed, err := ical.ParseCalendar(content)
|
||||
parsed, err := ics.ParseCalendar(strings.NewReader(content))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We put the task details in a map to be able to handle them more easily
|
||||
task := make(map[string]string)
|
||||
for _, c := range parsed.Children {
|
||||
if c.Name == "VTODO" {
|
||||
for _, entry := range c.Children {
|
||||
task[entry.Name] = entry.Value
|
||||
}
|
||||
// Breaking, to only process the first task
|
||||
break
|
||||
}
|
||||
for _, c := range parsed.Components[0].UnknownPropertiesIANAProperties() {
|
||||
task[c.IANAToken] = c.Value
|
||||
}
|
||||
|
||||
// Parse the priority
|
||||
|
@ -91,10 +86,13 @@ func ParseTaskFromVTODO(content string) (vTask *models.Task, err error) {
|
|||
// Parse the enddate
|
||||
duration, _ := time.ParseDuration(task["DURATION"])
|
||||
|
||||
description := strings.ReplaceAll(task["DESCRIPTION"], "\\,", ",")
|
||||
description = strings.ReplaceAll(description, "\\n", "\n")
|
||||
|
||||
vTask = &models.Task{
|
||||
UID: task["UID"],
|
||||
Title: task["SUMMARY"],
|
||||
Description: task["DESCRIPTION"],
|
||||
Description: description,
|
||||
Priority: priority,
|
||||
DueDate: caldavTimeToTimestamp(task["DUE"]),
|
||||
Updated: caldavTimeToTimestamp(task["DTSTAMP"]),
|
||||
|
@ -125,6 +123,10 @@ func caldavTimeToTimestamp(tstring string) time.Time {
|
|||
format = `20060102T150405Z`
|
||||
}
|
||||
|
||||
if len(tstring) == 8 {
|
||||
format = `20060102`
|
||||
}
|
||||
|
||||
t, err := time.Parse(format, tstring)
|
||||
if err != nil {
|
||||
log.Warningf("Error while parsing caldav time %s to TimeStamp: %s", tstring, err)
|
||||
|
|
|
@ -96,6 +96,7 @@ const (
|
|||
MailerPort Key = `mailer.port`
|
||||
MailerUsername Key = `mailer.username`
|
||||
MailerPassword Key = `mailer.password`
|
||||
MailerAuthType Key = `mailer.authtype`
|
||||
MailerSkipTLSVerify Key = `mailer.skiptlsverify`
|
||||
MailerFromEmail Key = `mailer.fromemail`
|
||||
MailerQueuelength Key = `mailer.queuelength`
|
||||
|
@ -318,13 +319,14 @@ func InitDefaultConfig() {
|
|||
MailerEnabled.setDefault(false)
|
||||
MailerHost.setDefault("")
|
||||
MailerPort.setDefault("587")
|
||||
MailerUsername.setDefault("user")
|
||||
MailerUsername.setDefault("")
|
||||
MailerPassword.setDefault("")
|
||||
MailerSkipTLSVerify.setDefault(false)
|
||||
MailerFromEmail.setDefault("mail@vikunja")
|
||||
MailerQueuelength.setDefault(100)
|
||||
MailerQueueTimeout.setDefault(30)
|
||||
MailerForceSSL.setDefault(false)
|
||||
MailerAuthType.setDefault("plain")
|
||||
// Redis
|
||||
RedisEnabled.setDefault(false)
|
||||
RedisHost.setDefault("localhost:6379")
|
||||
|
|
|
@ -19,7 +19,6 @@ package db
|
|||
import (
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
@ -153,8 +152,8 @@ func initPostgresEngine() (engine *xorm.Engine, err error) {
|
|||
connStr := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=%s sslcert=%s sslkey=%s sslrootcert=%s",
|
||||
host,
|
||||
port,
|
||||
url.PathEscape(config.DatabaseUser.GetString()),
|
||||
url.PathEscape(config.DatabasePassword.GetString()),
|
||||
config.DatabaseUser.GetString(),
|
||||
config.DatabasePassword.GetString(),
|
||||
config.DatabaseDatabase.GetString(),
|
||||
config.DatabaseSslMode.GetString(),
|
||||
config.DatabaseSslCert.GetString(),
|
||||
|
|
|
@ -19,6 +19,8 @@ package db
|
|||
import (
|
||||
"encoding/json"
|
||||
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
|
||||
"xorm.io/xorm/schemas"
|
||||
)
|
||||
|
||||
|
@ -57,6 +59,15 @@ func Restore(table string, contents []map[string]interface{}) (err error) {
|
|||
}
|
||||
}
|
||||
|
||||
if Type() == schemas.POSTGRES {
|
||||
idSequence := table + "_id_seq"
|
||||
_, err = x.Query("SELECT setval('" + idSequence + "', COALESCE(MAX(id), 1) )")
|
||||
if err != nil {
|
||||
log.Warningf("Could not reset id sequence for %s: %s", idSequence, err)
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -227,7 +227,7 @@ func TestArchived(t *testing.T) {
|
|||
assertHandlerErrorCode(t, err, models.ErrCodeListIsArchived)
|
||||
})
|
||||
t.Run("unarchivable", func(t *testing.T) {
|
||||
rec, err := testListHandler.testUpdateWithUser(nil, map[string]string{"list": "22"}, `{"title":"LoremIpsum","is_archived":false}`)
|
||||
rec, err := testListHandler.testUpdateWithUser(nil, map[string]string{"list": "22"}, `{"title":"LoremIpsum","is_archived":false,"namespace_id":1}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"is_archived":false`)
|
||||
})
|
||||
|
|
|
@ -232,12 +232,12 @@ func TestLinkSharing(t *testing.T) {
|
|||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared write", func(t *testing.T) {
|
||||
rec, err := testHandlerListWrite.testUpdateWithLinkShare(nil, map[string]string{"list": "2"}, `{"title":"TestLoremIpsum"}`)
|
||||
rec, err := testHandlerListWrite.testUpdateWithLinkShare(nil, map[string]string{"list": "2"}, `{"title":"TestLoremIpsum","namespace_id":1}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
|
||||
})
|
||||
t.Run("Shared admin", func(t *testing.T) {
|
||||
rec, err := testHandlerListAdmin.testUpdateWithLinkShare(nil, map[string]string{"list": "3"}, `{"title":"TestLoremIpsum"}`)
|
||||
rec, err := testHandlerListAdmin.testUpdateWithLinkShare(nil, map[string]string{"list": "3"}, `{"title":"TestLoremIpsum","namespace_id":2}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
|
||||
})
|
||||
|
|
|
@ -171,7 +171,7 @@ func TestList(t *testing.T) {
|
|||
t.Run("Update", func(t *testing.T) {
|
||||
t.Run("Normal", func(t *testing.T) {
|
||||
// Check the list was loaded successfully afterwards, see testReadOneWithUser
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "1"}, `{"title":"TestLoremIpsum"}`)
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "1"}, `{"title":"TestLoremIpsum","namespace_id":1}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
|
||||
// The description should not be updated but returned correctly
|
||||
|
@ -183,7 +183,7 @@ func TestList(t *testing.T) {
|
|||
assertHandlerErrorCode(t, err, models.ErrCodeListDoesNotExist)
|
||||
})
|
||||
t.Run("Normal with updating the description", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "1"}, `{"title":"TestLoremIpsum","description":"Lorem Ipsum dolor sit amet"}`)
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "1"}, `{"title":"TestLoremIpsum","description":"Lorem Ipsum dolor sit amet","namespace_id":1}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
|
||||
assert.Contains(t, rec.Body.String(), `"description":"Lorem Ipsum dolor sit amet`)
|
||||
|
@ -211,12 +211,12 @@ func TestList(t *testing.T) {
|
|||
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{"list": "7"}, `{"title":"TestLoremIpsum"}`)
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "7"}, `{"title":"TestLoremIpsum","namespace_id":6}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
|
||||
})
|
||||
t.Run("Shared Via Team admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "8"}, `{"title":"TestLoremIpsum"}`)
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "8"}, `{"title":"TestLoremIpsum","namespace_id":6}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
|
||||
})
|
||||
|
@ -227,12 +227,12 @@ func TestList(t *testing.T) {
|
|||
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{"list": "10"}, `{"title":"TestLoremIpsum"}`)
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "10"}, `{"title":"TestLoremIpsum","namespace_id":6}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
|
||||
})
|
||||
t.Run("Shared Via User admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "11"}, `{"title":"TestLoremIpsum"}`)
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "11"}, `{"title":"TestLoremIpsum","namespace_id":6}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
|
||||
})
|
||||
|
@ -243,12 +243,12 @@ func TestList(t *testing.T) {
|
|||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "13"}, `{"title":"TestLoremIpsum"}`)
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "13"}, `{"title":"TestLoremIpsum","namespace_id":8}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "14"}, `{"title":"TestLoremIpsum"}`)
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "14"}, `{"title":"TestLoremIpsum","namespace_id":9}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
|
||||
})
|
||||
|
@ -259,12 +259,12 @@ func TestList(t *testing.T) {
|
|||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "16"}, `{"title":"TestLoremIpsum"}`)
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "16"}, `{"title":"TestLoremIpsum","namespace_id":11}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "17"}, `{"title":"TestLoremIpsum"}`)
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "17"}, `{"title":"TestLoremIpsum","namespace_id":12}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
|
||||
})
|
||||
|
|
|
@ -17,31 +17,68 @@
|
|||
package mail
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"time"
|
||||
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"gopkg.in/gomail.v2"
|
||||
|
||||
"github.com/wneessen/go-mail"
|
||||
)
|
||||
|
||||
// Queue is the mail queue
|
||||
var Queue chan *gomail.Message
|
||||
var Queue chan *mail.Msg
|
||||
|
||||
func getDialer() *gomail.Dialer {
|
||||
d := gomail.NewDialer(config.MailerHost.GetString(), config.MailerPort.GetInt(), config.MailerUsername.GetString(), config.MailerPassword.GetString())
|
||||
// #nosec
|
||||
d.TLSConfig = &tls.Config{
|
||||
InsecureSkipVerify: config.MailerSkipTLSVerify.GetBool(),
|
||||
ServerName: config.MailerHost.GetString(),
|
||||
func getClient() (*mail.Client, error) {
|
||||
|
||||
var authType mail.SMTPAuthType
|
||||
switch config.MailerAuthType.GetString() {
|
||||
case "plain":
|
||||
authType = mail.SMTPAuthPlain
|
||||
case "login":
|
||||
authType = mail.SMTPAuthLogin
|
||||
case "cram-md5":
|
||||
authType = mail.SMTPAuthCramMD5
|
||||
}
|
||||
d.SSL = config.MailerForceSSL.GetBool()
|
||||
return d
|
||||
|
||||
tlsPolicy := mail.TLSOpportunistic
|
||||
if config.MailerForceSSL.GetBool() {
|
||||
tlsPolicy = mail.TLSMandatory
|
||||
}
|
||||
|
||||
opts := []mail.Option{
|
||||
mail.WithPort(config.MailerPort.GetInt()),
|
||||
mail.WithTLSPolicy(tlsPolicy),
|
||||
//#nosec G402
|
||||
mail.WithTLSConfig(&tls.Config{
|
||||
InsecureSkipVerify: config.MailerSkipTLSVerify.GetBool(),
|
||||
ServerName: config.MailerHost.GetString(),
|
||||
}),
|
||||
mail.WithTimeout((config.MailerQueueTimeout.GetDuration() + 3) * time.Second), // 3s more for us to close before mail server timeout
|
||||
}
|
||||
|
||||
if config.MailerUsername.GetString() != "" && config.MailerPassword.GetString() != "" {
|
||||
opts = append(opts, mail.WithSMTPAuth(authType))
|
||||
}
|
||||
|
||||
if config.MailerUsername.GetString() != "" {
|
||||
opts = append(opts, mail.WithUsername(config.MailerUsername.GetString()))
|
||||
}
|
||||
|
||||
if config.MailerPassword.GetString() != "" {
|
||||
opts = append(opts, mail.WithPassword(config.MailerPassword.GetString()))
|
||||
}
|
||||
|
||||
return mail.NewClient(
|
||||
config.MailerHost.GetString(),
|
||||
opts...,
|
||||
)
|
||||
}
|
||||
|
||||
// StartMailDaemon starts the mail daemon
|
||||
func StartMailDaemon() {
|
||||
Queue = make(chan *gomail.Message, config.MailerQueuelength.GetInt())
|
||||
Queue = make(chan *mail.Msg, config.MailerQueuelength.GetInt())
|
||||
|
||||
if !config.MailerEnabled.GetBool() {
|
||||
return
|
||||
|
@ -52,10 +89,12 @@ func StartMailDaemon() {
|
|||
return
|
||||
}
|
||||
|
||||
c, err := getClient()
|
||||
if err != nil {
|
||||
log.Errorf("Could not create mail client: %v", err)
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
d := getDialer()
|
||||
|
||||
var s gomail.SendCloser
|
||||
var err error
|
||||
open := false
|
||||
for {
|
||||
|
@ -65,14 +104,16 @@ func StartMailDaemon() {
|
|||
return
|
||||
}
|
||||
if !open {
|
||||
if s, err = d.Dial(); err != nil {
|
||||
log.Error("Error during connect to smtp server: %s", err)
|
||||
err = c.DialWithContext(context.Background())
|
||||
if err != nil {
|
||||
log.Errorf("Error during connect to smtp server: %s", err)
|
||||
break
|
||||
}
|
||||
open = true
|
||||
}
|
||||
if err := gomail.Send(s, m); err != nil {
|
||||
log.Error("Error when sending mail: %s", err)
|
||||
err = c.Send(m)
|
||||
if err != nil {
|
||||
log.Errorf("Error when sending mail: %s", err)
|
||||
break
|
||||
}
|
||||
// Close the connection to the SMTP server if no email was sent in
|
||||
|
@ -80,18 +121,14 @@ func StartMailDaemon() {
|
|||
case <-time.After(config.MailerQueueTimeout.GetDuration() * time.Second):
|
||||
if open {
|
||||
open = false
|
||||
if err := s.Close(); err != nil {
|
||||
log.Error("Error closing the mail server connection: %s\n", err)
|
||||
err = c.Close()
|
||||
if err != nil {
|
||||
log.Errorf("Error closing the mail server connection: %s\n", err)
|
||||
break
|
||||
}
|
||||
log.Infof("Closed connection to mailserver")
|
||||
log.Info("Closed connection to mail server")
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// StopMailDaemon closes the mail queue channel, aka stops the daemon
|
||||
func StopMailDaemon() {
|
||||
close(Queue)
|
||||
}
|
||||
|
|
|
@ -17,9 +17,14 @@
|
|||
package mail
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"io"
|
||||
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"gopkg.in/gomail.v2"
|
||||
"code.vikunja.io/api/pkg/version"
|
||||
|
||||
"github.com/wneessen/go-mail"
|
||||
)
|
||||
|
||||
// Opts holds infos for a mail
|
||||
|
@ -32,6 +37,8 @@ type Opts struct {
|
|||
ContentType ContentType
|
||||
Boundary string
|
||||
Headers []*header
|
||||
Embeds map[string]io.Reader
|
||||
EmbedFS map[string]*embed.FS
|
||||
}
|
||||
|
||||
// ContentType represents mail content types
|
||||
|
@ -45,11 +52,11 @@ const (
|
|||
)
|
||||
|
||||
type header struct {
|
||||
Field string
|
||||
Field mail.Header
|
||||
Content string
|
||||
}
|
||||
|
||||
// SendTestMail sends a test mail to a receipient.
|
||||
// SendTestMail sends a test mail to a recipient.
|
||||
// It works without a queue.
|
||||
func SendTestMail(opts *Opts) error {
|
||||
if config.MailerHost.GetString() == "" {
|
||||
|
@ -57,39 +64,51 @@ func SendTestMail(opts *Opts) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
d := getDialer()
|
||||
s, err := d.Dial()
|
||||
c, err := getClient()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
m := sendMail(opts)
|
||||
m := getMessage(opts)
|
||||
|
||||
return gomail.Send(s, m)
|
||||
return c.DialAndSend(m)
|
||||
}
|
||||
|
||||
func sendMail(opts *Opts) *gomail.Message {
|
||||
m := gomail.NewMessage()
|
||||
func getMessage(opts *Opts) *mail.Msg {
|
||||
m := mail.NewMsg()
|
||||
m.SetUserAgent("Vikunja " + version.Version)
|
||||
if opts.From == "" {
|
||||
opts.From = "Vikunja <" + config.MailerFromEmail.GetString() + ">"
|
||||
}
|
||||
m.SetHeader("From", opts.From)
|
||||
m.SetHeader("To", opts.To)
|
||||
m.SetHeader("Subject", opts.Subject)
|
||||
_ = m.From(opts.From)
|
||||
_ = m.To(opts.To)
|
||||
m.Subject(opts.Subject)
|
||||
|
||||
for _, h := range opts.Headers {
|
||||
m.SetHeader(h.Field, h.Content)
|
||||
}
|
||||
|
||||
for name, content := range opts.Embeds {
|
||||
m.EmbedReader(name, content)
|
||||
}
|
||||
|
||||
for name, fs := range opts.EmbedFS {
|
||||
err := m.EmbedFromEmbedFS(name, fs)
|
||||
if err != nil {
|
||||
log.Errorf("Error embedding %s via embed.FS into mail: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
switch opts.ContentType {
|
||||
case ContentTypePlain:
|
||||
m.SetBody("text/plain", opts.Message)
|
||||
m.SetBodyString("text/plain", opts.Message)
|
||||
case ContentTypeHTML:
|
||||
m.SetBody("text/html", opts.Message)
|
||||
m.SetBodyString("text/html", opts.Message)
|
||||
case ContentTypeMultipart:
|
||||
m.SetBody("text/plain", opts.Message)
|
||||
m.AddAlternative("text/html", opts.HTMLMessage)
|
||||
m.SetBodyString("text/plain", opts.Message)
|
||||
m.AddAlternativeString("text/html", opts.HTMLMessage)
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
|
@ -100,6 +119,6 @@ func SendMail(opts *Opts) {
|
|||
return
|
||||
}
|
||||
|
||||
m := sendMail(opts)
|
||||
m := getMessage(opts)
|
||||
Queue <- m
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package migration
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"image"
|
||||
|
||||
"code.vikunja.io/api/pkg/files"
|
||||
|
@ -64,9 +65,12 @@ func init() {
|
|||
}
|
||||
|
||||
src, _, err := image.Decode(bgFile.File)
|
||||
if err != nil {
|
||||
if err != nil && !errors.Is(err, image.ErrFormat) {
|
||||
return err
|
||||
}
|
||||
if err != nil && errors.Is(err, image.ErrFormat) {
|
||||
log.Warningf("Could not generate a blur hash of list %d's background image: %s", l.ID, err)
|
||||
}
|
||||
|
||||
dst := image.NewRGBA(image.Rect(0, 0, 32, 32))
|
||||
draw.NearestNeighbor.Scale(dst, dst.Rect, src, src.Bounds(), draw.Over, nil)
|
||||
|
|
108
pkg/migration/20220815200851.go
Normal file
108
pkg/migration/20220815200851.go
Normal file
|
@ -0,0 +1,108 @@
|
|||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public Licensee as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public Licensee
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package migration
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"src.techknowlogick.com/xormigrate"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func init() {
|
||||
migrations = append(migrations, &xormigrate.Migration{
|
||||
ID: "20220815200851",
|
||||
Description: "Migrate saved assignee filter to usernames instead of IDs",
|
||||
Migrate: func(tx *xorm.Engine) error {
|
||||
filters := []map[string]interface{}{} // not using the type here so that the migration does not depend on it
|
||||
err := tx.Select("*").
|
||||
Table("saved_filters").
|
||||
Find(&filters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, f := range filters {
|
||||
filter := map[string]interface{}{}
|
||||
filterJSON, is := f["filters"].(string)
|
||||
if !is {
|
||||
continue
|
||||
}
|
||||
err = json.Unmarshal([]byte(filterJSON), &filter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
filterBy := filter["filter_by"].([]interface{})
|
||||
filterValue := filter["filter_value"].([]interface{})
|
||||
for p, fb := range filterBy {
|
||||
if fb == "assignees" || fb == "user_id" {
|
||||
userIDs := []int64{}
|
||||
for _, sid := range strings.Split(filterValue[p].(string), ",") {
|
||||
id, err := strconv.ParseInt(sid, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
userIDs = append(userIDs, id)
|
||||
}
|
||||
|
||||
usernames := []string{}
|
||||
err := tx.Select("username").
|
||||
Table("users").
|
||||
In("id", userIDs).
|
||||
Find(&usernames)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
userfilter := ""
|
||||
for i, username := range usernames {
|
||||
if i > 0 {
|
||||
userfilter += ","
|
||||
}
|
||||
userfilter += username
|
||||
}
|
||||
filterValue[p] = userfilter
|
||||
}
|
||||
}
|
||||
|
||||
filter["filter_value"] = filterValue
|
||||
filtersJSON, err := json.Marshal(filter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f["filters"] = string(filtersJSON)
|
||||
|
||||
_, err = tx.Where("id = ?", f["id"]).
|
||||
Cols("filters").
|
||||
NoAutoCondition().
|
||||
Table("saved_filters").
|
||||
Update(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
Rollback: func(tx *xorm.Engine) error {
|
||||
return nil
|
||||
},
|
||||
})
|
||||
}
|
|
@ -237,7 +237,7 @@ type ErrListIsArchived struct {
|
|||
ListID int64
|
||||
}
|
||||
|
||||
// IsErrListIsArchived checks if an error is a .
|
||||
// IsErrListIsArchived checks if an error is a list is archived error.
|
||||
func IsErrListIsArchived(err error) bool {
|
||||
_, ok := err.(ErrListIsArchived)
|
||||
return ok
|
||||
|
@ -255,6 +255,62 @@ func (err ErrListIsArchived) HTTPError() web.HTTPError {
|
|||
return web.HTTPError{HTTPCode: http.StatusPreconditionFailed, Code: ErrCodeListIsArchived, Message: "This list is archived. Editing or creating new tasks is not possible."}
|
||||
}
|
||||
|
||||
// ErrListCannotBelongToAPseudoNamespace represents an error where a list cannot belong to a pseudo namespace
|
||||
type ErrListCannotBelongToAPseudoNamespace struct {
|
||||
ListID int64
|
||||
NamespaceID int64
|
||||
}
|
||||
|
||||
// IsErrListCannotBelongToAPseudoNamespace checks if an error is a list is archived error.
|
||||
func IsErrListCannotBelongToAPseudoNamespace(err error) bool {
|
||||
_, ok := err.(*ErrListCannotBelongToAPseudoNamespace)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err *ErrListCannotBelongToAPseudoNamespace) Error() string {
|
||||
return fmt.Sprintf("List cannot belong to a pseudo namespace [ListID: %d, NamespaceID: %d]", err.ListID, err.NamespaceID)
|
||||
}
|
||||
|
||||
// ErrCodeListCannotBelongToAPseudoNamespace holds the unique world-error code of this error
|
||||
const ErrCodeListCannotBelongToAPseudoNamespace = 3009
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err *ErrListCannotBelongToAPseudoNamespace) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{
|
||||
HTTPCode: http.StatusPreconditionFailed,
|
||||
Code: ErrCodeListCannotBelongToAPseudoNamespace,
|
||||
Message: "This list cannot belong a dynamically generated namespace.",
|
||||
}
|
||||
}
|
||||
|
||||
// ErrListMustBelongToANamespace represents an error where a list must belong to a namespace
|
||||
type ErrListMustBelongToANamespace struct {
|
||||
ListID int64
|
||||
NamespaceID int64
|
||||
}
|
||||
|
||||
// IsErrListMustBelongToANamespace checks if an error is a list must belong to a namespace error.
|
||||
func IsErrListMustBelongToANamespace(err error) bool {
|
||||
_, ok := err.(*ErrListMustBelongToANamespace)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err *ErrListMustBelongToANamespace) Error() string {
|
||||
return fmt.Sprintf("List must belong to a namespace [ListID: %d, NamespaceID: %d]", err.ListID, err.NamespaceID)
|
||||
}
|
||||
|
||||
// ErrCodeListMustBelongToANamespace holds the unique world-error code of this error
|
||||
const ErrCodeListMustBelongToANamespace = 3010
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err *ErrListMustBelongToANamespace) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{
|
||||
HTTPCode: http.StatusPreconditionFailed,
|
||||
Code: ErrCodeListMustBelongToANamespace,
|
||||
Message: "This list must belong to a namespace.",
|
||||
}
|
||||
}
|
||||
|
||||
// ================
|
||||
// List task errors
|
||||
// ================
|
||||
|
|
|
@ -476,12 +476,22 @@ func addListDetails(s *xorm.Session, lists []*List, a web.Auth) (err error) {
|
|||
return err
|
||||
}
|
||||
|
||||
subscriptions, err := GetSubscriptions(s, SubscriptionEntityList, listIDs, a)
|
||||
if err != nil {
|
||||
log.Errorf("An error occurred while getting list subscriptions for a namespace item: %s", err.Error())
|
||||
subscriptions = make(map[int64]*Subscription)
|
||||
}
|
||||
|
||||
for _, list := range lists {
|
||||
// Don't override the favorite state if it was already set from before (favorite saved filters do this)
|
||||
if list.IsFavorite {
|
||||
continue
|
||||
}
|
||||
list.IsFavorite = favs[list.ID]
|
||||
|
||||
if subscription, exists := subscriptions[list.ID]; exists {
|
||||
list.Subscription = subscription
|
||||
}
|
||||
}
|
||||
|
||||
if len(fileIDs) == 0 {
|
||||
|
@ -543,6 +553,10 @@ func (l *List) CheckIsArchived(s *xorm.Session) (err error) {
|
|||
}
|
||||
|
||||
func checkListBeforeUpdateOrDelete(s *xorm.Session, list *List) error {
|
||||
if list.NamespaceID < 0 {
|
||||
return &ErrListCannotBelongToAPseudoNamespace{ListID: list.ID, NamespaceID: list.NamespaceID}
|
||||
}
|
||||
|
||||
// Check if the namespace exists
|
||||
if list.NamespaceID > 0 {
|
||||
_, err := GetNamespaceByID(s, list.NamespaceID)
|
||||
|
@ -626,6 +640,13 @@ func UpdateList(s *xorm.Session, list *List, auth web.Auth, updateListBackground
|
|||
return
|
||||
}
|
||||
|
||||
if list.NamespaceID == 0 {
|
||||
return &ErrListMustBelongToANamespace{
|
||||
ListID: list.ID,
|
||||
NamespaceID: list.NamespaceID,
|
||||
}
|
||||
}
|
||||
|
||||
// We need to specify the cols we want to update here to be able to un-archive lists
|
||||
colsToUpdate := []string{
|
||||
"title",
|
||||
|
|
|
@ -140,8 +140,9 @@ func TestList_CreateOrUpdate(t *testing.T) {
|
|||
db.LoadAndAssertFixtures(t)
|
||||
s := db.NewSession()
|
||||
list := List{
|
||||
ID: 99999999,
|
||||
Title: "test",
|
||||
ID: 99999999,
|
||||
Title: "test",
|
||||
NamespaceID: 1,
|
||||
}
|
||||
err := list.Update(s, usr)
|
||||
assert.Error(t, err)
|
||||
|
@ -221,6 +222,25 @@ func TestList_CreateOrUpdate(t *testing.T) {
|
|||
assert.False(t, can) // namespace is not writeable by us
|
||||
_ = s.Close()
|
||||
})
|
||||
t.Run("pseudo namespace", func(t *testing.T) {
|
||||
usr := &user.User{
|
||||
ID: 6,
|
||||
Username: "user6",
|
||||
Email: "user6@example.com",
|
||||
}
|
||||
|
||||
db.LoadAndAssertFixtures(t)
|
||||
s := db.NewSession()
|
||||
list := List{
|
||||
ID: 6,
|
||||
Title: "Test6",
|
||||
Description: "Lorem Ipsum",
|
||||
NamespaceID: -1,
|
||||
}
|
||||
err := list.Update(s, usr)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, IsErrListCannotBelongToAPseudoNamespace(err))
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
@ -335,7 +335,7 @@ func getListsForNamespaces(s *xorm.Session, namespaceIDs []int64, archived bool)
|
|||
func getSharedListsInNamespace(s *xorm.Session, archived bool, doer *user.User) (sharedListsNamespace *NamespaceWithLists, err error) {
|
||||
// Create our pseudo namespace to hold the shared lists
|
||||
sharedListsPseudonamespace := SharedListsPseudoNamespace
|
||||
sharedListsPseudonamespace.Owner = doer
|
||||
sharedListsPseudonamespace.OwnerID = doer.ID
|
||||
sharedListsNamespace = &NamespaceWithLists{
|
||||
sharedListsPseudonamespace,
|
||||
[]*List{},
|
||||
|
@ -385,12 +385,13 @@ func getSharedListsInNamespace(s *xorm.Session, archived bool, doer *user.User)
|
|||
func getFavoriteLists(s *xorm.Session, lists []*List, namespaceIDs []int64, doer *user.User) (favoriteNamespace *NamespaceWithLists, err error) {
|
||||
// Create our pseudo namespace with favorite lists
|
||||
pseudoFavoriteNamespace := FavoritesPseudoNamespace
|
||||
pseudoFavoriteNamespace.Owner = doer
|
||||
pseudoFavoriteNamespace.OwnerID = doer.ID
|
||||
favoriteNamespace = &NamespaceWithLists{
|
||||
Namespace: pseudoFavoriteNamespace,
|
||||
Lists: []*List{{}},
|
||||
}
|
||||
*favoriteNamespace.Lists[0] = FavoritesPseudoList // Copying the list to be able to modify it later
|
||||
favoriteNamespace.Lists[0].Owner = doer
|
||||
|
||||
for _, list := range lists {
|
||||
if !list.IsFavorite {
|
||||
|
@ -448,7 +449,7 @@ func getSavedFilters(s *xorm.Session, doer *user.User) (savedFiltersNamespace *N
|
|||
}
|
||||
|
||||
savedFiltersPseudoNamespace := SavedFiltersPseudoNamespace
|
||||
savedFiltersPseudoNamespace.Owner = doer
|
||||
savedFiltersPseudoNamespace.OwnerID = doer.ID
|
||||
savedFiltersNamespace = &NamespaceWithLists{
|
||||
Namespace: savedFiltersPseudoNamespace,
|
||||
Lists: make([]*List, 0, len(savedFilters)),
|
||||
|
@ -517,6 +518,7 @@ func (n *Namespace) ReadAll(s *xorm.Session, a web.Auth, search string, page int
|
|||
if err != nil {
|
||||
return nil, 0, 0, err
|
||||
}
|
||||
ownerMap[doer.ID] = doer
|
||||
|
||||
if n.NamespacesOnly {
|
||||
all := makeNamespaceSlice(namespaces, ownerMap, subscriptionsMap)
|
||||
|
|
|
@ -18,6 +18,7 @@ package models
|
|||
|
||||
import (
|
||||
"bufio"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -230,14 +231,23 @@ func (n *UndoneTaskOverdueNotification) Name() string {
|
|||
// UndoneTasksOverdueNotification represents a UndoneTasksOverdueNotification notification
|
||||
type UndoneTasksOverdueNotification struct {
|
||||
User *user.User
|
||||
Tasks []*Task
|
||||
Tasks map[int64]*Task
|
||||
}
|
||||
|
||||
// ToMail returns the mail notification for UndoneTasksOverdueNotification
|
||||
func (n *UndoneTasksOverdueNotification) ToMail() *notifications.Mail {
|
||||
|
||||
overdueLine := ""
|
||||
sortedTasks := make([]*Task, 0, len(n.Tasks))
|
||||
for _, task := range n.Tasks {
|
||||
sortedTasks = append(sortedTasks, task)
|
||||
}
|
||||
|
||||
sort.Slice(sortedTasks, func(i, j int) bool {
|
||||
return sortedTasks[i].DueDate.Before(sortedTasks[j].DueDate)
|
||||
})
|
||||
|
||||
overdueLine := ""
|
||||
for _, task := range sortedTasks {
|
||||
until := time.Until(task.DueDate).Round(1*time.Hour) * -1
|
||||
overdueLine += `* [` + task.Title + `](` + config.ServiceFrontendurl.GetString() + "tasks/" + strconv.FormatInt(task.ID, 10) + `), overdue since ` + utils.HumanizeDuration(until) + "\n"
|
||||
}
|
||||
|
|
|
@ -228,28 +228,53 @@ func getSubscriberCondForEntity(entityType SubscriptionEntityType, entityID int6
|
|||
// that task, if there is none it will look for a subscription on the list the task belongs to and if that also
|
||||
// doesn't exist it will check for a subscription for the namespace the list is belonging to.
|
||||
func GetSubscription(s *xorm.Session, entityType SubscriptionEntityType, entityID int64, a web.Auth) (subscription *Subscription, err error) {
|
||||
subs, err := GetSubscriptions(s, entityType, []int64{entityID}, a)
|
||||
if err != nil || len(subs) == 0 {
|
||||
return nil, err
|
||||
}
|
||||
if sub, exists := subs[entityID]; exists {
|
||||
return sub, nil // Take exact match first, if available
|
||||
}
|
||||
for _, sub := range subs {
|
||||
return sub, nil // For parents, take next available
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// GetSubscriptions returns a map of subscriptions to a set of given entity IDs
|
||||
func GetSubscriptions(s *xorm.Session, entityType SubscriptionEntityType, entityIDs []int64, a web.Auth) (listsToSubscriptions map[int64]*Subscription, err error) {
|
||||
u, is := a.(*user.User)
|
||||
if !is {
|
||||
return
|
||||
}
|
||||
|
||||
if err := entityType.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
subscription = &Subscription{}
|
||||
cond := getSubscriberCondForEntity(entityType, entityID)
|
||||
exists, err := s.
|
||||
var entitiesFilter builder.Cond
|
||||
for _, eID := range entityIDs {
|
||||
if entitiesFilter == nil {
|
||||
entitiesFilter = getSubscriberCondForEntity(entityType, eID)
|
||||
continue
|
||||
}
|
||||
entitiesFilter = entitiesFilter.Or(getSubscriberCondForEntity(entityType, eID))
|
||||
}
|
||||
|
||||
var subscriptions []*Subscription
|
||||
err = s.
|
||||
Where("user_id = ?", u.ID).
|
||||
And(cond).
|
||||
Get(subscription)
|
||||
if !exists {
|
||||
And(entitiesFilter).
|
||||
Find(&subscriptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
subscription.Entity = subscription.EntityType.String()
|
||||
|
||||
return subscription, err
|
||||
listsToSubscriptions = make(map[int64]*Subscription)
|
||||
for _, sub := range subscriptions {
|
||||
sub.Entity = sub.EntityType.String()
|
||||
listsToSubscriptions[sub.EntityID] = sub
|
||||
}
|
||||
return listsToSubscriptions, nil
|
||||
}
|
||||
|
||||
func getSubscribersForEntity(s *xorm.Session, entityType SubscriptionEntityType, entityID int64) (subscriptions []*Subscription, err error) {
|
||||
|
|
|
@ -214,6 +214,12 @@ func getNativeValueForTaskField(fieldName string, comparator taskFilterComparato
|
|||
return
|
||||
}
|
||||
|
||||
if realFieldName == "Assignees" {
|
||||
vals := strings.Split(value, ",")
|
||||
valueSlice := append([]string{}, vals...)
|
||||
return valueSlice, nil
|
||||
}
|
||||
|
||||
field, ok := reflect.TypeOf(&Task{}).Elem().FieldByName(realFieldName)
|
||||
if !ok {
|
||||
return nil, ErrInvalidTaskField{TaskField: fieldName}
|
||||
|
|
|
@ -934,10 +934,10 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
|||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "filter assignees",
|
||||
name: "filter assignees by username",
|
||||
fields: fields{
|
||||
FilterBy: []string{"assignees"},
|
||||
FilterValue: []string{"1"},
|
||||
FilterValue: []string{"user1"},
|
||||
FilterComparator: []string{"equals"},
|
||||
},
|
||||
args: defaultArgs,
|
||||
|
@ -947,12 +947,80 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
|||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "filter assignees in",
|
||||
name: "filter assignees by username with users field name",
|
||||
fields: fields{
|
||||
FilterBy: []string{"users"},
|
||||
FilterValue: []string{"user1"},
|
||||
FilterComparator: []string{"equals"},
|
||||
},
|
||||
args: defaultArgs,
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "filter assignees by username with user_id field name",
|
||||
fields: fields{
|
||||
FilterBy: []string{"user_id"},
|
||||
FilterValue: []string{"user1"},
|
||||
FilterComparator: []string{"equals"},
|
||||
},
|
||||
args: defaultArgs,
|
||||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "filter assignees by multiple username",
|
||||
fields: fields{
|
||||
FilterBy: []string{"assignees", "assignees"},
|
||||
FilterValue: []string{"user1", "user2"},
|
||||
FilterComparator: []string{"equals", "equals"},
|
||||
},
|
||||
args: defaultArgs,
|
||||
want: []*Task{
|
||||
task30,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "filter assignees by numbers",
|
||||
fields: fields{
|
||||
FilterBy: []string{"assignees"},
|
||||
FilterValue: []string{"1"},
|
||||
FilterComparator: []string{"equals"},
|
||||
},
|
||||
args: defaultArgs,
|
||||
want: []*Task{},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "filter assignees by name with like",
|
||||
fields: fields{
|
||||
FilterBy: []string{"assignees"},
|
||||
FilterValue: []string{"user"},
|
||||
FilterComparator: []string{"like"},
|
||||
},
|
||||
args: defaultArgs,
|
||||
want: []*Task{},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "filter assignees in by id",
|
||||
fields: fields{
|
||||
FilterBy: []string{"assignees"},
|
||||
FilterValue: []string{"1,2"},
|
||||
FilterComparator: []string{"in"},
|
||||
},
|
||||
args: defaultArgs,
|
||||
want: []*Task{},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "filter assignees in by username",
|
||||
fields: fields{
|
||||
FilterBy: []string{"assignees"},
|
||||
FilterValue: []string{"user1,user2"},
|
||||
FilterComparator: []string{"in"},
|
||||
},
|
||||
args: defaultArgs,
|
||||
want: []*Task{
|
||||
task30,
|
||||
|
|
|
@ -92,10 +92,10 @@ func getUndoneOverdueTasks(s *xorm.Session, now time.Time) (usersWithTasks map[i
|
|||
if !exists {
|
||||
uts[t.User.ID] = &userWithTasks{
|
||||
user: t.User,
|
||||
tasks: []*Task{},
|
||||
tasks: make(map[int64]*Task),
|
||||
}
|
||||
}
|
||||
uts[t.User.ID].tasks = append(uts[t.User.ID].tasks, t.Task)
|
||||
uts[t.User.ID].tasks[t.Task.ID] = t.Task
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -104,7 +104,7 @@ func getUndoneOverdueTasks(s *xorm.Session, now time.Time) (usersWithTasks map[i
|
|||
|
||||
type userWithTasks struct {
|
||||
user *user.User
|
||||
tasks []*Task
|
||||
tasks map[int64]*Task
|
||||
}
|
||||
|
||||
// RegisterOverdueReminderCron registers a function which checks once a day for tasks that are overdue and not done.
|
||||
|
|
|
@ -29,9 +29,11 @@ import (
|
|||
"code.vikunja.io/api/pkg/events"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"code.vikunja.io/api/pkg/utils"
|
||||
"code.vikunja.io/web"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/imdario/mergo"
|
||||
"github.com/jinzhu/copier"
|
||||
"xorm.io/builder"
|
||||
"xorm.io/xorm"
|
||||
"xorm.io/xorm/schemas"
|
||||
|
@ -64,7 +66,7 @@ type Task struct {
|
|||
// The list this task belongs to.
|
||||
ListID int64 `xorm:"bigint INDEX not null" json:"list_id" param:"list"`
|
||||
// An amount in seconds this task repeats itself. If this is set, when marking the task as done, it will mark itself as "undone" and then increase all remindes and the due date by its amount.
|
||||
RepeatAfter int64 `xorm:"bigint INDEX null" json:"repeat_after"`
|
||||
RepeatAfter int64 `xorm:"bigint INDEX null" json:"repeat_after" valid:"range(0|9223372036854775807)"`
|
||||
// Can have three possible values which will trigger when the task is marked as done: 0 = repeats after the amount specified in repeat_after, 1 = repeats all dates each months (ignoring repeat_after), 3 = repeats from the current date rather than the last set date.
|
||||
RepeatMode TaskRepeatMode `xorm:"not null default 0" json:"repeat_mode"`
|
||||
// The task priority. Can be anything you want, it is possible to sort by this later.
|
||||
|
@ -336,8 +338,11 @@ func getRawTasksForLists(s *xorm.Session, lists []*List, a web.Auth, opts *taskO
|
|||
continue
|
||||
}
|
||||
|
||||
if f.field == "assignees" || f.field == "user_id" {
|
||||
f.field = "user_id"
|
||||
if f.field == "assignees" {
|
||||
if f.comparator == taskFilterComparatorLike {
|
||||
return nil, 0, 0, ErrInvalidTaskFilterValue{Field: f.field, Value: f.value}
|
||||
}
|
||||
f.field = "username"
|
||||
filter, err := getFilterCond(f, opts.filterIncludeNulls)
|
||||
if err != nil {
|
||||
return nil, 0, 0, err
|
||||
|
@ -428,7 +433,13 @@ func getRawTasksForLists(s *xorm.Session, lists []*List, a web.Auth, opts *taskO
|
|||
}
|
||||
|
||||
if len(assigneeFilters) > 0 {
|
||||
filters = append(filters, getFilterCondForSeparateTable("task_assignees", opts.filterConcat, assigneeFilters))
|
||||
assigneeFilter := []builder.Cond{
|
||||
builder.In("user_id",
|
||||
builder.Select("id").
|
||||
From("users").
|
||||
Where(builder.Or(assigneeFilters...)),
|
||||
)}
|
||||
filters = append(filters, getFilterCondForSeparateTable("task_assignees", opts.filterConcat, assigneeFilter))
|
||||
}
|
||||
|
||||
if len(labelFilters) > 0 {
|
||||
|
@ -676,7 +687,17 @@ func addRelatedTasksToTasks(s *xorm.Session, taskIDs []int64, taskMap map[int64]
|
|||
continue
|
||||
}
|
||||
fullRelatedTasks[rt.OtherTaskID].IsFavorite = taskFavorites[rt.OtherTaskID]
|
||||
taskMap[rt.TaskID].RelatedTasks[rt.RelationKind] = append(taskMap[rt.TaskID].RelatedTasks[rt.RelationKind], fullRelatedTasks[rt.OtherTaskID])
|
||||
|
||||
// We're duplicating the other task to avoid cycles as these can't be represented properly in json
|
||||
// and would thus fail with an error.
|
||||
otherTask := &Task{}
|
||||
err = copier.Copy(otherTask, fullRelatedTasks[rt.OtherTaskID])
|
||||
if err != nil {
|
||||
log.Errorf("Could not duplicate task object: %v", err)
|
||||
continue
|
||||
}
|
||||
otherTask.RelatedTasks = nil
|
||||
taskMap[rt.TaskID].RelatedTasks[rt.RelationKind] = append(taskMap[rt.TaskID].RelatedTasks[rt.RelationKind], otherTask)
|
||||
}
|
||||
|
||||
return
|
||||
|
@ -886,7 +907,7 @@ func createTask(s *xorm.Session, t *Task, a web.Auth, updateAssignees bool) (err
|
|||
|
||||
// Generate a uuid if we don't already have one
|
||||
if t.UID == "" {
|
||||
t.UID = utils.MakeRandomString(40)
|
||||
t.UID = uuid.NewString()
|
||||
}
|
||||
|
||||
// Get the default bucket and move the task there
|
||||
|
@ -1268,18 +1289,39 @@ func setTaskDatesFromCurrentDateRepeat(oldTask, newTask *Task) {
|
|||
}
|
||||
}
|
||||
|
||||
// If a task has a start and end date, the end date should keep the difference to the start date when setting them as new
|
||||
if !oldTask.StartDate.IsZero() && !oldTask.EndDate.IsZero() {
|
||||
diff := oldTask.EndDate.Sub(oldTask.StartDate)
|
||||
newTask.StartDate = now.Add(repeatDuration)
|
||||
newTask.EndDate = now.Add(repeatDuration + diff)
|
||||
} else {
|
||||
if !oldTask.StartDate.IsZero() {
|
||||
// We want to preserve intervals among the due, start and end dates.
|
||||
// The due date is used as a reference point for all new dates, so the
|
||||
// behaviour depends on whether the due date is set at all.
|
||||
if oldTask.DueDate.IsZero() {
|
||||
// If a task has no due date, but does have a start and end date, the
|
||||
// end date should keep the difference to the start date when setting
|
||||
// them as new
|
||||
if !oldTask.StartDate.IsZero() && !oldTask.EndDate.IsZero() {
|
||||
diff := oldTask.EndDate.Sub(oldTask.StartDate)
|
||||
newTask.StartDate = now.Add(repeatDuration)
|
||||
newTask.EndDate = now.Add(repeatDuration + diff)
|
||||
} else {
|
||||
if !oldTask.StartDate.IsZero() {
|
||||
newTask.StartDate = now.Add(repeatDuration)
|
||||
}
|
||||
|
||||
if !oldTask.EndDate.IsZero() {
|
||||
newTask.EndDate = now.Add(repeatDuration)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If the old task has a start and due date, we set the new start date
|
||||
// to preserve the interval between them.
|
||||
if !oldTask.StartDate.IsZero() {
|
||||
diff := oldTask.DueDate.Sub(oldTask.StartDate)
|
||||
newTask.StartDate = newTask.DueDate.Add(-diff)
|
||||
}
|
||||
|
||||
// If the old task has an end and due date, we set the new end date
|
||||
// to preserve the interval between them.
|
||||
if !oldTask.EndDate.IsZero() {
|
||||
newTask.EndDate = now.Add(repeatDuration)
|
||||
diff := oldTask.DueDate.Sub(oldTask.EndDate)
|
||||
newTask.EndDate = newTask.DueDate.Add(-diff)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -96,28 +96,15 @@ func ListUsersFromList(s *xorm.Session, l *List, search string) (users []*user.U
|
|||
uids = append(uids, id)
|
||||
}
|
||||
|
||||
var cond builder.Cond = builder.Like{"username", "%" + search + "%"}
|
||||
var cond builder.Cond
|
||||
|
||||
if len(uids) > 0 {
|
||||
cond = builder.And(
|
||||
builder.In("id", uids),
|
||||
cond,
|
||||
)
|
||||
}
|
||||
|
||||
// Get all users
|
||||
err = s.
|
||||
Table("users").
|
||||
Select("*").
|
||||
Where(cond).
|
||||
GroupBy("id").
|
||||
OrderBy("id").
|
||||
Find(&users)
|
||||
|
||||
// Obfuscate all user emails
|
||||
for _, u := range users {
|
||||
u.Email = ""
|
||||
cond = builder.In("id", uids)
|
||||
}
|
||||
|
||||
users, err = user.ListUsers(s, search, &user.ListUserOpts{
|
||||
AdditionalCond: cond,
|
||||
ReturnAllIfNoSearchProvided: true,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
|
|
@ -214,6 +214,13 @@ func TestListUsersFromList(t *testing.T) {
|
|||
testuser13, // Shared Via NamespaceUser admin
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "search for user1",
|
||||
args: args{l: &List{ID: 19, OwnerID: 7}, search: "user1"},
|
||||
wantUsers: []*user.User{
|
||||
testuser1, // Shared Via Team readonly
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
|
|
@ -63,6 +63,7 @@ func GetAllProviders() (providers []*Provider, err error) {
|
|||
if err != nil {
|
||||
if provider != nil {
|
||||
log.Errorf("Error while getting openid provider %s: %s", provider.Name, err)
|
||||
continue
|
||||
}
|
||||
log.Errorf("Error while getting openid provider: %s", err)
|
||||
continue
|
||||
|
|
|
@ -17,10 +17,15 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"image"
|
||||
_ "image/gif" // To make sure the decoder used for generating blurHashes recognizes gifs
|
||||
_ "image/jpeg" // To make sure the decoder used for generating blurHashes recognizes jpgs
|
||||
_ "image/png" // To make sure the decoder used for generating blurHashes recognizes pngs
|
||||
|
||||
_ "golang.org/x/image/bmp" // To make sure the decoder used for generating blurHashes recognizes bmps
|
||||
_ "golang.org/x/image/tiff" // To make sure the decoder used for generating blurHashes recognizes tiffs
|
||||
_ "golang.org/x/image/webp" // To make sure the decoder used for generating blurHashes recognizes tiffs
|
||||
|
||||
"image"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
|
|
@ -20,6 +20,7 @@ import (
|
|||
"archive/zip"
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
|
@ -122,20 +123,36 @@ func Restore(filename string) error {
|
|||
return fmt.Errorf("could not read migrations: %w", err)
|
||||
}
|
||||
sort.Slice(ms, func(i, j int) bool {
|
||||
return ms[i].ID > ms[j].ID
|
||||
return ms[i].ID < ms[j].ID
|
||||
})
|
||||
|
||||
lastMigration := ms[len(ms)-1]
|
||||
lastMigration := ms[len(ms)-2]
|
||||
log.Debugf("Last migration: %s", lastMigration.ID)
|
||||
if err := migration.MigrateTo(lastMigration.ID, nil); err != nil {
|
||||
return fmt.Errorf("could not create db structure: %w", err)
|
||||
}
|
||||
|
||||
delete(dbfiles, "migration")
|
||||
|
||||
// Restore all db data
|
||||
for table, d := range dbfiles {
|
||||
content, err := unmarshalFileToJSON(d)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not read table %s: %w", table, err)
|
||||
}
|
||||
|
||||
// FIXME: There has to be a general way to do this but this works for now.
|
||||
if table == "notifications" {
|
||||
for i := range content {
|
||||
decoded, err := base64.StdEncoding.DecodeString(content[i]["notification"].(string))
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not decode notification %s: %w", content[i]["notification"], err)
|
||||
}
|
||||
|
||||
content[i]["notification"] = string(decoded)
|
||||
}
|
||||
}
|
||||
|
||||
if err := db.Restore(table, content); err != nil {
|
||||
return fmt.Errorf("could not restore table data for table %s: %w", table, err)
|
||||
}
|
||||
|
|
BIN
pkg/notifications/logo.png
Normal file
BIN
pkg/notifications/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.1 KiB |
|
@ -18,6 +18,8 @@ package notifications
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"embed"
|
||||
_ "embed"
|
||||
templatehtml "html/template"
|
||||
templatetext "text/template"
|
||||
|
||||
|
@ -49,7 +51,7 @@ const mailTemplateHTML = `
|
|||
<div style="width: 100%; font-family: 'Open Sans', sans-serif; text-rendering: optimizeLegibility">
|
||||
<div style="width: 600px; margin: 0 auto; text-align: justify;">
|
||||
<h1 style="font-size: 30px; text-align: center;">
|
||||
<img src="{{.FrontendURL}}images/logo-full.svg" style="height: 75px;" alt="Vikunja"/>
|
||||
<img src="cid:logo.png" style="height: 75px;" alt="Vikunja"/>
|
||||
</h1>
|
||||
<div style="border: 1px solid #dbdbdb; -webkit-box-shadow: 0.3em 0.3em 0.8em #e6e6e6; box-shadow: 0.3em 0.3em 0.8em #e6e6e6; color: #4a4a4a; padding: 5px 25px; border-radius: 3px; background: #fff;">
|
||||
<p>
|
||||
|
@ -84,6 +86,9 @@ const mailTemplateHTML = `
|
|||
</html>
|
||||
`
|
||||
|
||||
//go:embed logo.png
|
||||
var logo embed.FS
|
||||
|
||||
// RenderMail takes a precomposed mail message and renders it into a ready to send mail.Opts object
|
||||
func RenderMail(m *Mail) (mailOpts *mail.Opts, err error) {
|
||||
|
||||
|
@ -155,6 +160,9 @@ func RenderMail(m *Mail) (mailOpts *mail.Opts, err error) {
|
|||
Message: plainContent.String(),
|
||||
HTMLMessage: htmlContent.String(),
|
||||
Boundary: boundary,
|
||||
EmbedFS: map[string]*embed.FS{
|
||||
"logo.png": &logo,
|
||||
},
|
||||
}
|
||||
|
||||
return mailOpts, nil
|
||||
|
|
|
@ -127,7 +127,7 @@ And one more, because why not?
|
|||
<div style="width: 100%; font-family: 'Open Sans', sans-serif; text-rendering: optimizeLegibility">
|
||||
<div style="width: 600px; margin: 0 auto; text-align: justify;">
|
||||
<h1 style="font-size: 30px; text-align: center;">
|
||||
<img src="images/logo-full.svg" style="height: 75px;" alt="Vikunja"/>
|
||||
<img src="cid:logo.png" style="height: 75px;" alt="Vikunja"/>
|
||||
</h1>
|
||||
<div style="border: 1px solid #dbdbdb; -webkit-box-shadow: 0.3em 0.3em 0.8em #e6e6e6; box-shadow: 0.3em 0.3em 0.8em #e6e6e6; color: #4a4a4a; padding: 5px 25px; border-radius: 3px; background: #fff;">
|
||||
<p>
|
||||
|
|
|
@ -47,7 +47,7 @@ func UserList(c echo.Context) error {
|
|||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
users, err := user.ListUsers(s, search)
|
||||
users, err := user.ListUsers(s, search, nil)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err, c)
|
||||
|
|
|
@ -22,49 +22,61 @@ import (
|
|||
"code.vikunja.io/api/pkg/db"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"xorm.io/xorm"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
func BasicAuth(username, password string, c echo.Context) (bool, error) {
|
||||
creds := &user.Login{
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
credentials := &user.Login{
|
||||
Username: username,
|
||||
Password: password,
|
||||
}
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
u, err := user.CheckUserCredentials(s, creds)
|
||||
if err != nil && !user.IsErrWrongUsernameOrPassword(err) {
|
||||
log.Errorf("Error during basic auth for caldav: %v", err)
|
||||
var err error
|
||||
u, err := checkUserCaldavTokens(s, credentials)
|
||||
if user.IsErrUserDoesNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
if u == nil {
|
||||
u, err = user.CheckUserCredentials(s, credentials)
|
||||
if err != nil {
|
||||
log.Errorf("Error during basic auth for caldav: %v", err)
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
if u != nil && err == nil {
|
||||
c.Set("userBasicAuth", u)
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
tokens, err := user.GetCaldavTokens(u)
|
||||
func checkUserCaldavTokens(s *xorm.Session, login *user.Login) (*user.User, error) {
|
||||
usr, err := user.GetUserByUsername(s, login.Username)
|
||||
if err != nil || usr == nil {
|
||||
log.Warningf("Error while retrieving users from database: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
tokens, err := user.GetCaldavTokens(usr)
|
||||
if err != nil {
|
||||
log.Errorf("Error while getting tokens for caldav auth: %v", err)
|
||||
return false, nil
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Looping over all tokens until we find one that matches
|
||||
for _, token := range tokens {
|
||||
err = bcrypt.CompareHashAndPassword([]byte(token.Token), []byte(password))
|
||||
err = bcrypt.CompareHashAndPassword([]byte(token.Token), []byte(login.Password))
|
||||
if err != nil {
|
||||
if errors.Is(err, bcrypt.ErrMismatchedHashAndPassword) {
|
||||
continue
|
||||
}
|
||||
log.Errorf("Error while verifying tokens for caldav auth: %v", err)
|
||||
return false, nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
c.Set("userBasicAuth", u)
|
||||
return true, nil
|
||||
return usr, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
return nil, nil
|
||||
}
|
||||
|
|
|
@ -49,6 +49,7 @@ package routes
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -209,6 +210,25 @@ func registerAPIRoutes(a *echo.Group) {
|
|||
n := a.Group("")
|
||||
setupRateLimit(n, "ip")
|
||||
|
||||
// Echo does not unescape url path params by default. To make sure values bound as :param in urls are passed
|
||||
// properly to handlers, we use this middleware to unescape them.
|
||||
// See https://kolaente.dev/vikunja/api/issues/1224
|
||||
// See https://github.com/labstack/echo/issues/766
|
||||
a.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
params := make([]string, 0, len(c.ParamValues()))
|
||||
for _, param := range c.ParamValues() {
|
||||
p, err := url.PathUnescape(param)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
params = append(params, p)
|
||||
}
|
||||
c.SetParamValues(params...)
|
||||
return next(c)
|
||||
}
|
||||
})
|
||||
|
||||
// Docs
|
||||
n.GET("/docs.json", apiv1.DocsJSON)
|
||||
n.GET("/docs", apiv1.RedocUI)
|
||||
|
|
|
@ -452,3 +452,30 @@ func (err *ErrAccountDisabled) HTTPError() web.HTTPError {
|
|||
Message: "This account is disabled. Check your emails or ask your administrator.",
|
||||
}
|
||||
}
|
||||
|
||||
// ErrAccountIsNotLocal represents a "AccountIsNotLocal" kind of error.
|
||||
type ErrAccountIsNotLocal struct {
|
||||
UserID int64
|
||||
}
|
||||
|
||||
// IsErrAccountIsNotLocal checks if an error is a ErrAccountIsNotLocal.
|
||||
func IsErrAccountIsNotLocal(err error) bool {
|
||||
_, ok := err.(*ErrAccountIsNotLocal)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err *ErrAccountIsNotLocal) Error() string {
|
||||
return "Account is not local"
|
||||
}
|
||||
|
||||
// ErrCodeAccountIsNotLocal holds the unique world-error code of this error
|
||||
const ErrCodeAccountIsNotLocal = 1021
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err *ErrAccountIsNotLocal) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{
|
||||
HTTPCode: http.StatusPreconditionFailed,
|
||||
Code: ErrCodeAccountIsNotLocal,
|
||||
Message: "This account is managed by a third-party authentication provider.",
|
||||
}
|
||||
}
|
||||
|
|
|
@ -200,7 +200,7 @@ func (apiUser *APIUserPassword) APIFormat() *User {
|
|||
|
||||
// GetUserByID gets informations about a user by its ID
|
||||
func GetUserByID(s *xorm.Session, id int64) (user *User, err error) {
|
||||
// Apparently xorm does otherwise look for all users but return only one, which leads to returing one even if the ID is 0
|
||||
// Apparently xorm does otherwise look for all users but return only one, which leads to returning one even if the ID is 0
|
||||
if id < 1 {
|
||||
return &User{}, ErrUserDoesNotExist{}
|
||||
}
|
||||
|
@ -280,6 +280,10 @@ func getUser(s *xorm.Session, user *User, withEmail bool) (userOut *User, err er
|
|||
userOut.Email = ""
|
||||
}
|
||||
|
||||
if userOut.OverdueTasksRemindersTime == "" {
|
||||
userOut.OverdueTasksRemindersTime = "9:00"
|
||||
}
|
||||
|
||||
return userOut, err
|
||||
}
|
||||
|
||||
|
@ -314,6 +318,10 @@ func CheckUserCredentials(s *xorm.Session, u *Login) (*User, error) {
|
|||
return nil, ErrWrongUsernameOrPassword{}
|
||||
}
|
||||
|
||||
if user.Issuer != IssuerLocal {
|
||||
return user, &ErrAccountIsNotLocal{UserID: user.ID}
|
||||
}
|
||||
|
||||
// The user is invalid if they need to verify their email address
|
||||
if user.Status == StatusEmailConfirmationRequired {
|
||||
return &User{}, ErrEmailNotConfirmed{UserID: user.ID}
|
||||
|
|
|
@ -20,7 +20,9 @@ import (
|
|||
"testing"
|
||||
|
||||
"code.vikunja.io/api/pkg/db"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
func TestCreateUser(t *testing.T) {
|
||||
|
@ -362,7 +364,7 @@ func TestListUsers(t *testing.T) {
|
|||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
all, err := ListUsers(s, "user1")
|
||||
all, err := ListUsers(s, "user1", nil)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, len(all) > 0)
|
||||
assert.Equal(t, all[0].Username, "user1")
|
||||
|
@ -381,7 +383,7 @@ func TestListUsers(t *testing.T) {
|
|||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
all, err := ListUsers(s, "")
|
||||
all, err := ListUsers(s, "", nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, all, 0)
|
||||
})
|
||||
|
@ -390,11 +392,12 @@ func TestListUsers(t *testing.T) {
|
|||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
all, err := ListUsers(s, "user1@example.com")
|
||||
all, err := ListUsers(s, "user1@example.com", nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, all, 0)
|
||||
db.AssertExists(t, "users", map[string]interface{}{
|
||||
"email": "user1@example.com",
|
||||
"email": "user1@example.com",
|
||||
"discoverable_by_email": false,
|
||||
}, false)
|
||||
})
|
||||
t.Run("not discoverable by name", func(t *testing.T) {
|
||||
|
@ -402,11 +405,12 @@ func TestListUsers(t *testing.T) {
|
|||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
all, err := ListUsers(s, "one else")
|
||||
all, err := ListUsers(s, "one else", nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, all, 0)
|
||||
db.AssertExists(t, "users", map[string]interface{}{
|
||||
"name": "Some one else",
|
||||
"name": "Some one else",
|
||||
"discoverable_by_name": false,
|
||||
}, false)
|
||||
})
|
||||
t.Run("discoverable by email", func(t *testing.T) {
|
||||
|
@ -414,20 +418,67 @@ func TestListUsers(t *testing.T) {
|
|||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
all, err := ListUsers(s, "user7@example.com")
|
||||
all, err := ListUsers(s, "user7@example.com", nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, all, 1)
|
||||
assert.Equal(t, int64(7), all[0].ID)
|
||||
db.AssertExists(t, "users", map[string]interface{}{
|
||||
"email": "user7@example.com",
|
||||
"discoverable_by_email": true,
|
||||
}, false)
|
||||
})
|
||||
t.Run("discoverable by partial name", func(t *testing.T) {
|
||||
db.LoadAndAssertFixtures(t)
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
all, err := ListUsers(s, "with space")
|
||||
all, err := ListUsers(s, "with space", nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, all, 1)
|
||||
assert.Equal(t, int64(12), all[0].ID)
|
||||
db.AssertExists(t, "users", map[string]interface{}{
|
||||
"name": "Name with spaces",
|
||||
"discoverable_by_name": true,
|
||||
}, false)
|
||||
})
|
||||
t.Run("discoverable by email with extra condition", func(t *testing.T) {
|
||||
db.LoadAndAssertFixtures(t)
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
all, err := ListUsers(s, "user7@example.com", &ListUserOpts{AdditionalCond: builder.In("id", 7)})
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, all, 1)
|
||||
assert.Equal(t, int64(7), all[0].ID)
|
||||
db.AssertExists(t, "users", map[string]interface{}{
|
||||
"email": "user7@example.com",
|
||||
"discoverable_by_email": true,
|
||||
}, false)
|
||||
})
|
||||
t.Run("discoverable by exact username", func(t *testing.T) {
|
||||
db.LoadAndAssertFixtures(t)
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
all, err := ListUsers(s, "user7", nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, all, 1)
|
||||
assert.Equal(t, int64(7), all[0].ID)
|
||||
db.AssertExists(t, "users", map[string]interface{}{
|
||||
"username": "user7",
|
||||
}, false)
|
||||
})
|
||||
t.Run("not discoverable by partial username", func(t *testing.T) {
|
||||
db.LoadAndAssertFixtures(t)
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
all, err := ListUsers(s, "user", nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, all, 0)
|
||||
db.AssertExists(t, "users", map[string]interface{}{
|
||||
"username": "user7",
|
||||
}, false)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -23,29 +23,58 @@ import (
|
|||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
// ListUsers returns a list with all users, filtered by an optional searchstring
|
||||
func ListUsers(s *xorm.Session, search string) (users []*User, err error) {
|
||||
type ListUserOpts struct {
|
||||
AdditionalCond builder.Cond
|
||||
ReturnAllIfNoSearchProvided bool
|
||||
}
|
||||
|
||||
// ListUsers returns a list with all users, filtered by an optional search string
|
||||
func ListUsers(s *xorm.Session, search string, opts *ListUserOpts) (users []*User, err error) {
|
||||
if opts == nil {
|
||||
opts = &ListUserOpts{}
|
||||
}
|
||||
|
||||
// Prevent searching for placeholders
|
||||
search = strings.ReplaceAll(search, "%", "")
|
||||
|
||||
if search == "" || strings.ReplaceAll(search, " ", "") == "" {
|
||||
if (search == "" || strings.ReplaceAll(search, " ", "") == "") && !opts.ReturnAllIfNoSearchProvided {
|
||||
return
|
||||
}
|
||||
|
||||
conds := []builder.Cond{}
|
||||
|
||||
if search != "" {
|
||||
for _, queryPart := range strings.Split(search, ",") {
|
||||
conds = append(conds,
|
||||
builder.Eq{"username": queryPart},
|
||||
builder.And(
|
||||
builder.Eq{"email": queryPart},
|
||||
builder.Eq{"discoverable_by_email": true},
|
||||
),
|
||||
builder.And(
|
||||
builder.Like{"name", "%" + queryPart + "%"},
|
||||
builder.Eq{"discoverable_by_name": true},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
cond := builder.Or(conds...)
|
||||
|
||||
if opts.AdditionalCond != nil {
|
||||
cond = builder.And(
|
||||
cond,
|
||||
opts.AdditionalCond,
|
||||
)
|
||||
}
|
||||
|
||||
err = s.
|
||||
Where(builder.Or(
|
||||
builder.Like{"username", "%" + search + "%"},
|
||||
builder.And(
|
||||
builder.Eq{"email": search},
|
||||
builder.Eq{"discoverable_by_email": true},
|
||||
),
|
||||
builder.And(
|
||||
builder.Like{"name", "%" + search + "%"},
|
||||
builder.Eq{"discoverable_by_name": true},
|
||||
),
|
||||
)).
|
||||
Where(cond).
|
||||
Find(&users)
|
||||
|
||||
for _, u := range users {
|
||||
u.Email = ""
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user