forked from vikunja/vikunja
Compare commits
327 Commits
Author | SHA1 | Date | |
---|---|---|---|
6aadaaaffc | |||
6566f0e81d | |||
6d8db0ce1e | |||
085b9222bb | |||
a0b3a444df | |||
8916de0366 | |||
769db0dab2 | |||
259cf7d25b | |||
8dc6c95333 | |||
869d4a336c | |||
7a9611c2da | |||
7cab3a77a9 | |||
77ad90d53e | |||
55410ea73d | |||
e4f841cf6a | |||
2940eae1aa | |||
0a3fdc0344 | |||
06f1d2e912 | |||
61a3380a94 | |||
fb818ea186 | |||
7e53a21407 | |||
8f4abd2fe8 | |||
2fba7bdf02 | |||
349e6a5905 | |||
80266d1383 | |||
c0c523f0a8 | |||
672fb35bcb | |||
1f13b5d7b4 | |||
e79778e213 | |||
a897ffc8ee | |||
4de0efec1d | |||
09ddd5a31a | |||
387aa2db93 | |||
4f62e978ef | |||
a5c241758a | |||
abf38defa2 | |||
29f8522de9 | |||
9f14466dfa | |||
4a82f3be3c | |||
a39b3aeb06 | |||
b4c215f4dd | |||
f0a8516926 | |||
077baba2ea | |||
066c26f83e | |||
eda5135b3c | |||
906c9fc06b | |||
e37dbb1648 | |||
cd13f86e0d | |||
7ad754dd3b | |||
|
a62b57ac62 | ||
534d04a1db | |||
e8c85562b1 | |||
|
1afc72e190 | ||
|
53197b85e3 | ||
d47535b831 | |||
c9ce9c2382 | |||
168287923f | |||
ca6d1946da | |||
8b745ad599 | |||
1efa1696bf | |||
6908167fb2 | |||
26b280019b | |||
d19529e84f | |||
0f7f595cc3 | |||
c6769d407e | |||
8df94e8800 | |||
5af1147e0d | |||
8bf224d392 | |||
0a0374e268 | |||
da9d25cf72 | |||
eb33655c1c | |||
20a5994b17 | |||
5a0cee2fc5 | |||
fceb5dae0f | |||
659803fadf | |||
4fbe2b313b | |||
a6a47a27cb | |||
247f678491 | |||
7d796cea0a | |||
984bcb90e1 | |||
6bbcd1399f | |||
58da38adb6 | |||
4a05d1a497 | |||
d6643f5af3 | |||
aa25ccdc91 | |||
cb96590611 | |||
5d242f7e54 | |||
02352841dc | |||
d682a22cd5 | |||
fdbe110945 | |||
7b46446e03 | |||
3b1887e438 | |||
32ff4b2cbd | |||
6ddadba573 | |||
|
afdceb0aff | ||
437960b146 | |||
75baba32a6 | |||
9432b437fe | |||
ef8e97f95e | |||
522bf7d2fc | |||
|
88dd544fc9 | ||
|
f660badc3d | ||
491a142378 | |||
46b261c9fe | |||
40411e4100 | |||
f2b4e9260b | |||
682123a9c9 | |||
0cd9cd324e | |||
100897cc9d | |||
c59e006453 | |||
6a87d919fa | |||
d52816c7d7 | |||
87d0134bb2 | |||
d19fc80b8b | |||
fecce19f06 | |||
1971df7b84 | |||
31a1452839 | |||
530bb0a63c | |||
7bf7a13bb9 | |||
10e6843b11 | |||
78f43829cf | |||
acd31c4ff7 | |||
1c02114cf5 | |||
82f4a5ad50 | |||
04614614fe | |||
608bde9806 | |||
568cc16797 | |||
|
4b5e65d4c2 | ||
3329d83363 | |||
c4e5e722e4 | |||
|
508a3157e2 | ||
|
321a8f7e2b | ||
dd7dcdd0cc | |||
cb6368036c | |||
36dfcb8ddb | |||
7f8c85118e | |||
f3476bec6c | |||
392bdd1b94 | |||
80634d43c1 | |||
4fa45bf9dc | |||
ef1d1e2b20 | |||
|
ca3580766e | ||
c6429c8b13 | |||
304481cf28 | |||
897a6e5d5c | |||
194b88e2eb | |||
c5327845ee | |||
ea1d06bda6 | |||
0104aa504b | |||
6a97a214a3 | |||
a79b1de2d0 | |||
e9ce930230 | |||
6cb48e430e | |||
a2c8426d02 | |||
3be10ca4a2 | |||
ec297009d3 | |||
dbc30284f3 | |||
879324dcd0 | |||
3be6e93a05 | |||
f93317bf5d | |||
1cfdb085e5 | |||
51cf8beaed | |||
b8c3b570a4 | |||
8ae062a095 | |||
941d1e06c5 | |||
1f2eb57602 | |||
51911a8868 | |||
47aae115df | |||
fbc4b91e0f | |||
8c67be558f | |||
e27cd9b336 | |||
23b01a1ff6 | |||
f47faf577a | |||
312525ebef | |||
a17d2f4288 | |||
7e0aa20658 | |||
c5a55e39bf | |||
4d4ffe8b34 | |||
3d9fcb9ffb | |||
a6e214b654 | |||
c47e07f9b0 | |||
3a4a04ee8e | |||
96b5e93379 | |||
87c2e442f2 | |||
33e27c66a0 | |||
986129a784 | |||
3d7605591e | |||
811514855b | |||
a9e6776abf | |||
15828df041 | |||
f9b48ec091 | |||
3b0b4a8460 | |||
2ef5e54588 | |||
65bca226e0 | |||
63a9148132 | |||
b880d0e300 | |||
2c46fc25d4 | |||
641a9da93d | |||
4d4dca12ef | |||
622f2f0562 | |||
c495096444 | |||
649d1e3e6f | |||
c83cb8480d | |||
556abcd9d2 | |||
b10dbce1a1 | |||
7b77974b03 | |||
05358350af | |||
9fc08a0790 | |||
f6b897e8e7 | |||
8de78c48f8 | |||
a13126d1dd | |||
b96e681270 | |||
f5fd849a0b | |||
144e115394 | |||
815fc10135 | |||
955a1771ae | |||
aca930655b | |||
2ba78d240f | |||
cad18945bb | |||
9fc0fc184d | |||
1577c8d3f3 | |||
2eb4d07aa9 | |||
4789a69455 | |||
35f01a4549 | |||
dca51c762b | |||
6515dd6908 | |||
0ea4de3f56 | |||
c2104a3374 | |||
aaceb4e968 | |||
4ec4c0a65d | |||
c68bd235e8 | |||
df2e36c2a3 | |||
f5a33478f2 | |||
0d044997df | |||
5e40f4ec89 | |||
5871d32c2d | |||
3af9855148 | |||
e5394d6d4b | |||
b8769c746c | |||
b331fdd29a | |||
2fc690a783 | |||
bcb286a7f0 | |||
008908eb49 | |||
12e0e12bae | |||
d43762e9d9 | |||
631a265d2d | |||
e113fe34d0 | |||
0eb47096db | |||
0e1904d50b | |||
b4b25499f2 | |||
b735ffc4b3 | |||
95105aaa35 | |||
66331b1002 | |||
ed6a27da6a | |||
b5ee39b887 | |||
0d8451ab6e | |||
81f09f7dc0 | |||
5a40100ac5 | |||
0694314e52 | |||
a547a9eb25 | |||
0fcd03f561 | |||
ffedd02b08 | |||
c84684a425 | |||
aed560339b | |||
0612f4d0e0 | |||
ce621ee5d6 | |||
9c4bb5a244 | |||
c076f73a87 | |||
|
36265fcedf | ||
53419180be | |||
c5bd09702a | |||
fcb205a842 | |||
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 |
|
@ -1,4 +1,7 @@
|
|||
files/
|
||||
dist/
|
||||
logs/
|
||||
|
||||
Dockerfile
|
||||
docker-manifest.tmpl
|
||||
docker-manifest-unstable.tmpl
|
||||
|
|
316
.drone.yml
316
.drone.yml
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: testing
|
||||
|
||||
workspace:
|
||||
|
@ -111,7 +112,7 @@ steps:
|
|||
# compiling the same magefile at the same time. It's also faster if each step does not need to compile it first.
|
||||
- name: mage
|
||||
image: vikunja/golang-build:latest
|
||||
pull: true
|
||||
pull: always
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
commands:
|
||||
|
@ -122,7 +123,7 @@ steps:
|
|||
|
||||
- name: build
|
||||
image: vikunja/golang-build:latest
|
||||
pull: true
|
||||
pull: always
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
depends_on: [ mage ]
|
||||
|
@ -132,21 +133,22 @@ steps:
|
|||
event: [ push, tag, pull_request ]
|
||||
|
||||
- name: lint
|
||||
image: golang:1.17-alpine
|
||||
pull: true
|
||||
image: golang:1.19-alpine
|
||||
pull: always
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
depends_on: [ build ]
|
||||
commands:
|
||||
- export "GOROOT=$(go env GOROOT)"
|
||||
- apk --no-cache add build-base git
|
||||
- wget -O - -q https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.47.3
|
||||
- wget -O - -q https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.51.2
|
||||
- ./mage-static check:all
|
||||
when:
|
||||
event: [ push, tag, pull_request ]
|
||||
|
||||
- name: test-migration-prepare
|
||||
image: kolaente/toolbox:latest
|
||||
pull: true
|
||||
pull: always
|
||||
commands:
|
||||
# Get the latest version
|
||||
- wget https://dl.vikunja.io/api/unstable/vikunja-unstable-linux-amd64-full.zip -q -O vikunja-latest.zip
|
||||
|
@ -154,7 +156,7 @@ steps:
|
|||
|
||||
- name: test-migration-sqlite
|
||||
image: vikunja/golang-build:latest
|
||||
pull: true
|
||||
pull: always
|
||||
depends_on: [ test-migration-prepare, build ]
|
||||
environment:
|
||||
VIKUNJA_DATABASE_TYPE: sqlite
|
||||
|
@ -173,7 +175,7 @@ steps:
|
|||
|
||||
- name: test-migration-mysql
|
||||
image: vikunja/golang-build:latest
|
||||
pull: true
|
||||
pull: always
|
||||
depends_on: [ test-migration-prepare, build ]
|
||||
environment:
|
||||
VIKUNJA_DATABASE_TYPE: mysql
|
||||
|
@ -192,7 +194,7 @@ steps:
|
|||
|
||||
- name: test-migration-psql
|
||||
image: vikunja/golang-build:latest
|
||||
pull: true
|
||||
pull: always
|
||||
depends_on: [ test-migration-prepare, build ]
|
||||
environment:
|
||||
VIKUNJA_DATABASE_TYPE: postgres
|
||||
|
@ -212,7 +214,7 @@ steps:
|
|||
|
||||
- name: test
|
||||
image: vikunja/golang-build:latest
|
||||
pull: true
|
||||
pull: always
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
commands:
|
||||
|
@ -223,7 +225,7 @@ steps:
|
|||
|
||||
- name: test-sqlite
|
||||
image: vikunja/golang-build:latest
|
||||
pull: true
|
||||
pull: always
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
VIKUNJA_TESTS_USE_CONFIG: 1
|
||||
|
@ -240,7 +242,7 @@ steps:
|
|||
|
||||
- name: test-mysql
|
||||
image: vikunja/golang-build:latest
|
||||
pull: true
|
||||
pull: always
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
VIKUNJA_TESTS_USE_CONFIG: 1
|
||||
|
@ -257,7 +259,7 @@ steps:
|
|||
|
||||
- name: test-postgres
|
||||
image: vikunja/golang-build:latest
|
||||
pull: true
|
||||
pull: always
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
VIKUNJA_TESTS_USE_CONFIG: 1
|
||||
|
@ -275,7 +277,7 @@ steps:
|
|||
|
||||
- name: integration-test
|
||||
image: vikunja/golang-build:latest
|
||||
pull: true
|
||||
pull: always
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
commands:
|
||||
|
@ -286,7 +288,7 @@ steps:
|
|||
|
||||
- name: integration-test-sqlite
|
||||
image: vikunja/golang-build:latest
|
||||
pull: true
|
||||
pull: always
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
VIKUNJA_TESTS_USE_CONFIG: 1
|
||||
|
@ -303,7 +305,7 @@ steps:
|
|||
|
||||
- name: integration-test-mysql
|
||||
image: vikunja/golang-build:latest
|
||||
pull: true
|
||||
pull: always
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
VIKUNJA_TESTS_USE_CONFIG: 1
|
||||
|
@ -320,7 +322,7 @@ steps:
|
|||
|
||||
- name: integration-test-postgres
|
||||
image: vikunja/golang-build:latest
|
||||
pull: true
|
||||
pull: always
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
VIKUNJA_TESTS_USE_CONFIG: 1
|
||||
|
@ -342,14 +344,15 @@ steps:
|
|||
########
|
||||
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: release
|
||||
|
||||
depends_on:
|
||||
- testing
|
||||
|
||||
workspace:
|
||||
base: /go
|
||||
path: src/code.vikunja.io/api
|
||||
base: /source
|
||||
path: /
|
||||
|
||||
trigger:
|
||||
ref:
|
||||
|
@ -367,7 +370,7 @@ steps:
|
|||
# compiling the same magefile at the same time. It's also faster if each step does not need to compile it first.
|
||||
- name: mage
|
||||
image: vikunja/golang-build:latest
|
||||
pull: true
|
||||
pull: always
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
commands:
|
||||
|
@ -377,7 +380,7 @@ steps:
|
|||
|
||||
- name: before-static-build
|
||||
image: techknowlogick/xgo:latest
|
||||
pull: true
|
||||
pull: always
|
||||
commands:
|
||||
- export PATH=$PATH:$GOPATH/bin
|
||||
- go install github.com/magefile/mage
|
||||
|
@ -386,7 +389,7 @@ steps:
|
|||
|
||||
- name: static-build-windows
|
||||
image: techknowlogick/xgo:latest
|
||||
pull: true
|
||||
pull: always
|
||||
environment:
|
||||
# This path does not exist. However, when we set the gopath to /go, the build fails. Not sure why.
|
||||
# Leaving this here until we know how to resolve this properly.
|
||||
|
@ -399,7 +402,7 @@ steps:
|
|||
|
||||
- name: static-build-linux
|
||||
image: techknowlogick/xgo:latest
|
||||
pull: true
|
||||
pull: always
|
||||
environment:
|
||||
# This path does not exist. However, when we set the gopath to /go, the build fails. Not sure why.
|
||||
# Leaving this here until we know how to resolve this properly.
|
||||
|
@ -412,7 +415,7 @@ steps:
|
|||
|
||||
- name: static-build-darwin
|
||||
image: techknowlogick/xgo:latest
|
||||
pull: true
|
||||
pull: always
|
||||
environment:
|
||||
# This path does not exist. However, when we set the gopath to /go, the build fails. Not sure why.
|
||||
# Leaving this here until we know how to resolve this properly.
|
||||
|
@ -425,7 +428,7 @@ steps:
|
|||
|
||||
- name: after-build-compress
|
||||
image: kolaente/upx
|
||||
pull: true
|
||||
pull: always
|
||||
depends_on:
|
||||
- static-build-windows
|
||||
- static-build-linux
|
||||
|
@ -435,7 +438,7 @@ steps:
|
|||
|
||||
- name: after-build-static
|
||||
image: techknowlogick/xgo:latest
|
||||
pull: true
|
||||
pull: always
|
||||
depends_on:
|
||||
- after-build-compress
|
||||
commands:
|
||||
|
@ -447,7 +450,7 @@ steps:
|
|||
|
||||
- name: sign-release
|
||||
image: plugins/gpgsign:1
|
||||
pull: true
|
||||
pull: always
|
||||
depends_on: [ after-build-static ]
|
||||
settings:
|
||||
key:
|
||||
|
@ -461,7 +464,7 @@ steps:
|
|||
# Push the releases to our pseudo-s3-bucket
|
||||
- name: release-latest
|
||||
image: plugins/s3
|
||||
pull: true
|
||||
pull: always
|
||||
settings:
|
||||
bucket: vikunja-releases
|
||||
access_key:
|
||||
|
@ -483,7 +486,7 @@ steps:
|
|||
|
||||
- name: release-version
|
||||
image: plugins/s3
|
||||
pull: true
|
||||
pull: always
|
||||
settings:
|
||||
bucket: vikunja-releases
|
||||
access_key:
|
||||
|
@ -502,21 +505,40 @@ steps:
|
|||
depends_on: [ sign-release ]
|
||||
|
||||
# Build os packages and push it to our bucket
|
||||
- name: build-os-packages
|
||||
image: goreleaser/nfpm
|
||||
pull: true
|
||||
- name: build-os-packages-unstable
|
||||
image: goreleaser/nfpm:v2.27.1
|
||||
pull: always
|
||||
commands:
|
||||
- apk add git go
|
||||
- ./mage-static release:packages
|
||||
- mv dist/os-packages/vikunja*.x86_64.rpm dist/os-packages/vikunja-unstable-x86_64.rpm
|
||||
- mv dist/os-packages/vikunja*_amd64.deb dist/os-packages/vikunja-unstable-amd64.deb
|
||||
- mv dist/os-packages/vikunja*_x86_64.apk dist/os-packages/vikunja-unstable-x86_64.apk
|
||||
depends_on: [ static-build-linux ]
|
||||
when:
|
||||
branch:
|
||||
- main
|
||||
event:
|
||||
- push
|
||||
depends_on: [ after-build-compress ]
|
||||
|
||||
- name: build-os-packages-version
|
||||
image: goreleaser/nfpm:v2.27.1
|
||||
pull: always
|
||||
commands:
|
||||
- apk add git go
|
||||
- ./mage-static release:packages
|
||||
- mv dist/os-packages/vikunja*.x86_64.rpm dist/os-packages/vikunja-${DRONE_TAG##v}-x86_64.rpm
|
||||
- mv dist/os-packages/vikunja*_amd64.deb dist/os-packages/vikunja-${DRONE_TAG##v}-amd64.deb
|
||||
- mv dist/os-packages/vikunja*_x86_64.apk dist/os-packages/vikunja-${DRONE_TAG##v}-x86_64.apk
|
||||
when:
|
||||
event:
|
||||
- tag
|
||||
depends_on: [ after-build-compress ]
|
||||
|
||||
# Push the os releases to our pseudo-s3-bucket
|
||||
- name: release-os-latest
|
||||
image: plugins/s3
|
||||
pull: true
|
||||
pull: always
|
||||
settings:
|
||||
bucket: vikunja-releases
|
||||
access_key:
|
||||
|
@ -534,11 +556,11 @@ steps:
|
|||
- main
|
||||
event:
|
||||
- push
|
||||
depends_on: [ build-os-packages ]
|
||||
depends_on: [ build-os-packages-unstable ]
|
||||
|
||||
- name: release-os-version
|
||||
image: plugins/s3
|
||||
pull: true
|
||||
pull: always
|
||||
settings:
|
||||
bucket: vikunja-releases
|
||||
access_key:
|
||||
|
@ -554,47 +576,11 @@ steps:
|
|||
when:
|
||||
event:
|
||||
- tag
|
||||
depends_on: [ build-os-packages ]
|
||||
|
||||
### Broken, disabled until we figure out how to fix it
|
||||
# - name: deb-structure
|
||||
# image: kolaente/reprepro
|
||||
# pull: true
|
||||
# environment:
|
||||
# GPG_PRIVATE_KEY:
|
||||
# from_secret: gpg_privatekey
|
||||
# commands:
|
||||
# - export GPG_TTY=$(tty)
|
||||
# - gpg -qk
|
||||
# - echo "use-agent" >> ~/.gnupg/gpg.conf
|
||||
# - gpgconf --kill gpg-agent
|
||||
# - echo $GPG_PRIVATE_KEY > ~/frederik.gpg
|
||||
# - gpg --import ~/frederik.gpg
|
||||
# - mkdir debian/conf -p
|
||||
# - cp build/reprepro-dist-conf debian/conf/distributions
|
||||
# - ./mage-static release:reprepro
|
||||
# depends_on: [ build-os-packages ]
|
||||
|
||||
# Push the releases to our pseudo-s3-bucket
|
||||
- name: release-deb
|
||||
image: plugins/s3
|
||||
pull: true
|
||||
settings:
|
||||
bucket: vikunja-releases
|
||||
access_key:
|
||||
from_secret: aws_access_key_id
|
||||
secret_key:
|
||||
from_secret: aws_secret_access_key
|
||||
endpoint: https://s3.fr-par.scw.cloud
|
||||
region: fr-par
|
||||
path_style: true
|
||||
strip_prefix: debian
|
||||
source: debian/*/*/*/*/*
|
||||
target: /deb/
|
||||
# depends_on: [ deb-structure ]
|
||||
depends_on: [ build-os-packages-version ]
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: deploy-docs
|
||||
|
||||
workspace:
|
||||
|
@ -613,8 +599,7 @@ trigger:
|
|||
steps:
|
||||
- name: theme
|
||||
image: kolaente/toolbox
|
||||
pull: true
|
||||
group: build-static
|
||||
pull: always
|
||||
commands:
|
||||
- mkdir docs/themes/vikunja -p
|
||||
- cd docs/themes/vikunja
|
||||
|
@ -622,8 +607,8 @@ steps:
|
|||
- tar -xzf vikunja-theme.tar.gz
|
||||
|
||||
- name: build
|
||||
image: klakegg/hugo:0.93.3
|
||||
pull: true
|
||||
image: klakegg/hugo:0.107.0
|
||||
pull: always
|
||||
commands:
|
||||
- cd docs
|
||||
- hugo
|
||||
|
@ -631,7 +616,7 @@ steps:
|
|||
|
||||
- name: docker
|
||||
image: plugins/docker
|
||||
pull: true
|
||||
pull: always
|
||||
settings:
|
||||
username:
|
||||
from_secret: docker_username
|
||||
|
@ -644,69 +629,11 @@ steps:
|
|||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: docker-arm-release
|
||||
name: docker-release
|
||||
|
||||
depends_on:
|
||||
- testing
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: arm64
|
||||
|
||||
trigger:
|
||||
ref:
|
||||
- refs/heads/main
|
||||
- "refs/tags/**"
|
||||
|
||||
steps:
|
||||
- name: fetch-tags
|
||||
image: docker:git
|
||||
commands:
|
||||
- git fetch --tags
|
||||
|
||||
- name: docker-arm64-unstable
|
||||
image: plugins/docker:linux-arm64
|
||||
pull: true
|
||||
settings:
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
repo: vikunja/api
|
||||
tags: unstable-linux-arm64
|
||||
depends_on: [ fetch-tags ]
|
||||
when:
|
||||
ref:
|
||||
- refs/heads/main
|
||||
|
||||
- name: docker-arm64
|
||||
image: plugins/docker:linux-arm64
|
||||
pull: true
|
||||
settings:
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
repo: vikunja/api
|
||||
auto_tag: true
|
||||
auto_tag_suffix: linux-arm64
|
||||
depends_on: [ fetch-tags ]
|
||||
when:
|
||||
ref:
|
||||
- "refs/tags/**"
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: docker-amd64-release
|
||||
|
||||
depends_on:
|
||||
- testing
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: amd64
|
||||
|
||||
trigger:
|
||||
ref:
|
||||
- refs/heads/main
|
||||
|
@ -719,94 +646,55 @@ steps:
|
|||
- git fetch --tags
|
||||
|
||||
- name: docker-unstable
|
||||
image: plugins/docker:linux-amd64
|
||||
pull: true
|
||||
settings:
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
repo: vikunja/api
|
||||
tags: unstable-linux-amd64
|
||||
depends_on: [ fetch-tags ]
|
||||
when:
|
||||
ref:
|
||||
- refs/heads/main
|
||||
|
||||
- name: docker
|
||||
image: plugins/docker:linux-amd64
|
||||
pull: true
|
||||
settings:
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
repo: vikunja/api
|
||||
auto_tag: true
|
||||
auto_tag_suffix: linux-amd64
|
||||
depends_on: [ fetch-tags ]
|
||||
when:
|
||||
ref:
|
||||
- "refs/tags/**"
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: docker-manifest
|
||||
|
||||
trigger:
|
||||
ref:
|
||||
- refs/heads/main
|
||||
- "refs/tags/**"
|
||||
|
||||
depends_on:
|
||||
- docker-amd64-release
|
||||
- docker-arm-release
|
||||
|
||||
steps:
|
||||
- name: manifest-unstable
|
||||
image: thegeeklab/drone-docker-buildx
|
||||
privileged: true
|
||||
pull: always
|
||||
image: plugins/manifest
|
||||
settings:
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
repo: vikunja/api
|
||||
tags: unstable
|
||||
ignore_missing: true
|
||||
spec: docker-manifest-unstable.tmpl
|
||||
password:
|
||||
from_secret: docker_password
|
||||
username:
|
||||
from_secret: docker_username
|
||||
platforms:
|
||||
- linux/386
|
||||
- linux/amd64
|
||||
- linux/arm/v6
|
||||
- linux/arm/v7
|
||||
- linux/arm64/v8
|
||||
depends_on: [ fetch-tags ]
|
||||
when:
|
||||
ref:
|
||||
- refs/heads/main
|
||||
|
||||
- name: manifest-release
|
||||
pull: always
|
||||
image: plugins/manifest
|
||||
settings:
|
||||
auto_tag: true
|
||||
ignore_missing: true
|
||||
spec: docker-manifest.tmpl
|
||||
password:
|
||||
from_secret: docker_password
|
||||
username:
|
||||
from_secret: docker_username
|
||||
- name: generate-tags
|
||||
image: thegeeklab/docker-autotag
|
||||
environment:
|
||||
DOCKER_AUTOTAG_VERSION: ${DRONE_TAG}
|
||||
DOCKER_AUTOTAG_EXTRA_TAGS: latest
|
||||
DOCKER_AUTOTAG_OUTPUT_FILE: .tags
|
||||
depends_on: [ fetch-tags ]
|
||||
when:
|
||||
ref:
|
||||
- "refs/tags/**"
|
||||
|
||||
- name: manifest-release-latest
|
||||
- name: docker-release
|
||||
image: thegeeklab/drone-docker-buildx
|
||||
privileged: true
|
||||
pull: always
|
||||
image: plugins/manifest
|
||||
depends_on:
|
||||
- clone
|
||||
settings:
|
||||
tags: latest
|
||||
ignore_missing: true
|
||||
spec: docker-manifest.tmpl
|
||||
password:
|
||||
from_secret: docker_password
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
repo: vikunja/api
|
||||
platforms:
|
||||
- linux/386
|
||||
- linux/amd64
|
||||
- linux/arm/v6
|
||||
- linux/arm/v7
|
||||
- linux/arm64/v8
|
||||
depends_on: [ generate-tags ]
|
||||
when:
|
||||
ref:
|
||||
- "refs/tags/**"
|
||||
|
@ -825,9 +713,7 @@ depends_on:
|
|||
- testing
|
||||
- release
|
||||
- deploy-docs
|
||||
- docker-arm-release
|
||||
- docker-amd64-release
|
||||
- docker-manifest
|
||||
- docker-release
|
||||
|
||||
steps:
|
||||
- name: notify
|
||||
|
@ -845,6 +731,6 @@ steps:
|
|||
- failure
|
||||
---
|
||||
kind: signature
|
||||
hmac: 59e619a19be0bda1935ff2b49ac7b56a0511ac8ecfd2a05a6460bf5840876ba5
|
||||
hmac: f1743e2bb4ca8e5d8df1e222720feb4eac91e15a5ef74d4b54f1df246f2353fe
|
||||
|
||||
...
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
<!--
|
||||
|
||||
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)
|
|
@ -1,11 +0,0 @@
|
|||
# Description
|
||||
|
||||
|
||||
|
||||
# Checklist
|
||||
|
||||
* [ ] I added or improved tests
|
||||
* [ ] I added or improved docs for my feature
|
||||
* [ ] Swagger (including `mage do-the-swag`)
|
||||
* [ ] Error codes
|
||||
* [ ] New config options (including adding them to `config.yml.saml` and running `mage generate-docs`)
|
3
.github/FUNDING.yml
vendored
3
.github/FUNDING.yml
vendored
|
@ -1,2 +1,3 @@
|
|||
github: kolaente
|
||||
custom: https://www.buymeacoffee.com/kolaente
|
||||
open_collective: vikunja
|
||||
custom: ["https://vikunja.cloud", "https://www.buymeacoffee.com/kolaente"]
|
||||
|
|
58
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
Normal file
58
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
Normal file
|
@ -0,0 +1,58 @@
|
|||
name: Bug Report
|
||||
description: Found something you weren't expecting? Report it here!
|
||||
labels: kind/bug
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
NOTE: If your issue is a security concern, please send an email to security@vikunja.io instead of opening a public issue.
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Please fill out this issue template to report a bug.
|
||||
|
||||
1. If you want to propose a new feature, please open a discussion thread in the forum: https://community.vikunja.io
|
||||
2. Please ask questions or configuration/deploy problems on our [Matrix Room](https://matrix.to/#/#vikunja:matrix.org) or forum (https://community.vikunja.io).
|
||||
3. Make sure you are using the latest release and
|
||||
take a moment to check that your issue hasn't been reported before.
|
||||
4. Please give all relevant information below for bug reports, because
|
||||
incomplete details will be handled as an invalid report and closed.
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
description: |
|
||||
Please provide a description of your issue here, with a URL if you were able to reproduce the issue (see below).
|
||||
- type: input
|
||||
id: frontend-version
|
||||
attributes:
|
||||
label: Vikunja Frontend Version
|
||||
description: Vikunja frontend version (or commit reference) of your instance
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: api-version
|
||||
attributes:
|
||||
label: Vikunja API Version
|
||||
description: Vikunja API version (or commit reference) of your instance
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: browser-version
|
||||
attributes:
|
||||
label: Browser and version
|
||||
description: If your issue is related to a frontend problem, please provide the browser and version you used to reproduce it.
|
||||
- type: dropdown
|
||||
id: can-reproduce
|
||||
attributes:
|
||||
label: Can you reproduce the bug on the Vikunja demo site?
|
||||
options:
|
||||
- "Yes"
|
||||
- "No"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: screenshots
|
||||
attributes:
|
||||
label: Screenshots
|
||||
description: If this issue involves the Web Interface, please provide one or more screenshots
|
17
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
17
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
|
@ -0,0 +1,17 @@
|
|||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Frontend issues
|
||||
url: https://code.vikunja.io/frontend/issues
|
||||
about: This is the API repo. Please open frontend-related bug reports and discussions in the frontend repo. Not sure if you issue is frontend or api? Ask in Matrix or the forum first.
|
||||
- name: Forum
|
||||
url: https://community.vikunja.io/
|
||||
about: Feature Requests, Questions, configuration or deployment problems should be discussed in the forum.
|
||||
- name: Security-related issues
|
||||
url: https://vikunja.io/contact/#security
|
||||
about: For security concerns, please send a mail to security@vikunja.io instead of opening a public issue.
|
||||
- name: Chat on Matrix
|
||||
url: https://matrix.to/#/#vikunja:matrix.org
|
||||
about: Please ask any quick questions here.
|
||||
- name: Translations
|
||||
url: https://crowdin.com/project/vikunja
|
||||
about: Any problems or requests for new languages about translations should be handled in crowdin.
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -4,6 +4,8 @@
|
|||
config.yml
|
||||
config.yaml
|
||||
!docs/config.yml
|
||||
!.github/ISSUE_TEMPLATE/config.yml
|
||||
!.gitea/ISSUE_TEMPLATE/config.yml
|
||||
docs/themes/
|
||||
*.db
|
||||
Run
|
||||
|
|
|
@ -79,6 +79,7 @@ issues:
|
|||
- path: pkg/routes/api/v1/docs.go
|
||||
linters:
|
||||
- goheader
|
||||
- misspell
|
||||
- text: "Missed string"
|
||||
linters:
|
||||
- goheader
|
||||
|
@ -88,3 +89,6 @@ issues:
|
|||
- path: pkg/models/favorites\.go
|
||||
linters:
|
||||
- nilerr
|
||||
- path: pkg/models/events\.go
|
||||
linters:
|
||||
- musttag
|
||||
|
|
450
CHANGELOG.md
450
CHANGELOG.md
|
@ -7,6 +7,456 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|||
|
||||
All releases can be found on https://code.vikunja.io/api/releases.
|
||||
|
||||
## [0.20.4] - 2023-03-12
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* *(docker)* Allow non-unique group id
|
||||
|
||||
### Documentation
|
||||
|
||||
* Add link to tutorial for installing Vikunja on Synology ([4de0efe](4de0efec1dd7da95dbf936728d7e23791396a63a))
|
||||
|
||||
|
||||
## [0.20.3] - 2023-03-10
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* *(build)* Downgrade xgo to 1.19.2 so that builds work again
|
||||
* *(caldav)* Add Z suffix to dates make it clear dates are in UTC
|
||||
* *(caldav)* Use const for repeat modes
|
||||
* *(caldav)* Make sure only labels where the user has permission to use them are used
|
||||
* *(ci)* Pipeline dependency
|
||||
* *(ci)* Pin nfpm container version and binary location
|
||||
* *(ci)* Set release path to /source
|
||||
* *(ci)* Tagging logic for release docker images
|
||||
* *(ci)* Save generated .tags file to correctly tag docker releases
|
||||
* *(ci)* Sign drone config
|
||||
* *(docd)* Update Subdirectory Documentation (#1363)
|
||||
* *(docker)* Cross compilation with buildx
|
||||
* *(docker)* Re-add expose
|
||||
* *(docker)* Passing environment variables into the container
|
||||
* *(docker)* Make sure the vikunja user always exists and only modify the uid instead of recreating the user
|
||||
* *(docs)* Add docs about cli user delete
|
||||
* *(docs)* Old helm charts url (#1344)
|
||||
* *(docs)* Fix a few minor typos (#59)
|
||||
* *(docs)* Fix traefik v2 example (#65)
|
||||
* *(docs)* Clarify support for caldav reccurrence
|
||||
* *(drone)* Add type, fix pull, remove group (#1355)
|
||||
* *(dump)* Make sure null dates are properly set when restoring from a dump
|
||||
* *(export)* Ignore file size for export files
|
||||
* *(list)* Return lists for a namespace id even if that namespace is deleted
|
||||
* *(list)* When list background is removed, delete file from file system and DB (#1372)
|
||||
* *(mailer)* Forcessl config (#60)
|
||||
* *(migration)* Use Todoist v9 api to migrate tasks from them
|
||||
* *(migration)* Import TickTick data by column name instead of index (#1356)
|
||||
* *(migration)* Use the proper authorization method for Todoist's api, fix issues with importing deleted items
|
||||
* *(migration)* Remove unused todoist parameters
|
||||
* *(migration)* Todoist pagination now avoids too many loops
|
||||
* *(migration)* Don't try to add nonexistent tasks as related
|
||||
* *(migration)* Make sure trello checklists are properly imported
|
||||
* *(reminders)* Overdue tasks join condition
|
||||
* *(reminders)* Make sure an overdue reminder is sent when there is only one overdue task
|
||||
* *(reminders)* Prevent duplicate reminders when updating task details
|
||||
* *(restore)* Check if we're really dealing with a string
|
||||
* *(task)* Make sure the task's last updated timestamp is always updated when releated entities changed
|
||||
* *(task)* Correctly load tasks by id and uuid in caldav
|
||||
* *(tasks)* Don't include undone overdue tasks from archived lists or namespaces in notification mails
|
||||
* *(tasks)* Don't reset the kanban bucket when updating a task and not providing one
|
||||
* *(tasks)* Don't set a repeating task done when moving it do the done bucket
|
||||
* *(tasks)* Recalculate position of all tasks in a list or bucket when it would hit 0
|
||||
* *(tasks)* Make sure tasks are sorted by position before recalculating them
|
||||
* *(user)* Make reset the user's name to empty actually work
|
||||
* Swagger docs ([96b5e93](96b5e933796275e87f3007e31db0623688dbdb3a))
|
||||
* Restore notifications table from dump when it already had the correct format ([8c67be5](8c67be558f697ab52740c51ab453092c0f8f7c14))
|
||||
* Make sure labels are always exported as caldav (#1412) ([1afc72e](1afc72e1906c02b093bb6d9748235b93ab0eb181))
|
||||
* Lint ([491a142](491a1423788b76f236d070071cb46f5b2f5d3fd0))
|
||||
* Lint ([20a5994](20a5994b1717e7751750f14a9a164825a8e6ade6))
|
||||
* Lint ([077baba](077baba2eaff2f10b97384f07375ece7f51ec0fa))
|
||||
* Lint ([9f14466](9f14466dfa8660362a4e51b3c8c6810bf8d66a22))
|
||||
|
||||
|
||||
### Dependencies
|
||||
|
||||
* *(deps)* Update module github.com/yuin/goldmark to v1.5.3 (#1317)
|
||||
* *(deps)* Update module golang.org/x/crypto to v0.2.0 (#1315)
|
||||
* *(deps)* Update module github.com/spf13/afero to v1.9.3 (#1320)
|
||||
* *(deps)* Update module golang.org/x/crypto to v0.3.0 (#1321)
|
||||
* *(deps)* Update github.com/arran4/golang-ical digest to a677353 (#1323)
|
||||
* *(deps)* Update module github.com/wneessen/go-mail to v0.3.5 (#1325)
|
||||
* *(deps)* Update github.com/arran4/golang-ical digest to 1093469 (#1326)
|
||||
* *(deps)* Update module github.com/golang-jwt/jwt/v4 to v4.4.3 (#1328)
|
||||
* *(deps)* Update module github.com/go-sql-driver/mysql to v1.7.0 (#1332)
|
||||
* *(deps)* Update module golang.org/x/sys to v0.3.0 (#1333)
|
||||
* *(deps)* Update module golang.org/x/term to v0.3.0 (#1336)
|
||||
* *(deps)* Update module golang.org/x/image to v0.2.0 (#1335)
|
||||
* *(deps)* Update module golang.org/x/oauth2 to v0.2.0 (#1316)
|
||||
* *(deps)* Update module golang.org/x/oauth2 to v0.3.0 (#1337)
|
||||
* *(deps)* Update module github.com/getsentry/sentry-go to v0.16.0 (#1338)
|
||||
* *(deps)* Update module golang.org/x/crypto to v0.4.0 (#1339)
|
||||
* *(deps)* Update module github.com/pquerna/otp to v1.4.0 (#1341)
|
||||
* *(deps)* Update module github.com/swaggo/swag to v1.8.9 (#1327)
|
||||
* *(deps)* Update module github.com/wneessen/go-mail to v0.3.6 (#1342)
|
||||
* *(deps)* Update module github.com/labstack/echo/v4 to v4.10.0 (#1343)
|
||||
* *(deps)* Update module github.com/wneessen/go-mail to v0.3.7 (#1348)
|
||||
* *(deps)* Update module github.com/coreos/go-oidc/v3 to v3.5.0 (#1349)
|
||||
* *(deps)* Update module golang.org/x/sys to v0.4.0 (#1351)
|
||||
* *(deps)* Update module golang.org/x/image to v0.3.0 (#1350)
|
||||
* *(deps)* Update module golang.org/x/term to v0.4.0 (#1352)
|
||||
* *(deps)* Update module golang.org/x/crypto to v0.5.0 (#1353)
|
||||
* *(deps)* Update goreleaser/nfpm docker tag to v2.23.0 (#1347)
|
||||
* *(deps)* Update module github.com/wneessen/go-mail to v0.3.8 (#1357)
|
||||
* *(deps)* Update module src.techknowlogick.com/xgo to v1.6.0+1.19.5 (#1358)
|
||||
* *(deps)* Update klakegg/hugo docker tag to v0.107.0 (#1272)
|
||||
* *(deps)* Update module github.com/getsentry/sentry-go to v0.17.0 (#1361)
|
||||
* *(deps)* Update module src.techknowlogick.com/xgo to v1.7.0+1.19.5 (#1364)
|
||||
* *(deps)* Update module github.com/spf13/viper to v1.15.0 (#1365)
|
||||
* *(deps)* Update module github.com/labstack/echo-jwt/v4 to v4.0.1 (#1369)
|
||||
* *(deps)* Update module golang.org/x/oauth2 to v0.4.0 (#1354)
|
||||
* *(deps)* Update github.com/gocarina/gocsv digest to 763e25b (#1370)
|
||||
* *(deps)* Update goreleaser/nfpm docker tag to v2.24.0 (#1367)
|
||||
* *(deps)* Update module github.com/swaggo/swag to v1.8.10 (#1371)
|
||||
* *(deps)* Update module github.com/go-redis/redis/v8 to v9 (#1377)
|
||||
* *(deps)* Update module github.com/labstack/echo-jwt/v4 to v4.1.0
|
||||
* *(deps)* Update module github.com/ulule/limiter/v3 to v3.11.0 (#1378)
|
||||
* *(deps)* Update module github.com/redis/go-redis/v9 to v9.0.2
|
||||
* *(deps)* Update goreleaser/nfpm docker tag to v2.25.0 (#1382)
|
||||
* *(deps)* Upgrade golangci-lint to 1.51.0
|
||||
* *(deps)* Update module github.com/yuin/goldmark to v1.5.4
|
||||
* *(deps)* Update module go to 1.20
|
||||
* *(deps)* Update xgo to 1.20
|
||||
* *(deps)* Update module golang.org/x/sys to v0.5.0
|
||||
* *(deps)* Update module github.com/getsentry/sentry-go to v0.18.0 (#1386)
|
||||
* *(deps)* Update module golang.org/x/term to v0.5.0
|
||||
* *(deps)* Update module golang.org/x/crypto to v0.6.0
|
||||
* *(deps)* Update module golang.org/x/oauth2 to v0.5.0
|
||||
* *(deps)* Update module golang.org/x/image to v0.4.0
|
||||
* *(deps)* Update goreleaser/nfpm docker tag to v2.26.0 (#1394)
|
||||
* *(deps)* Update github.com/arran4/golang-ical digest to 07c6aad
|
||||
* *(deps)* Update module github.com/threedotslabs/watermill to v1.2.0 (#1384)
|
||||
* *(deps)* Update module golang.org/x/image to v0.5.0 (#1396)
|
||||
* *(deps)* Update golang.org/x/net to 0.7.0
|
||||
* *(deps)* Update module github.com/golang-jwt/jwt/v4 to v4.5.0 (#1399)
|
||||
* *(deps)* Update github.com/gocarina/gocsv digest to bcce7dc
|
||||
* *(deps)* Update golangci-lint to 1.51.2
|
||||
* *(deps)* Update module github.com/labstack/echo/v4 to v4.10.1
|
||||
* *(deps)* Update github.com/gocarina/gocsv digest to bee85ea
|
||||
* *(deps)* Update module github.com/labstack/echo/v4 to v4.10.2
|
||||
* *(deps)* Update module github.com/spf13/afero to v1.9.4
|
||||
* *(deps)* Update github.com/gocarina/gocsv digest to dc4ee9d
|
||||
* *(deps)* Update module github.com/stretchr/testify to v1.8.2
|
||||
* *(deps)* Update github.com/gocarina/gocsv digest to 70c27cb
|
||||
* *(deps)* Update module golang.org/x/sys to v0.6.0
|
||||
* *(deps)* Update module golang.org/x/term to v0.6.0
|
||||
* *(deps)* Update module golang.org/x/crypto to v0.7.0
|
||||
* *(deps)* Update module golang.org/x/oauth2 to v0.6.0
|
||||
* *(deps)* Update module golang.org/x/image to v0.6.0
|
||||
* *(deps)* Update github.com/kolaente/caldav-go digest to 2a4eb8b
|
||||
* *(deps)* Remove fsnotify replacement
|
||||
* *(deps)* Update github.com/vectordotdev/go-datemath digest to f3954d0
|
||||
* *(deps)* Update src.techknowlogick.com/xgo digest to 44f7e66
|
||||
* *(deps)* Update module github.com/getsentry/sentry-go to v0.19.0
|
||||
* *(deps)* Update module github.com/spf13/afero to v1.9.5
|
||||
* *(deps)* Update module github.com/ulule/limiter/v3 to v3.11.1
|
||||
* *(deps)* Update src.techknowlogick.com/xgo digest to b607086
|
||||
* *(deps)* Update module github.com/gabriel-vasile/mimetype to v1.4.2
|
||||
|
||||
### Features
|
||||
|
||||
* *(background)* Add Last-Modified header (#1376)
|
||||
* *(caldav)* Add support for repeating tasks
|
||||
* *(caldav)* Export Labels to Caldav (#1409)
|
||||
* *(caldav)* Import caldav categories as Labels (#1413)
|
||||
* *(migrators)* Remove wunderlist (#1346)
|
||||
* *(release)* Use compressed binaries for package releases
|
||||
* Use docker buildx to build multiarch images ([a6e214b](a6e214b654f28836cc8b93683dbfd5999282d11c))
|
||||
* Provide logout url for openid providers (#1340) ([a79b1de](a79b1de2d0247a424f49cecaa267d30e8fa70a83))
|
||||
* Refactored Dockerfile (#1375) ([522bf7d](522bf7d2fc3cc1704f58299b6435baccc7add533))
|
||||
* Disable events log by default ([da9d25c](da9d25cf727c56acd7394b4b74e17a2959ee5242))
|
||||
- **BREAKING**: events log level is now off unless explicitly enabled
|
||||
|
||||
|
||||
### Miscellaneous Tasks
|
||||
|
||||
* *(docs)* Adjust docs about frontend docker container
|
||||
* *(docs)* Remove sponsors
|
||||
* *(task)* Add test to check if a task's reminders are duplicated
|
||||
* Remove custom gitea bug template in favor of githubs ([4fa45bf](4fa45bf9dcbaa8a41a53fc2305c4c2c1aa15691c))
|
||||
* 0.20.2 release preperations ([d19fc80](d19fc80b8be08673136d84e10187cadb293822bf))
|
||||
* Update funding links ([aa25ccd](aa25ccdc917684583a9bff4b7cb272004386f0fa))
|
||||
|
||||
|
||||
### Other
|
||||
|
||||
* *(other)* Added Google & Google Workspace to OpenId examples (#1319)
|
||||
|
||||
|
||||
## [0.20.2] - 2023-01-24
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* *(build)* Downgrade xgo to 1.19.2 so that builds work again
|
||||
* *(caldav)* Add Z suffix to dates make it clear dates are in UTC
|
||||
* *(caldav)* Use const for repeat modes
|
||||
* *(ci)* Pipeline dependency
|
||||
* *(ci)* Pin nfpm container version and binary location
|
||||
* *(ci)* Set release path to /source
|
||||
* *(ci)* Tagging logic for release docker images
|
||||
* *(docs)* Add docs about cli user delete
|
||||
* *(docs)* Old helm charts url (#1344)
|
||||
* *(docs)* Fix a few minor typos (#59)
|
||||
* *(drone)* Add type, fix pull, remove group (#1355)
|
||||
* *(dump)* Make sure null dates are properly set when restoring from a dump
|
||||
* *(export)* Ignore file size for export files
|
||||
* *(list)* Return lists for a namespace id even if that namespace is deleted
|
||||
* *(mailer)* Forcessl config (#60)
|
||||
* *(migration)* Use Todoist v9 api to migrate tasks from them
|
||||
* *(migration)* Import TickTick data by column name instead of index (#1356)
|
||||
* *(migration)* Use the proper authorization method for Todoist's api, fix issues with importing deleted items
|
||||
* *(reminders)* Overdue tasks join condition
|
||||
* *(reminders)* Make sure an overdue reminder is sent when there is only one overdue task
|
||||
* *(reminders)* Prevent duplicate reminders when updating task details
|
||||
* *(restore)* Check if we're really dealing with a string
|
||||
* *(tasks)* Don't include undone overdue tasks from archived lists or namespaces in notification mails
|
||||
* *(tasks)* Don't reset the kanban bucket when updating a task and not providing one
|
||||
* *(tasks)* Don't set a repeating task done when moving it do the done bucket
|
||||
* *(user)* Make reset the user's name to empty actually work* Swagger docs ([41c9e3f](41c9e3f9a47280887b56941280904aea6ef31f85))
|
||||
* Restore notifications table from dump when it already had the correct format ([15811fd](15811fd4d4485cd25cf8d2f8fdd04ebfea8e6663))
|
||||
|
||||
|
||||
### Dependencies
|
||||
|
||||
* *(deps)* Update module github.com/yuin/goldmark to v1.5.3 (#1317)
|
||||
* *(deps)* Update module golang.org/x/crypto to v0.2.0 (#1315)
|
||||
* *(deps)* Update module github.com/spf13/afero to v1.9.3 (#1320)
|
||||
* *(deps)* Update module golang.org/x/crypto to v0.3.0 (#1321)
|
||||
* *(deps)* Update github.com/arran4/golang-ical digest to a677353 (#1323)
|
||||
* *(deps)* Update module github.com/wneessen/go-mail to v0.3.5 (#1325)
|
||||
* *(deps)* Update github.com/arran4/golang-ical digest to 1093469 (#1326)
|
||||
* *(deps)* Update module github.com/golang-jwt/jwt/v4 to v4.4.3 (#1328)
|
||||
* *(deps)* Update module github.com/go-sql-driver/mysql to v1.7.0 (#1332)
|
||||
* *(deps)* Update module golang.org/x/sys to v0.3.0 (#1333)
|
||||
* *(deps)* Update module golang.org/x/term to v0.3.0 (#1336)
|
||||
* *(deps)* Update module golang.org/x/image to v0.2.0 (#1335)
|
||||
* *(deps)* Update module golang.org/x/oauth2 to v0.2.0 (#1316)
|
||||
* *(deps)* Update module golang.org/x/oauth2 to v0.3.0 (#1337)
|
||||
* *(deps)* Update module github.com/getsentry/sentry-go to v0.16.0 (#1338)
|
||||
* *(deps)* Update module golang.org/x/crypto to v0.4.0 (#1339)
|
||||
* *(deps)* Update module github.com/pquerna/otp to v1.4.0 (#1341)
|
||||
* *(deps)* Update module github.com/swaggo/swag to v1.8.9 (#1327)
|
||||
* *(deps)* Update module github.com/wneessen/go-mail to v0.3.6 (#1342)
|
||||
* *(deps)* Update module github.com/labstack/echo/v4 to v4.10.0 (#1343)
|
||||
* *(deps)* Update module github.com/wneessen/go-mail to v0.3.7 (#1348)
|
||||
* *(deps)* Update module github.com/coreos/go-oidc/v3 to v3.5.0 (#1349)
|
||||
* *(deps)* Update module golang.org/x/sys to v0.4.0 (#1351)
|
||||
* *(deps)* Update module golang.org/x/image to v0.3.0 (#1350)
|
||||
* *(deps)* Update module golang.org/x/term to v0.4.0 (#1352)
|
||||
* *(deps)* Update module golang.org/x/crypto to v0.5.0 (#1353)
|
||||
* *(deps)* Update goreleaser/nfpm docker tag to v2.23.0 (#1347)
|
||||
* *(deps)* Update module github.com/wneessen/go-mail to v0.3.8 (#1357)
|
||||
* *(deps)* Update module src.techknowlogick.com/xgo to v1.6.0+1.19.5 (#1358)
|
||||
* *(deps)* Update klakegg/hugo docker tag to v0.107.0 (#1272)
|
||||
* *(deps)* Update module github.com/getsentry/sentry-go to v0.17.0 (#1361)
|
||||
* *(deps)* Update module src.techknowlogick.com/xgo to v1.7.0+1.19.5 (#1364)
|
||||
* *(deps)* Update module github.com/spf13/viper to v1.15.0 (#1365)
|
||||
* *(deps)* Update module github.com/labstack/echo-jwt/v4 to v4.0.1 (#1369)
|
||||
|
||||
### Features
|
||||
|
||||
* *(migrators)* Remove wunderlist (#1346)
|
||||
* *(release)* Use compressed binaries for package releases
|
||||
* Use docker buildx to build multiarch images ([9bd6795](9bd6795266fd54ae42664c20ed7633ac7daf6199))
|
||||
|
||||
### Miscellaneous Tasks
|
||||
|
||||
* Remove custom gitea bug template in favor of githubs ([7b1e1c7](7b1e1c79e358f3fcecb217259491f016402cdcc7))
|
||||
|
||||
### Other
|
||||
|
||||
* *(other)* Added Google & Google Workspace to OpenId examples (#1319)
|
||||
|
||||
## [0.20.1] - 2022-11-11
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* *(docs)* Add explanation on how to run the cli in docker
|
||||
* *(filter)* Also check for 0 values if the filter should include nulls
|
||||
* *(filter)* Only check for 0 values in filter fields with numeric values
|
||||
* *(filters)* Try to parse date filter fields of the provided dates are not valid iso dates
|
||||
* *(filters)* Try parsing dates without time
|
||||
* *(filters)* Try parsing invalid dates like 2022-11-1
|
||||
* *(metrics)* Make currently active users actually work
|
||||
* *(task)* Duplicate reminders when adding different ones between winter / summer time
|
||||
* *(tasks)* Allow sorting by task index* Make sure task indexes are calculated correctly when moving tasks between lists ([c495096](c4950964443a9bffc4cdd8fc25004ad951520f20))
|
||||
* Look for the default bucket based on the position instead of the index ([622f2f0](622f2f0562bd8e3a5c97ec0b001c646a33a86c2b))
|
||||
* Usage with postgres over unix socket (#1308) ([641a9da](641a9da93d24a18d6cbad2929eea1be6c1e0d0b2))
|
||||
|
||||
### Dependencies
|
||||
|
||||
* *(deps)* Update module github.com/prometheus/client_golang to v1.13.1 (#1307)
|
||||
* *(deps)* Update module github.com/spf13/viper to v1.14.0 (#1309)
|
||||
* *(deps)* Update module golang.org/x/sys to v0.2.0 (#1311)
|
||||
* *(deps)* Update module golang.org/x/term to v0.2.0 (#1312)
|
||||
* *(deps)* Update module github.com/prometheus/client_golang to v1.14.0 (#1313)
|
||||
* *(deps)* Update module github.com/getsentry/sentry-go to v0.15.0 (#1314)
|
||||
|
||||
### Features
|
||||
|
||||
* *(docs)* Add relase checklist
|
||||
|
||||
### Other
|
||||
|
||||
* *(other)* Nessecary is a common misspelling of necessary (#1304)
|
||||
|
||||
## [0.20.0] - 2022-10-28
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* *(caldav)* Make sure duration and due date follow rfc5545
|
||||
* *(caldav)* No failed login emails for tokens (#1252)
|
||||
* *(ci)* Make sure release zip files have a .zip ending
|
||||
* *(ci)* Make sure release os packages are properly named
|
||||
* *(docs)* Clarify using port 25 as mail port when mail does not work
|
||||
* *(docs)* Document pnpm instead of yarn
|
||||
* *(docs)* Fix redirect_url example (#50)
|
||||
* *(lists)* Return correct max right for lists where the user has created the namespace
|
||||
* *(mail)* Pass mail server timeout (#1253)
|
||||
* *(migration)* Properly parse duration
|
||||
* *(migration)* Expose ticktick migrator to /info
|
||||
* *(migration)* Make sure importing works when the csv file has errors and don't try to parse empty values as dates
|
||||
* *(namespaces)* Add list subscriptions (#1254)
|
||||
* *(todoist)* Properly import all done tasks* Properly log extra message ([c194797](c19479757a20d72484b4e071b45055746ff2b67e))
|
||||
* Don't try to compress riscv64 binaries in releases ([d8f387f](d8f387f7967ffb94035de2fcfc4578247ae1023e))
|
||||
* Preserve dates for repeating tasks (#47) ([090c671](090c67138a16258480b866b05c6fdc2e02d12c89))
|
||||
* Tasks with the same assignee as doer should not appear twice in overdue task mails ([45defeb](45defebcf435cade4b72763236e1e2dfdac770cc))
|
||||
* Don't allow setting a list namespace to 0 ([96ed1e3](96ed1e33e38beec1bb1ab0813074b035dd02fade))
|
||||
* Make sure pseudo namespaces and lists always have the current user as owner ([878d19b](878d19beb81869392e33a8ffc1ec247d1cf1e4d6))
|
||||
* Use connection string for postgres ([fcb205a](fcb205a842a4e828e6e933339b23f5aa8b297125))
|
||||
* Make sure user searches are always case-insensitive ([c076f73](c076f73a87bc9b39b17389e25d0186ab71aa24bf))
|
||||
* Make cover image id actually updatable ([0e1904d](0e1904d50b8576a2e9ea5812314aa3c8f304edb5))
|
||||
* Make cover image id actually updatable ([0eb4709](0eb47096db02ceb5032c7439b3b901fbadd0d1bb))
|
||||
* Make sure a user can only be assigned once to a task ([008908e](008908eb49eeb50a554c416422feb3b465efa165))
|
||||
* Make sure list subscriptions are set correctly when their namespace has a subscription already ([2fc690a](2fc690a783f5b702fad71da627aa616017727f56))
|
||||
|
||||
|
||||
### Dependencies
|
||||
|
||||
* *(deps)* Update klakegg/hugo docker tag to v0.101.0
|
||||
* *(deps)* Update golang.org/x/sync digest to 8fcdb60
|
||||
* *(deps)* Update golang.org/x/oauth2 digest to f213421
|
||||
* *(deps)* Update module src.techknowlogick.com/xgo to v1.5.0+1.19
|
||||
* *(deps)* Update module github.com/coreos/go-oidc/v3 to v3.4.0
|
||||
* *(deps)* Update golang.org/x/image digest to e7cb969
|
||||
* *(deps)* Update golang.org/x/term digest to 7a66f97
|
||||
* *(deps)* Update module github.com/lib/pq to v1.10.7
|
||||
* *(deps)* Update module github.com/spf13/viper to v1.13.0 (#1260)
|
||||
* *(deps)* Update dependency golang to v1.19 (#1228)
|
||||
* *(deps)* Update module github.com/wneessen/go-mail to v0.2.8 (#1258)
|
||||
* *(deps)* Update module github.com/yuin/goldmark to v1.5.2 (#1261)
|
||||
* *(deps)* Update module src.techknowlogick.com/xormigrate to v1.5.0 (#1262)
|
||||
* *(deps)* Update module github.com/magefile/mage to v1.14.0 (#1259)
|
||||
* *(deps)* Update module github.com/swaggo/swag to v1.8.6 (#1243)
|
||||
* *(deps)* Update module github.com/wneessen/go-mail to v0.2.9 (#1264)
|
||||
* *(deps)* Update dependency klakegg/hugo to v0.102.3 (#1265)
|
||||
* *(deps)* Update module github.com/getsentry/sentry-go to v0.14.0 (#1266)
|
||||
* *(deps)* Update module github.com/labstack/gommon to v0.4.0 (#1269)
|
||||
* *(deps)* Update golang.org/x/crypto digest to 4161e89 (#1268)
|
||||
* *(deps)* Update golang.org/x/oauth2 digest to b44042a (#1270)
|
||||
* *(deps)* Update golang.org/x/sys digest to 84dc82d (#1271)
|
||||
* *(deps)* Update dependency klakegg/hugo to v0.104.2 (#1267)
|
||||
* *(deps)* Update golang.org/x/crypto digest to d6f0a8c (#1275)
|
||||
* *(deps)* Update golang.org/x/sys digest to 090e330 (#1276)
|
||||
* *(deps)* Update module github.com/spf13/cobra to v1.6.0 (#1277)
|
||||
* *(deps)* Update module github.com/wneessen/go-mail to v0.3.0 (#1278)
|
||||
* *(deps)* Update golang.org/x/crypto digest to 56aed06 (#1280)
|
||||
* *(deps)* Update golang.org/x/text to v0.3.8
|
||||
* *(deps)* Update module github.com/wneessen/go-mail to v0.3.1 (#1281)
|
||||
* *(deps)* Update module github.com/labstack/echo/v4 to v4.9.1 (#1282)
|
||||
* *(deps)* Update golang.org/x/sys digest to 95e765b (#1283)
|
||||
* *(deps)* Update golang.org/x/oauth2 digest to 6fdb5e3 (#1284)
|
||||
* *(deps)* Update golang.org/x/image digest to ffcb3fe (#1288)
|
||||
* *(deps)* Update module golang.org/x/sync to v0.1.0 (#1291)
|
||||
* *(deps)* Update module github.com/swaggo/swag to v1.8.7 (#1290)
|
||||
* *(deps)* Update golang.org/x/term digest to 8365914 (#1289)
|
||||
* *(deps)* Update module github.com/coreos/go-systemd/v22 to v22.4.0 (#1287)
|
||||
* *(deps)* Update module golang.org/x/oauth2 to v0.1.0 (#1296)
|
||||
* *(deps)* Update module golang.org/x/crypto to v0.1.0 (#1295)
|
||||
* *(deps)* Update module golang.org/x/image to v0.1.0 (#1293)
|
||||
* *(deps)* Update module github.com/wneessen/go-mail to v0.3.2 (#1297)
|
||||
* *(deps)* Update module github.com/stretchr/testify to v1.8.1 (#1298)
|
||||
* *(deps)* Update module github.com/spf13/cobra to v1.6.1 (#1299)
|
||||
* *(deps)* Update module github.com/wneessen/go-mail to v0.3.3 (#1300)
|
||||
* *(deps)* Update module github.com/wneessen/go-mail to v0.3.4 (#1302)
|
||||
* *(deps)* Update module github.com/mattn/go-sqlite3 to v1.14.16 (#1301)
|
||||
|
||||
### Features
|
||||
|
||||
* *(docs)* Add docs about how to deploy Vikunja in a subdirectory
|
||||
* *(docs)* Document pnpm (#1251)
|
||||
* *(migration)* Add TickTick migrator
|
||||
* *(migration)* Add routes for TickTick migrator
|
||||
* *(migration)* Generate swagger docs
|
||||
* *(task)* Add cover image attachment id property
|
||||
* *(task)* Add cover image attachment id property (#1263)* Add sponsor to readme (relm) ([f814dd0](f814dd03eb7f1ae08ea67ae0e3e89b8b4e684ce3))
|
||||
* Upgrade xorm ([b1fd13b](b1fd13bbcbc551d1bbfe78d91fe6209369709df5))
|
||||
* Upgrade xorm ([4323803](4323803fd6801e21121eac0d9f9cd62879f090f7))
|
||||
* Upgrade xorm (#1197) ([5341918](53419180be386d675b4513e7ec70aca85b5ac99b))
|
||||
* Add github issue templates ([9c4bb5a](9c4bb5a24429dec686e3ccdcd2b920ce5528031c))
|
||||
* Remove gitea issue template so that only the form is used ([ce621ee](ce621ee5d6b47a0776628073bbd53312a97d116b))
|
||||
* Add gitea issue template ([0612f4d](0612f4d0e03fbe85018f51056c4833557e78cd3f))
|
||||
* Provide default user settings for new users via config ([5a40100](5a40100ac5be33d2cbce3c25e355d4036b9b4d3f))
|
||||
* Add proper checks and errors to see if an attachment belongs to the task it's being used as cover image in ([631a265](631a265d2de9a6196faf28574023fc3cdcc0bfc7))
|
||||
* Allow a user to remove themselves from a team ([b8769c7](b8769c746ceddc9818f91d6a8a404293ea2e837e))
|
||||
* TickTick migrator (#1273) ([df2e36c](df2e36c2a378d4bd1b81d959da180b6e9b9a37b9))
|
||||
|
||||
|
||||
### Miscellaneous Tasks
|
||||
|
||||
* Upgrade echo ([86ee827](86ee8273bce36c7b4767a34e0d878d63b37ea1b4))
|
||||
* Go mod tidy ([903b8ff](903b8ff43871234f41f706d571ee2caaba5f4232))
|
||||
* Generate swagger docs ([e113fe3](e113fe34d074f698f4b0cb237821f359976daa5c))
|
||||
* Remove unused dependencies ([f5fd849](f5fd849a0b93ff3bba53ac4907bb3fb04fa8692b))
|
||||
|
||||
## [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
|
||||
|
|
57
Dockerfile
57
Dockerfile
|
@ -1,49 +1,42 @@
|
|||
# syntax=docker/dockerfile:1
|
||||
# ┬─┐┬ ┐o┬ ┬─┐
|
||||
# │─││ │││ │ │
|
||||
# ┘─┘┘─┘┘┘─┘┘─┘
|
||||
|
||||
##############
|
||||
# Build stage
|
||||
FROM golang:1.18-alpine AS build-env
|
||||
FROM --platform=$BUILDPLATFORM techknowlogick/xgo:go-1.20.0 AS builder
|
||||
|
||||
RUN apk --no-cache add build-base git && \
|
||||
go install github.com/magefile/mage@latest && \
|
||||
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
|
||||
COPY . ./
|
||||
|
||||
# Checkout version if set
|
||||
RUN if [ -n "${VIKUNJA_VERSION}" ]; then git checkout "${VIKUNJA_VERSION}"; fi \
|
||||
&& mage build:clean build
|
||||
ARG TARGETOS TARGETARCH TARGETVARIANT
|
||||
|
||||
RUN mage build:clean && \
|
||||
mage release:xgo "${TARGETOS}/${TARGETARCH}/${TARGETVARIANT}"
|
||||
|
||||
# ┬─┐┬ ┐┌┐┐┌┐┐┬─┐┬─┐
|
||||
# │┬┘│ │││││││├─ │┬┘
|
||||
# ┘└┘┘─┘┘└┘┘└┘┴─┘┘└┘
|
||||
|
||||
###################
|
||||
# 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.16
|
||||
FROM alpine:3.16 AS runner
|
||||
LABEL maintainer="maintainers@vikunja.io"
|
||||
WORKDIR /app/vikunja
|
||||
ENTRYPOINT [ "/sbin/tini", "-g", "--", "/entrypoint.sh" ]
|
||||
EXPOSE 3456
|
||||
|
||||
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 apk --no-cache add shadow && \
|
||||
addgroup -g ${PGID} vikunja && \
|
||||
adduser -s /bin/sh -D -G vikunja -u ${PUID} vikunja -h /app/vikunja -H && \
|
||||
chown vikunja -R /app/vikunja
|
||||
COPY run.sh /run.sh
|
||||
|
||||
# Add time zone data
|
||||
RUN apk --no-cache add tzdata
|
||||
RUN apk --update --no-cache add tzdata tini shadow && \
|
||||
addgroup vikunja && \
|
||||
adduser -s /bin/sh -D -G vikunja vikunja -h /app/vikunja -H
|
||||
COPY docker/entrypoint.sh /entrypoint.sh
|
||||
RUN chmod 0755 /entrypoint.sh && mkdir files
|
||||
|
||||
# Files permissions
|
||||
RUN mkdir /app/vikunja/files && \
|
||||
chown -R vikunja /app/vikunja/files
|
||||
VOLUME /app/vikunja/files
|
||||
|
||||
CMD ["/run.sh"]
|
||||
EXPOSE 3456
|
||||
COPY --from=builder /build/vikunja-* vikunja
|
||||
|
|
|
@ -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.19.0-brightgreen.svg)](https://dl.vikunja.io)
|
||||
[![Download](https://img.shields.io/badge/download-v0.20.4-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)
|
||||
|
|
|
@ -30,7 +30,7 @@ service:
|
|||
enablecaldav: true
|
||||
# Set the motd message, available from the /info endpoint
|
||||
motd: ""
|
||||
# Enable sharing of lists via a link
|
||||
# Enable sharing of project via a link
|
||||
enablelinksharing: true
|
||||
# Whether to let new users registering themselves or not
|
||||
enableregistration: true
|
||||
|
@ -127,7 +127,8 @@ 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"
|
||||
|
@ -161,10 +162,10 @@ log:
|
|||
databaselevel: "WARNING"
|
||||
# Whether to log http requests or not. Possible values are stdout, stderr, file or off to disable http logging.
|
||||
http: "stdout"
|
||||
# Echo has its own logging which usually is unnessecary, which is why it is disabled by default. Possible values are stdout, stderr, file or off to disable standard logging.
|
||||
# Echo has its own logging which usually is unnecessary, which is why it is disabled by default. Possible values are stdout, stderr, file or off to disable standard logging.
|
||||
echo: "off"
|
||||
# Whether or not to log events. Useful for debugging. Possible values are stdout, stderr, file or off to disable events logging.
|
||||
events: "stdout"
|
||||
events: "off"
|
||||
# The log level for event log messages. Possible values (case-insensitive) are ERROR, INFO, DEBUG.
|
||||
eventslevel: "info"
|
||||
|
||||
|
@ -190,21 +191,6 @@ files:
|
|||
maxsize: 20MB
|
||||
|
||||
migration:
|
||||
# These are the settings for the wunderlist migrator
|
||||
wunderlist:
|
||||
# Wheter to enable the wunderlist migrator or not
|
||||
enable: false
|
||||
# The client id, required for making requests to the wunderlist api
|
||||
# You need to register your vikunja instance at https://developer.wunderlist.com/apps/new to get this
|
||||
clientid:
|
||||
# The client secret, also required for making requests to the wunderlist api
|
||||
clientsecret:
|
||||
# The url where clients are redirected after they authorized Vikunja to access their wunderlist stuff.
|
||||
# This needs to match the url you entered when registering your Vikunja instance at wunderlist.
|
||||
# This is usually the frontend url where the frontend then makes a request to /migration/wunderlist/migrate
|
||||
# with the code obtained from the wunderlist api.
|
||||
# Note that the vikunja frontend expects this to be /migrate/wunderlist
|
||||
redirecturl:
|
||||
todoist:
|
||||
# Wheter to enable the todoist migrator or not
|
||||
enable: false
|
||||
|
@ -252,14 +238,14 @@ avatar:
|
|||
gravatarexpiration: 3600
|
||||
|
||||
backgrounds:
|
||||
# Whether to enable backgrounds for lists at all.
|
||||
# Whether to enable backgrounds for projects at all.
|
||||
enabled: true
|
||||
providers:
|
||||
upload:
|
||||
# Whethere to enable uploaded list backgrounds
|
||||
# Whether to enable uploaded project backgrounds
|
||||
enabled: true
|
||||
unsplash:
|
||||
# Whether to enable setting backgrounds from unsplash as list backgrounds
|
||||
# Whether to enable setting backgrounds from unsplash as project backgrounds
|
||||
enabled: false
|
||||
# You need to create an application for your installation at https://unsplash.com/oauth/applications/new
|
||||
# and set the access token below.
|
||||
|
@ -301,6 +287,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:
|
||||
|
@ -308,6 +296,9 @@ auth:
|
|||
- name:
|
||||
# The auth url to send users to if they want to authenticate using OpenID Connect.
|
||||
authurl:
|
||||
# The oidc logouturl that users will be redirected to on logout.
|
||||
# Leave empty or delete key, if you do not want to be redirected.
|
||||
logouturl:
|
||||
# The client ID used to authenticate Vikunja at the OpenID Connect provider.
|
||||
clientid:
|
||||
# The client secret used to authenticate Vikunja at the OpenID Connect provider.
|
||||
|
@ -321,3 +312,28 @@ metrics:
|
|||
username:
|
||||
# If set to a non-empty value the /metrics endpoint will require this as a password via basic auth in combination with the username below.
|
||||
password:
|
||||
|
||||
# Provide default settings for new users. When a new user is created, these settings will automatically be set for the user. If you change them in the config file afterwards they will not be changed back for existing users.
|
||||
defaultsettings:
|
||||
# The avatar source for the user. Can be `gravatar`, `initials`, `upload` or `marble`. If you set this to `upload` you'll also need to specify `defaultsettings.avatar_file_id`.
|
||||
avatar_provider: initials
|
||||
# The id of the file used as avatar.
|
||||
avatar_file_id: 0
|
||||
# If set to true users will get task reminders via email.
|
||||
email_reminders_enabled: false
|
||||
# If set to true will allow other users to find this user when searching for parts of their name.
|
||||
discoverable_by_name: false
|
||||
# If set to true will allow other users to find this user when searching for their exact email.
|
||||
discoverable_by_email: false
|
||||
# If set to true will send an email every day with all overdue tasks at a configured time.
|
||||
overdue_tasks_reminders_enabled: true
|
||||
# When to send the overdue task reminder email.
|
||||
overdue_tasks_reminders_time: 9:00
|
||||
# The id of the default project. Make sure users actually have access to this project when setting this value.
|
||||
default_project_id: 0
|
||||
# Start of the week for the user. `0` is sunday, `1` is monday and so on.
|
||||
week_start: 0
|
||||
# The language of the user interface. Must be an ISO 639-1 language code. Will default to the browser language the user uses when signing up.
|
||||
language: <unset>
|
||||
# The time zone of each individual user. This will affect when users get reminders and overdue task emails.
|
||||
timezone: <time zone set at service.timezone>
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
image: vikunja/api:unstable
|
||||
manifests:
|
||||
-
|
||||
image: vikunja/api:unstable-linux-amd64
|
||||
platform:
|
||||
architecture: amd64
|
||||
os: linux
|
||||
-
|
||||
image: vikunja/api:unstable-linux-arm64
|
||||
platform:
|
||||
architecture: arm64
|
||||
os: linux
|
|
@ -1,18 +0,0 @@
|
|||
image: vikunja/api:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}}
|
||||
{{#if build.tags}}
|
||||
tags:
|
||||
{{#each build.tags}}
|
||||
- {{this}}
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
manifests:
|
||||
-
|
||||
image: vikunja/api:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-amd64
|
||||
platform:
|
||||
architecture: amd64
|
||||
os: linux
|
||||
-
|
||||
image: vikunja/api:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm64
|
||||
platform:
|
||||
architecture: arm64
|
||||
os: linux
|
15
docker/entrypoint.sh
Normal file
15
docker/entrypoint.sh
Normal file
|
@ -0,0 +1,15 @@
|
|||
#!/usr/bin/env sh
|
||||
set -e
|
||||
|
||||
if [ -n "$PUID" ] && [ "$PUID" -ne 0 ] && \
|
||||
[ -n "$PGID" ] && [ "$PGID" -ne 0 ] ; then
|
||||
echo "info: creating the new user vikunja with $PUID:$PGID"
|
||||
groupmod -g "$PGID" -o vikunja
|
||||
usermod -u "$PUID" -o vikunja
|
||||
chown -R vikunja:vikunja ./files/
|
||||
chown vikunja:vikunja .
|
||||
exec su vikunja -c /app/vikunja/vikunja "$@"
|
||||
else
|
||||
echo "info: creation of non-root user is skipped"
|
||||
exec /app/vikunja/vikunja "$@"
|
||||
fi
|
0
docs/.hugo_build.lock
Normal file
0
docs/.hugo_build.lock
Normal file
|
@ -28,7 +28,7 @@ markup:
|
|||
menu:
|
||||
page:
|
||||
- name: Home
|
||||
url: https://vikunja.io/en/
|
||||
url: https://vikunja.io/
|
||||
weight: 10
|
||||
- name: Features
|
||||
url: https://vikunja.io/features
|
||||
|
@ -36,6 +36,9 @@ menu:
|
|||
- name: Download
|
||||
url: https://vikunja.io/download
|
||||
weight: 30
|
||||
- name: Blog
|
||||
url: https://vikunja.io/blog/
|
||||
weight: 35
|
||||
- name: Docs
|
||||
url: https://vikunja.io/docs
|
||||
weight: 40
|
||||
|
@ -45,6 +48,9 @@ menu:
|
|||
- name: Community
|
||||
url: https://community.vikunja.io/
|
||||
weight: 60
|
||||
- name: Stickers
|
||||
url: https://vikunja.cloud/stickers?utm_source=io&utm_medium=io&utm_campaign=menu
|
||||
weight: 65
|
||||
- name: Get it Hosted
|
||||
url: https://vikunja.cloud/?utm_source=io&utm_medium=io&utm_campaign=menu
|
||||
weight: 70
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
date: "2019-02-12:00:00+02:00"
|
||||
date: "2022-09-21:00:00+02:00"
|
||||
title: "Development"
|
||||
toc: true
|
||||
draft: false
|
||||
|
@ -36,13 +36,13 @@ 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
|
||||
|
||||
The `main` branch is the latest and bleeding edge branch with all changes. Unstable releases are automatically
|
||||
created from this branch.
|
||||
The `main` branch is the latest and bleeding edge branch with all changes. Unstable releases are automatically created from this branch.
|
||||
|
||||
A release gets tagged from the main branch with the version name as tag name.
|
||||
|
||||
|
@ -50,7 +50,6 @@ Backports and point-releases should go to a `release/version` branch, based on t
|
|||
|
||||
## Conventional commits
|
||||
|
||||
We're using [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) because they greatly simplify
|
||||
generating release notes.
|
||||
We're using [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) because they greatly simplify generating release notes.
|
||||
|
||||
It is not required to use them when creating a PR, but appreciated.
|
||||
|
|
|
@ -49,7 +49,7 @@ func (err ErrUserDoesNotExist) Error() string {
|
|||
// This needs to be unique, so you should check whether the error code exists or not.
|
||||
// The general convention for error codes is as follows:
|
||||
// * Every "group" errors lives in a thousend something. For example all user issues are 1000-something, all
|
||||
// list errors are 3000-something and so on.
|
||||
// project errors are 3000-something and so on.
|
||||
// * New error codes should be the current max error code + 1. Don't take free numbers to prevent old errors
|
||||
// which are depricated and removed from being "new ones". For example, if there are error codes 1001, 1002, 1004,
|
||||
// a new error should be 1005 and not 1003.
|
||||
|
|
|
@ -26,8 +26,8 @@ It returns the `limit` (max-length) and `offset` parameters needed for SQL-Queri
|
|||
You can feed this function directly into xorm's `Limit`-Function like so:
|
||||
|
||||
{{< highlight golang >}}
|
||||
lists := []List{}
|
||||
err := x.Limit(getLimitFromPageIndex(pageIndex, itemsPerPage)).Find(&lists)
|
||||
projects := []*Project{}
|
||||
err := x.Limit(getLimitFromPageIndex(pageIndex, itemsPerPage)).Find(&projects)
|
||||
{{< /highlight >}}
|
||||
|
||||
// TODO: Add a full example from start to finish, like a tutorial on how to create a new endpoint?
|
||||
|
|
|
@ -21,7 +21,7 @@ There are two ways of migrating data from another service:
|
|||
oauth flow. You can then call the service's api on behalf of your user to get all the data.
|
||||
The Todoist, Trello and Microsoft To-Do Migrators use this pattern.
|
||||
2. A file migration where the user uploads a file obtained from some third-party service. In your migrator, you need
|
||||
to parse the file and create the lists, tasks etc.
|
||||
to parse the file and create the projects, tasks etc.
|
||||
The Vikunja File Import uses this pattern.
|
||||
|
||||
To differentiate the two, there are two different interfaces you must implement.
|
||||
|
@ -61,7 +61,7 @@ type FileMigrator interface {
|
|||
// Name holds the name of the migration.
|
||||
// This is used to show the name to users and to keep track of users who already migrated.
|
||||
Name() string
|
||||
// Migrate is the interface used to migrate a user's tasks, list and other things from a file to vikunja.
|
||||
// Migrate is the interface used to migrate a user's tasks, projects and other things from a file to vikunja.
|
||||
// The user object is the user who's tasks will be migrated.
|
||||
Migrate(user *user.User, file io.ReaderAt, size int64) error
|
||||
}
|
||||
|
@ -103,9 +103,9 @@ You should also document the routes with [swagger annotations]({{< ref "swagger-
|
|||
## Insertion helper method
|
||||
|
||||
There is a method available in the `migration` package which takes a fully nested Vikunja structure and creates it with all relations.
|
||||
This means you start by adding a namespace, then add lists inside of that namespace, then tasks in the lists and so on.
|
||||
This means you start by adding a namespace, then add projects inside that namespace, then tasks in the lists and so on.
|
||||
|
||||
The root structure must be present as `[]*models.NamespaceWithListsAndTasks`. It allows to represent all of Vikunja's
|
||||
The root structure must be present as `[]*models.NamespaceWithProjectsAndTasks`. It allows to represent all of Vikunja's
|
||||
hierachie as a single data structure.
|
||||
|
||||
Then call the method like so:
|
||||
|
|
39
docs/content/doc/development/releasing.md
Normal file
39
docs/content/doc/development/releasing.md
Normal file
|
@ -0,0 +1,39 @@
|
|||
---
|
||||
title: "Releasing a new Vikunja version"
|
||||
date: 2022-10-28T13:06:05+02:00
|
||||
draft: false
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "development"
|
||||
---
|
||||
|
||||
# Releasing a new Vikunja version
|
||||
|
||||
This checklist is a collection of all steps usually involved when releasing a new version of Vikunja.
|
||||
Not all steps are necessary for every release.
|
||||
|
||||
* Website update :
|
||||
* New Features: If there are new features worth mentioning the feature page should be updated.
|
||||
* New Screenshots: If an overhaul of an existing feature happend so that it now looks different from the existing screenshot, a new one is required.
|
||||
* Generate changelogs: (with git-cliff)
|
||||
* Frontend
|
||||
* API
|
||||
* Desktop
|
||||
* Tag a new version: Include the changelog for that version as the tag message
|
||||
* Frontend
|
||||
* API
|
||||
* Desktop
|
||||
* Once built: Prune the cloudflare cache so that the new versions show up at dl.vikunja.io
|
||||
* Release Highlights Blogpost:
|
||||
* Include a section about Vikunja in general (totally fine to copy one from the earlier blog posts)
|
||||
* New Features & Improvements: Mention bigger features, potentially with screenshots. Things like refactoring are sometimes also worth mentioneing.
|
||||
* Publish:
|
||||
* Reddit
|
||||
* Twitter
|
||||
* Mastodon
|
||||
* Chat
|
||||
* Newsletter
|
||||
* Forum
|
||||
* If features in the release were sponsored, send an email to relevant stakeholders
|
||||
* Update Vikunja Cloud version and other instances
|
||||
|
|
@ -108,7 +108,7 @@ Contains all possible avatar providers a user can choose to set their avatar.
|
|||
|
||||
#### background
|
||||
|
||||
All list background providers are in sub-packages of this package.
|
||||
All project background providers are in sub-packages of this package.
|
||||
|
||||
#### dump
|
||||
|
||||
|
|
|
@ -19,29 +19,51 @@ The api documentation is generated using [swaggo](https://github.com/swaggo/swag
|
|||
You should always comment every field which will be exposed as a json in the api.
|
||||
These comments will show up in the documentation, it'll make it easier for developers using the api.
|
||||
|
||||
As an example, this is the definition of a list with all comments:
|
||||
As an example, this is the definition of a project with all comments:
|
||||
|
||||
{{< highlight golang >}}
|
||||
// List represents a list of tasks
|
||||
type List struct {
|
||||
// The unique, numeric id of this list.
|
||||
ID int64 `xorm:"bigint autoincr not null unique pk" json:"id" param:"list"`
|
||||
// The title of the list. You'll see this in the namespace overview.
|
||||
Title string `xorm:"varchar(250)" json:"title" valid:"required,runelength(3|250)" minLength:"3" maxLength:"250"`
|
||||
// The description of the list.
|
||||
Description string `xorm:"varchar(1000)" json:"description" valid:"runelength(0|1000)" maxLength:"1000"`
|
||||
OwnerID int64 `xorm:"bigint INDEX" json:"-"`
|
||||
NamespaceID int64 `xorm:"bigint INDEX" json:"-" param:"namespace"`
|
||||
type Project struct {
|
||||
// The unique, numeric id of this project.
|
||||
ID int64 `xorm:"bigint autoincr not null unique pk" json:"id" param:"project"`
|
||||
// The title of the project. You'll see this in the namespace overview.
|
||||
Title string `xorm:"varchar(250) not null" json:"title" valid:"required,runelength(1|250)" minLength:"1" maxLength:"250"`
|
||||
// The description of the project.
|
||||
Description string `xorm:"longtext null" json:"description"`
|
||||
// The unique project short identifier. Used to build task identifiers.
|
||||
Identifier string `xorm:"varchar(10) null" json:"identifier" valid:"runelength(0|10)" minLength:"0" maxLength:"10"`
|
||||
// The hex color of this project
|
||||
HexColor string `xorm:"varchar(6) null" json:"hex_color" valid:"runelength(0|6)" maxLength:"6"`
|
||||
|
||||
// The user who created this list.
|
||||
Owner User `xorm:"-" json:"owner" valid:"-"`
|
||||
// An array of tasks which belong to the list.
|
||||
Tasks []*ListTask `xorm:"-" json:"tasks"`
|
||||
OwnerID int64 `xorm:"bigint INDEX not null" json:"-"`
|
||||
NamespaceID int64 `xorm:"bigint INDEX not null" json:"namespace_id" param:"namespace"`
|
||||
|
||||
// A unix timestamp when this list was created. You cannot change this value.
|
||||
Created int64 `xorm:"created" json:"created"`
|
||||
// A unix timestamp when this list was last updated. You cannot change this value.
|
||||
Updated int64 `xorm:"updated" json:"updated"`
|
||||
// The user who created this project.
|
||||
Owner *user.User `xorm:"-" json:"owner" valid:"-"`
|
||||
|
||||
// Whether or not a project is archived.
|
||||
IsArchived bool `xorm:"not null default false" json:"is_archived" query:"is_archived"`
|
||||
|
||||
// The id of the file this project has set as background
|
||||
BackgroundFileID int64 `xorm:"null" json:"-"`
|
||||
// Holds extra information about the background set since some background providers require attribution or similar. If not null, the background can be accessed at /projects/{projectID}/background
|
||||
BackgroundInformation interface{} `xorm:"-" json:"background_information"`
|
||||
// Contains a very small version of the project background to use as a blurry preview until the actual background is loaded. Check out https://blurha.sh/ to learn how it works.
|
||||
BackgroundBlurHash string `xorm:"varchar(50) null" json:"background_blur_hash"`
|
||||
|
||||
// True if a project is a favorite. Favorite projects show up in a separate namespace. This value depends on the user making the call to the api.
|
||||
IsFavorite bool `xorm:"-" json:"is_favorite"`
|
||||
|
||||
// The subscription status for the user reading this project. You can only read this property, use the subscription endpoints to modify it.
|
||||
// Will only returned when retreiving one project.
|
||||
Subscription *Subscription `xorm:"-" json:"subscription,omitempty"`
|
||||
|
||||
// The position this project has when querying all projects. See the tasks.position property on how to use this.
|
||||
Position float64 `xorm:"double null" json:"position"`
|
||||
|
||||
// A timestamp when this project was created. You cannot change this value.
|
||||
Created time.Time `xorm:"created not null" json:"created"`
|
||||
// A timestamp when this project was last updated. You cannot change this value.
|
||||
Updated time.Time `xorm:"updated not null" json:"updated"`
|
||||
|
||||
web.CRUDable `xorm:"-" json:"-"`
|
||||
web.Rights `xorm:"-" json:"-"`
|
||||
|
|
|
@ -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 run 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 run test:unit-watch
|
||||
{{< /highlight >}}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
date: "2019-02-12:00:00+02:00"
|
||||
date: "2022-09-21:00:00+02:00"
|
||||
title: "Build from sources"
|
||||
draft: false
|
||||
type: "doc"
|
||||
|
@ -22,7 +22,7 @@ That means compiling it boils down to these steps:
|
|||
1. Make sure [Go](https://golang.org/doc/install) is properly installed on your system. You'll need at least Go `1.17`.
|
||||
2. Make sure [Mage](https://magefile.org) is properly installed on your system.
|
||||
3. Clone the repo with `git clone https://code.vikunja.io/api` and switch into the directory.
|
||||
3. Run `mage build:build` in the source of this repo. This will build a binary in the root of the repo which will be able to run on your system.
|
||||
4. Run `mage build:build` in the source of this repo. This will build a binary in the root of the repo which will be able to run on your system.
|
||||
|
||||
*Note:* Static ressources such as email templates are built into the binary.
|
||||
For these to work, you may need to run `mage build:generate` before building the vikunja binary.
|
||||
|
@ -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/installation) 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 run build`. This will result in a static js bundle in the `dist/` folder which you can deploy.
|
||||
|
|
|
@ -208,7 +208,7 @@ Environment path: `VIKUNJA_SERVICE_MOTD`
|
|||
|
||||
### enablelinksharing
|
||||
|
||||
Enable sharing of lists via a link
|
||||
Enable sharing of project via a link
|
||||
|
||||
Default: `true`
|
||||
|
||||
|
@ -657,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`
|
||||
|
||||
|
@ -839,7 +840,7 @@ Environment path: `VIKUNJA_LOG_HTTP`
|
|||
|
||||
### echo
|
||||
|
||||
Echo has its own logging which usually is unnessecary, which is why it is disabled by default. Possible values are stdout, stderr, file or off to disable standard logging.
|
||||
Echo has its own logging which usually is unnecessary, which is why it is disabled by default. Possible values are stdout, stderr, file or off to disable standard logging.
|
||||
|
||||
Default: `off`
|
||||
|
||||
|
@ -852,7 +853,7 @@ Environment path: `VIKUNJA_LOG_ECHO`
|
|||
|
||||
Whether or not to log events. Useful for debugging. Possible values are stdout, stderr, file or off to disable events logging.
|
||||
|
||||
Default: `stdout`
|
||||
Default: `off`
|
||||
|
||||
Full path: `log.events`
|
||||
|
||||
|
@ -968,17 +969,6 @@ Environment path: `VIKUNJA_FILES_MAXSIZE`
|
|||
|
||||
|
||||
|
||||
### wunderlist
|
||||
|
||||
These are the settings for the wunderlist migrator
|
||||
|
||||
Default: `<empty>`
|
||||
|
||||
Full path: `migration.wunderlist`
|
||||
|
||||
Environment path: `VIKUNJA_MIGRATION_WUNDERLIST`
|
||||
|
||||
|
||||
### todoist
|
||||
|
||||
Default: `<empty>`
|
||||
|
@ -1031,7 +1021,7 @@ Environment path: `VIKUNJA_AVATAR_GRAVATAREXPIRATION`
|
|||
|
||||
### enabled
|
||||
|
||||
Whether to enable backgrounds for lists at all.
|
||||
Whether to enable backgrounds for projects at all.
|
||||
|
||||
Default: `true`
|
||||
|
||||
|
@ -1173,3 +1163,132 @@ Full path: `metrics.password`
|
|||
Environment path: `VIKUNJA_METRICS_PASSWORD`
|
||||
|
||||
|
||||
---
|
||||
|
||||
## defaultsettings
|
||||
|
||||
Provide default settings for new users. When a new user is created, these settings will automatically be set for the user. If you change them in the config file afterwards they will not be changed back for existing users.
|
||||
|
||||
|
||||
|
||||
### avatar_provider
|
||||
|
||||
The avatar source for the user. Can be `gravatar`, `initials`, `upload` or `marble`. If you set this to `upload` you'll also need to specify `defaultsettings.avatar_file_id`.
|
||||
|
||||
Default: `initials`
|
||||
|
||||
Full path: `defaultsettings.avatar_provider`
|
||||
|
||||
Environment path: `VIKUNJA_DEFAULTSETTINGS_AVATAR_PROVIDER`
|
||||
|
||||
|
||||
### avatar_file_id
|
||||
|
||||
The id of the file used as avatar.
|
||||
|
||||
Default: `0`
|
||||
|
||||
Full path: `defaultsettings.avatar_file_id`
|
||||
|
||||
Environment path: `VIKUNJA_DEFAULTSETTINGS_AVATAR_FILE_ID`
|
||||
|
||||
|
||||
### email_reminders_enabled
|
||||
|
||||
If set to true users will get task reminders via email.
|
||||
|
||||
Default: `false`
|
||||
|
||||
Full path: `defaultsettings.email_reminders_enabled`
|
||||
|
||||
Environment path: `VIKUNJA_DEFAULTSETTINGS_EMAIL_REMINDERS_ENABLED`
|
||||
|
||||
|
||||
### discoverable_by_name
|
||||
|
||||
If set to true will allow other users to find this user when searching for parts of their name.
|
||||
|
||||
Default: `false`
|
||||
|
||||
Full path: `defaultsettings.discoverable_by_name`
|
||||
|
||||
Environment path: `VIKUNJA_DEFAULTSETTINGS_DISCOVERABLE_BY_NAME`
|
||||
|
||||
|
||||
### discoverable_by_email
|
||||
|
||||
If set to true will allow other users to find this user when searching for their exact email.
|
||||
|
||||
Default: `false`
|
||||
|
||||
Full path: `defaultsettings.discoverable_by_email`
|
||||
|
||||
Environment path: `VIKUNJA_DEFAULTSETTINGS_DISCOVERABLE_BY_EMAIL`
|
||||
|
||||
|
||||
### overdue_tasks_reminders_enabled
|
||||
|
||||
If set to true will send an email every day with all overdue tasks at a configured time.
|
||||
|
||||
Default: `true`
|
||||
|
||||
Full path: `defaultsettings.overdue_tasks_reminders_enabled`
|
||||
|
||||
Environment path: `VIKUNJA_DEFAULTSETTINGS_OVERDUE_TASKS_REMINDERS_ENABLED`
|
||||
|
||||
|
||||
### overdue_tasks_reminders_time
|
||||
|
||||
When to send the overdue task reminder email.
|
||||
|
||||
Default: `9:00`
|
||||
|
||||
Full path: `defaultsettings.overdue_tasks_reminders_time`
|
||||
|
||||
Environment path: `VIKUNJA_DEFAULTSETTINGS_OVERDUE_TASKS_REMINDERS_TIME`
|
||||
|
||||
|
||||
### default_project_id
|
||||
|
||||
The id of the default project. Make sure users actually have access to this project when setting this value.
|
||||
|
||||
Default: `0`
|
||||
|
||||
Full path: `defaultsettings.default_project_id`
|
||||
|
||||
Environment path: `VIKUNJA_DEFAULTSETTINGS_DEFAULT_PROJECT_ID`
|
||||
|
||||
|
||||
### week_start
|
||||
|
||||
Start of the week for the user. `0` is sunday, `1` is monday and so on.
|
||||
|
||||
Default: `0`
|
||||
|
||||
Full path: `defaultsettings.week_start`
|
||||
|
||||
Environment path: `VIKUNJA_DEFAULTSETTINGS_WEEK_START`
|
||||
|
||||
|
||||
### language
|
||||
|
||||
The language of the user interface. Must be an ISO 639-1 language code. Will default to the browser language the user uses when signing up.
|
||||
|
||||
Default: `<unset>`
|
||||
|
||||
Full path: `defaultsettings.language`
|
||||
|
||||
Environment path: `VIKUNJA_DEFAULTSETTINGS_LANGUAGE`
|
||||
|
||||
|
||||
### timezone
|
||||
|
||||
The time zone of each individual user. This will affect when users get reminders and overdue task emails.
|
||||
|
||||
Default: `<time zone set at service.timezone>`
|
||||
|
||||
Full path: `defaultsettings.timezone`
|
||||
|
||||
Environment path: `VIKUNJA_DEFAULTSETTINGS_TIMEZONE`
|
||||
|
||||
|
||||
|
|
|
@ -76,7 +76,7 @@ This defines four services, each with their own container:
|
|||
|
||||
* An api service which runs the vikunja api. Most of the core logic lives here.
|
||||
* The frontend which will make vikunja actually usable for most people.
|
||||
* A database container which will store all lists, tasks, etc. We're using mariadb here, but you're free to use mysql or postgres if you want.
|
||||
* A database container which will store all projects, tasks, etc. We're using mariadb here, but you're free to use mysql or postgres if you want.
|
||||
* A proxy service which makes the frontend and api available on the same port, redirecting all requests to `/api` to the api container.
|
||||
If you already have a proxy on your host, you may want to check out the [reverse proxy examples]() to use that.
|
||||
By default, it uses port 80 on the host.
|
||||
|
@ -201,7 +201,6 @@ You should see something like this:
|
|||
"max_file_size": "20MB",
|
||||
"registration_enabled": true,
|
||||
"available_migrators": [
|
||||
"wunderlist",
|
||||
"todoist"
|
||||
],
|
||||
"task_attachments_enabled": true
|
||||
|
|
|
@ -10,7 +10,7 @@ menu:
|
|||
|
||||
# Full docker example
|
||||
|
||||
This docker compose configuration will run Vikunja with backend and frontend with a mariadb as database.
|
||||
This docker compose configuration will run Vikunja with backend and frontend with a mariadb database.
|
||||
It uses an nginx container or traefik on the host to proxy backend and frontend into a single port.
|
||||
|
||||
For all available configuration options, see [configuration]({{< ref "config.md">}}).
|
||||
|
@ -76,7 +76,7 @@ This example lets you host Vikunja without any reverse proxy in front of it. Thi
|
|||
you need to get something up and running. If you want to host Vikunja on one single port instead of two different ones
|
||||
or need tls termination, check out one of the other examples.
|
||||
|
||||
Not that you need to change the `VIKUNJA_API_URL` environment variable to the ip (the docker host you're running this on)
|
||||
Note that you need to change the `VIKUNJA_API_URL` environment variable to the ip (the docker host you're running this on)
|
||||
is reachable at. Because the browser you'll use to access the Vikunja frontend uses that url to make the requests, it
|
||||
has to be able to reach that ip + port from the outside. Putting everything in a private network won't work.
|
||||
|
||||
|
@ -125,7 +125,7 @@ services:
|
|||
|
||||
This example assumes [traefik](https://traefik.io) version 2 installed and configured to [use docker as a configuration provider](https://docs.traefik.io/providers/docker/).
|
||||
|
||||
We also make a few assumtions here which you'll most likely need to adjust for your traefik setup:
|
||||
We also make a few assumptions here which you'll most likely need to adjust for your traefik setup:
|
||||
|
||||
* Your domain is `vikunja.example.com`
|
||||
* The entrypoint you want to make vikunja available from is called `https`
|
||||
|
@ -155,7 +155,7 @@ services:
|
|||
restart: unless-stopped
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.vikunja-api.rule=Host(`vikunja.example.com`) && PathPrefix(`/api/v1`, `/dav/`, `/.well-known/`)"
|
||||
- "traefik.http.routers.vikunja-api.rule=Host(`vikunja.example.com`) && (PathPrefix(`/api/v1`) || PathPrefix(`/dav/`) || PathPrefix(`/.well-known/`))"
|
||||
- "traefik.http.routers.vikunja-api.entrypoints=https"
|
||||
- "traefik.http.routers.vikunja-api.tls.certResolver=acme"
|
||||
frontend:
|
||||
|
@ -398,7 +398,7 @@ docker main folders:
|
|||
* vikunja
|
||||
* mariadb
|
||||
|
||||
Synology has it's own GUI for managing Docker containers... But it's easier via docker compose.
|
||||
Synology has its own GUI for managing Docker containers... But it's easier via docker compose.
|
||||
|
||||
To do that, you can
|
||||
|
||||
|
@ -407,7 +407,7 @@ To do that, you can
|
|||
* without activating SSH, by using Portainer (you have to install first, check out [this tutorial](https://www.portainer.io/blog/how-to-install-portainer-on-a-synology-nas) for exmple):
|
||||
1. Go to **Dashboard / Stacks** click the button **"Add Stack"**
|
||||
2. Give it the name Vikunja and paste the adapted docker compose file
|
||||
3. Deploy the Stack with the "Delpoy Stack" button:
|
||||
3. Deploy the Stack with the "Deploy Stack" button:
|
||||
|
||||
![Portainer Stack deploy](/docs/synology-proxy-2.png)
|
||||
|
||||
|
|
|
@ -83,7 +83,7 @@ WantedBy=multi-user.target
|
|||
|
||||
If you've installed Vikunja to a directory other than `/opt/vikunja`, you need to adapt `WorkingDirectory` accordingly.
|
||||
|
||||
After you made all nessecary modifications, it's time to start the service:
|
||||
After you made all necessary modifications, it's time to start the service:
|
||||
|
||||
{{< highlight bash >}}
|
||||
sudo systemctl enable vikunja
|
||||
|
@ -97,7 +97,7 @@ To build vikunja from source, see [building from source]({{< ref "build-from-sou
|
|||
### Updating
|
||||
|
||||
Simply replace the binary and templates with the new version, then restart Vikunja.
|
||||
It will automatically run all nessecary database migrations.
|
||||
It will automatically run all necessary database migrations.
|
||||
**Make sure to take a look at the changelog for the new version to not miss any manual steps the update may involve!**
|
||||
|
||||
## Docker
|
||||
|
|
|
@ -35,7 +35,7 @@ Just open the file with a text editor - there are comments which will explain ho
|
|||
|
||||
## Docker
|
||||
|
||||
The docker image is based on nginx and just contains all nessecary files for the frontend.
|
||||
The docker image is based on nginx and just contains all necessary files for the frontend.
|
||||
|
||||
To run it, all you need is
|
||||
|
||||
|
@ -47,10 +47,7 @@ which will run the docker image and expose port 80 on the host.
|
|||
|
||||
See [full docker example]({{< ref "full-docker-example.md">}}) for more varations of this config.
|
||||
|
||||
### Setting user and group id of the user running vikunja
|
||||
|
||||
You can set the user and group id of the user running vikunja with the `PUID` and `PGID` evironment variables.
|
||||
This follows the pattern used by [the linuxserver.io](https://docs.linuxserver.io/general/understanding-puid-and-pgid) docker images.
|
||||
The docker container runs as an unprivileged user and does not mount anything.
|
||||
|
||||
### API URL configuration in docker
|
||||
|
||||
|
|
|
@ -56,3 +56,4 @@ A third-party Helm Chart is available from the k8s-at-home project [here](https:
|
|||
* [Install Vikunja in Docker for self-hosted Task Tracking](https://smarthomepursuits.com/install-vikunja-in-docker-for-self-hosted-task-tracking/)
|
||||
* [Self-Hosted To-Do List with Vikunja in Docker](https://www.youtube.com/watch?v=DqyqDWpEvKI) (Youtube)
|
||||
* [Vikunja self-hosted (step by step)](https://nguyenminhhung.com/vikunja-self-hosted-step-by-step/)
|
||||
* [How to Install Vikunja on Your Synology NAS](https://mariushosting.com/how-to-install-vikunja-on-your-synology-nas/)
|
||||
|
|
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/charts/stable/vikunja/)
|
||||
* [k8s at Home](https://github.com/k8s-at-home/charts)
|
||||
|
68
docs/content/doc/setup/openid-examples.md
Normal file
68
docs/content/doc/setup/openid-examples.md
Normal file
|
@ -0,0 +1,68 @@
|
|||
---
|
||||
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
|
||||
```
|
||||
|
||||
## Google / Google Workspace
|
||||
|
||||
Vikunja Config:
|
||||
|
||||
```yaml
|
||||
openid:
|
||||
enabled: true
|
||||
redirecturl: https://vikunja.mydomain.com/auth/openid/ <---- slash at the end is important
|
||||
providers:
|
||||
- name: Google
|
||||
authurl: https://accounts.google.com
|
||||
clientid: <google-oauth-client-id>
|
||||
clientsecret: <google-oauth-client-secret>
|
||||
```
|
||||
|
||||
Google config:
|
||||
|
||||
- Navigate to https://console.cloud.google.com/apis/credentials in the target project
|
||||
- Create a new OAuth client ID
|
||||
- Configure an authorized redirect URI of https://vikunja.mydomain.com/auth/openid/google
|
||||
|
||||
Note that there currently seems to be no way to stop creation of new users, even when enableregistration is false in the configuration. This means that this approach works well only with an "Internal Organization" app for Google Workspace, which limits the allowed users to organizational accounts only. External / public applications will potentially allow every Google user to register.
|
51
docs/content/doc/setup/subdirectory.md
Normal file
51
docs/content/doc/setup/subdirectory.md
Normal file
|
@ -0,0 +1,51 @@
|
|||
---
|
||||
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.
|
||||
|
||||
### Dynamicly set with build command
|
||||
|
||||
Run the build with the `VIKUNJA_FRONTEND_BASE` variable specified.
|
||||
|
||||
```
|
||||
VIKUNJA_FRONTEND_BASE=/SUBPATH/ pnpm run build
|
||||
```
|
||||
|
||||
Where `SUBPATH` is the subdirectory you want to run Vikunja on.
|
||||
|
||||
### Set via .env.local
|
||||
|
||||
* Copy `.env.local.example` to `.env.local`
|
||||
* Uncomment `VIKUNJA_FRONTEND_BASE` and set `/subpath/` to the desired path.
|
||||
|
||||
After saving, build Vikunja as normal.
|
||||
|
||||
```
|
||||
pnpm run build
|
||||
```
|
||||
|
||||
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.
|
|
@ -12,7 +12,7 @@ menu:
|
|||
|
||||
Vikunja itself is always fully capable of handling utf-8 characters.
|
||||
However, your database might be not.
|
||||
Vikunja itself will work just fine until you want to use non-latin characters in your tasks/lists/etc.
|
||||
Vikunja itself will work just fine until you want to use non-latin characters in your tasks/projects/etc.
|
||||
|
||||
On this page, you will find information about how to fully ensure non-latin characters like aüäß or emojis work
|
||||
with your installation.
|
||||
|
|
|
@ -24,10 +24,10 @@ All urls are located under the `/dav` subspace.
|
|||
|
||||
Urls are:
|
||||
|
||||
* `/principals/<username>/`: Returns urls for list discovery. *Use this url to initially make connections to new clients.*
|
||||
* `/lists/`: Used to manage lists
|
||||
* `/lists/<List ID>/`: Used to manage a single list
|
||||
* `/lists/<List ID>/<Task UID>`: Used to manage a task on a list
|
||||
* `/principals/<username>/`: Returns urls for project discovery. *Use this url to initially make connections to new clients.*
|
||||
* `/projects/`: Used to manage projects
|
||||
* `/projects/<List ID>/`: Used to manage a single project
|
||||
* `/projects/<List ID>/<Task UID>`: Used to manage a task on a project
|
||||
|
||||
## Supported properties
|
||||
|
||||
|
@ -37,6 +37,7 @@ Vikunja currently supports the following properties:
|
|||
* `SUMMARY`
|
||||
* `DESCRIPTION`
|
||||
* `PRIORITY`
|
||||
* `CATEGORIES`
|
||||
* `COMPLETED`
|
||||
* `DUE`
|
||||
* `DTSTART`
|
||||
|
@ -46,11 +47,11 @@ Vikunja currently supports the following properties:
|
|||
* `CREATED`
|
||||
* `DTSTAMP`
|
||||
* `LAST-MODIFIED`
|
||||
* Recurrence
|
||||
|
||||
Vikunja **currently does not** support these properties:
|
||||
|
||||
* `ATTACH`
|
||||
* `CATEGORIES`
|
||||
* `CLASS`
|
||||
* `COMMENT`
|
||||
* `GEO`
|
||||
|
@ -61,7 +62,6 @@ Vikunja **currently does not** support these properties:
|
|||
* `CONTACT`
|
||||
* `RECURRENCE-ID`
|
||||
* `URL`
|
||||
* Recurrence
|
||||
* `SEQUENCE`
|
||||
|
||||
## Tested Clients
|
||||
|
|
|
@ -26,6 +26,15 @@ If you don't specify a command, the [`web`](#web) command will be executed.
|
|||
|
||||
All commands use the same standard [config file]({{< ref "../setup/config.md">}}).
|
||||
|
||||
## Using the cli in docker
|
||||
|
||||
When running Vikunja in docker, you'll need to execute all commands in the `api` container.
|
||||
Instead of running the `vikunja` binary directly, run it like this:
|
||||
|
||||
```
|
||||
docker exec <name of the vikunja api container> /app/vikunja/vikunja <subcommand>
|
||||
```
|
||||
|
||||
### `dump`
|
||||
|
||||
Creates a zip file with all vikunja-related files.
|
||||
|
@ -127,6 +136,21 @@ Flags:
|
|||
* `-p`, `--password`: The password of the new user. You will be asked to enter it if not provided through the flag.
|
||||
* `-u`, `--username`: The username of the new user.
|
||||
|
||||
#### `user delete`
|
||||
|
||||
Start the user deletion process.
|
||||
If called without the `--now` flag, this command will only trigger an email to the user in order for them to confirm and start the deletion process (this is the same behavoir as if the user requested their deletion via the web interface).
|
||||
With the flag the user is deleted **immediately**.
|
||||
|
||||
**USE WITH CAUTION.**
|
||||
|
||||
{{< highlight bash >}}
|
||||
$ vikunja user delete <id> <flags>
|
||||
{{< /highlight >}}
|
||||
|
||||
Flags:
|
||||
* `-n`, `--now` If provided, deletes the user immediately instead of emailing them first.
|
||||
|
||||
#### `user list`
|
||||
|
||||
Shows a list of all users.
|
||||
|
|
|
@ -4,8 +4,8 @@ title: "Errors"
|
|||
draft: false
|
||||
type: "doc"
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "usage"
|
||||
sidebar:
|
||||
parent: "usage"
|
||||
---
|
||||
|
||||
# Errors
|
||||
|
@ -42,6 +42,8 @@ This document describes the different errors Vikunja can return.
|
|||
| 1018 | 412 | The provided user avatar provider type setting is invalid. |
|
||||
| 1019 | 412 | No openid email address was provided. |
|
||||
| 1020 | 412 | This user account is disabled. |
|
||||
| 1021 | 412 | This account is managed by a third-party authentication provider. |
|
||||
| 1021 | 412 | The username must not contain spaces. |
|
||||
|
||||
## Validation
|
||||
|
||||
|
@ -50,24 +52,26 @@ This document describes the different errors Vikunja can return.
|
|||
| 2001 | 400 | ID cannot be empty or 0. |
|
||||
| 2002 | 400 | Some of the request data was invalid. The response contains an aditional array with all invalid fields. |
|
||||
|
||||
## List
|
||||
## Project
|
||||
|
||||
| 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. |
|
||||
|-----------|------------------|-------------------------------------------------------------------------------------------------------------------------------|
|
||||
| 3001 | 404 | The project does not exist. |
|
||||
| 3004 | 403 | The user needs to have read permissions on that project to perform that action. |
|
||||
| 3005 | 400 | The project title cannot be empty. |
|
||||
| 3006 | 404 | The project share does not exist. |
|
||||
| 3007 | 400 | A project with this identifier already exists. |
|
||||
| 3008 | 412 | The project is archived and can therefore only be accessed read only. This is also true for all tasks associated with this project. |
|
||||
| 3009 | 412 | The project cannot belong to a dynamically generated namespace like "Favorites". |
|
||||
| 3010 | 412 | The project must belong to a namespace. |
|
||||
|
||||
## Task
|
||||
|
||||
| ErrorCode | HTTP Status Code | Description |
|
||||
|-----------|------------------|-------------|
|
||||
| 4001 | 400 | The list task text cannot be empty. |
|
||||
| 4002 | 404 | The list task does not exist. |
|
||||
| 4003 | 403 | All bulk editing tasks must belong to the same list. |
|
||||
| 4001 | 400 | The project task text cannot be empty. |
|
||||
| 4002 | 404 | The project task does not exist. |
|
||||
| 4003 | 403 | All bulk editing tasks must belong to the same project. |
|
||||
| 4004 | 403 | Need at least one task when bulk editing tasks. |
|
||||
| 4005 | 403 | The user does not have the right to see the task. |
|
||||
| 4006 | 403 | The user tried to set a parent task as the task itself. |
|
||||
|
@ -80,10 +84,12 @@ This document describes the different errors Vikunja can return.
|
|||
| 4013 | 400 | The task sort param is invalid. |
|
||||
| 4014 | 400 | The task sort order is invalid. |
|
||||
| 4015 | 404 | The task comment does not exist. |
|
||||
| 4016 | 403 | Invalid task field. |
|
||||
| 4017 | 403 | Invalid task filter comparator. |
|
||||
| 4018 | 403 | Invalid task filter concatinator. |
|
||||
| 4019 | 403 | Invalid task filter value. |
|
||||
| 4016 | 400 | Invalid task field. |
|
||||
| 4017 | 400 | Invalid task filter comparator. |
|
||||
| 4018 | 400 | Invalid task filter concatinator. |
|
||||
| 4019 | 400 | Invalid task filter value. |
|
||||
| 4020 | 400 | The provided attachment does not belong to that task. |
|
||||
| 4021 | 400 | This user is already assigned to that task. |
|
||||
|
||||
## Namespace
|
||||
|
||||
|
@ -103,17 +109,17 @@ This document describes the different errors Vikunja can return.
|
|||
|-----------|------------------|-------------|
|
||||
| 6001 | 400 | The team name cannot be emtpy. |
|
||||
| 6002 | 404 | The team does not exist. |
|
||||
| 6004 | 409 | The team already has access to that namespace or list. |
|
||||
| 6004 | 409 | The team already has access to that namespace or project. |
|
||||
| 6005 | 409 | The user is already a member of that team. |
|
||||
| 6006 | 400 | Cannot delete the last team member. |
|
||||
| 6007 | 403 | The team does not have access to the list to perform that action. |
|
||||
| 6007 | 403 | The team does not have access to the project to perform that action. |
|
||||
|
||||
## User List Access
|
||||
## User Project Access
|
||||
|
||||
| ErrorCode | HTTP Status Code | Description |
|
||||
|-----------|------------------|-------------|
|
||||
| 7002 | 409 | The user already has access to that list. |
|
||||
| 7003 | 403 | The user does not have access to that list. |
|
||||
| 7002 | 409 | The user already has access to that project. |
|
||||
| 7003 | 403 | The user does not have access to that project. |
|
||||
|
||||
## Label
|
||||
|
||||
|
@ -134,10 +140,10 @@ This document describes the different errors Vikunja can return.
|
|||
| ErrorCode | HTTP Status Code | Description |
|
||||
|-----------|------------------|-------------|
|
||||
| 10001 | 404 | The bucket does not exist. |
|
||||
| 10002 | 400 | The bucket does not belong to that list. |
|
||||
| 10003 | 412 | You cannot remove the last bucket on a list. |
|
||||
| 10002 | 400 | The bucket does not belong to that project. |
|
||||
| 10003 | 412 | You cannot remove the last bucket on a project. |
|
||||
| 10004 | 412 | You cannot add the task to this bucket as it already exceeded the limit of tasks it can hold. |
|
||||
| 10005 | 412 | There can be only one done bucket per list. |
|
||||
| 10005 | 412 | There can be only one done bucket per project. |
|
||||
|
||||
## Saved Filters
|
||||
|
||||
|
|
|
@ -8,9 +8,9 @@ menu:
|
|||
parent: "usage"
|
||||
---
|
||||
|
||||
# List and namespace rights for teams and users
|
||||
# Project and namespace rights for teams and users
|
||||
|
||||
Whenever you share a list or namespace with a user or team, you can specify a `rights` parameter.
|
||||
Whenever you share a project or namespace with a user or team, you can specify a `rights` parameter.
|
||||
This parameter controls the rights that team or user is going to have (or has, if you request the current sharing status).
|
||||
|
||||
Rights are being specified using integers.
|
||||
|
@ -18,9 +18,9 @@ Rights are being specified using integers.
|
|||
The following values are possible:
|
||||
|
||||
| Right (int) | Meaning |
|
||||
|-------------|---------|
|
||||
|-------------|---------------------------------------------------------------------------------------------------------------|
|
||||
| 0 (Default) | Read only. Anything which is shared with this right cannot be edited. |
|
||||
| 1 | Read and write. Namespaces or lists shared with this right can be read and written to by the team or user. |
|
||||
| 1 | Read and write. Namespaces or projects shared with this right can be read and written to by the team or user. |
|
||||
| 2 | Admin. Can do anything like read and write, but can additionally manage sharing options. |
|
||||
|
||||
## Team admins
|
||||
|
|
143
go.mod
143
go.mod
|
@ -19,62 +19,63 @@ module code.vikunja.io/api
|
|||
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/ThreeDotsLabs/watermill v1.2.0
|
||||
github.com/adlio/trello v1.10.0
|
||||
github.com/arran4/golang-ical v0.0.0-20220517104411-fd89fefb0182
|
||||
github.com/arran4/golang-ical v0.0.0-20230318005454-19abf92700cc
|
||||
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef
|
||||
github.com/bbrks/go-blurhash v1.1.1
|
||||
github.com/c2h5oh/datasize v0.0.0-20220606134207-859f65c6625b
|
||||
github.com/coreos/go-oidc/v3 v3.2.0
|
||||
github.com/coreos/go-oidc/v3 v3.5.0
|
||||
github.com/cweill/gotests v1.6.0
|
||||
github.com/d4l3k/messagediff v1.2.1
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/dustinkirkland/golang-petname v0.0.0-20191129215211-8e5a1ed0cff0
|
||||
github.com/gabriel-vasile/mimetype v1.4.1
|
||||
github.com/getsentry/sentry-go v0.13.0
|
||||
github.com/go-redis/redis/v8 v8.11.5
|
||||
github.com/go-sql-driver/mysql v1.6.0
|
||||
github.com/gabriel-vasile/mimetype v1.4.2
|
||||
github.com/getsentry/sentry-go v0.19.0
|
||||
github.com/go-sql-driver/mysql v1.7.0
|
||||
github.com/go-testfixtures/testfixtures/v3 v3.8.1
|
||||
github.com/golang-jwt/jwt/v4 v4.4.2
|
||||
github.com/gocarina/gocsv v0.0.0-20230226133904-70c27cb2918a
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/iancoleman/strcase v0.2.0
|
||||
github.com/imdario/mergo v0.3.13
|
||||
github.com/imdario/mergo v0.3.14
|
||||
github.com/jinzhu/copier v0.3.5
|
||||
github.com/labstack/echo/v4 v4.7.2
|
||||
github.com/labstack/gommon v0.3.1
|
||||
github.com/lib/pq v1.10.6
|
||||
github.com/magefile/mage v1.13.0
|
||||
github.com/mattn/go-sqlite3 v1.14.14
|
||||
github.com/labstack/echo-jwt/v4 v4.1.0
|
||||
github.com/labstack/echo/v4 v4.10.2
|
||||
github.com/labstack/gommon v0.4.0
|
||||
github.com/lib/pq v1.10.7
|
||||
github.com/magefile/mage v1.14.0
|
||||
github.com/mattn/go-sqlite3 v1.14.16
|
||||
github.com/olekukonko/tablewriter v0.0.5
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||
github.com/pquerna/otp v1.3.0
|
||||
github.com/prometheus/client_golang v1.12.2
|
||||
github.com/pquerna/otp v1.4.0
|
||||
github.com/prometheus/client_golang v1.14.0
|
||||
github.com/redis/go-redis/v9 v9.0.2
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/samedi/caldav-go v3.0.0+incompatible
|
||||
github.com/spf13/afero v1.9.2
|
||||
github.com/spf13/cobra v1.5.0
|
||||
github.com/spf13/viper v1.12.0
|
||||
github.com/stretchr/testify v1.8.0
|
||||
github.com/swaggo/swag v1.8.4
|
||||
github.com/spf13/afero v1.9.5
|
||||
github.com/spf13/cobra v1.6.1
|
||||
github.com/spf13/viper v1.15.0
|
||||
github.com/stretchr/testify v1.8.2
|
||||
github.com/swaggo/swag v1.8.11
|
||||
github.com/tkuchiki/go-timezone v0.2.2
|
||||
github.com/ulule/limiter/v3 v3.10.0
|
||||
github.com/vectordotdev/go-datemath v0.1.1-0.20211214182920-0a4ac8742b93
|
||||
github.com/wneessen/go-mail v0.2.5
|
||||
github.com/yuin/goldmark v1.4.13
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d
|
||||
golang.org/x/image v0.0.0-20220617043117-41969df76e82
|
||||
golang.org/x/oauth2 v0.0.0-20220718184931-c8730f7fcb92
|
||||
golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f
|
||||
golang.org/x/sys v0.0.0-20220731174439-a90be440212d
|
||||
golang.org/x/term v0.0.0-20220526004731-065cf7ba2467
|
||||
github.com/ulule/limiter/v3 v3.11.1
|
||||
github.com/vectordotdev/go-datemath v0.1.1-0.20220323213446-f3954d0b18ae
|
||||
github.com/wneessen/go-mail v0.3.8
|
||||
github.com/yuin/goldmark v1.5.4
|
||||
golang.org/x/crypto v0.7.0
|
||||
golang.org/x/image v0.6.0
|
||||
golang.org/x/oauth2 v0.6.0
|
||||
golang.org/x/sync v0.1.0
|
||||
golang.org/x/sys v0.6.0
|
||||
golang.org/x/term v0.6.0
|
||||
gopkg.in/d4l3k/messagediff.v1 v1.2.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
src.techknowlogick.com/xgo v1.4.1-0.20210311222705-d25c33fcd864
|
||||
src.techknowlogick.com/xormigrate v1.4.0
|
||||
xorm.io/builder v0.3.9
|
||||
xorm.io/core v0.7.3
|
||||
xorm.io/xorm v1.1.2
|
||||
src.techknowlogick.com/xgo v1.7.1-0.20230307171022-b60708668fc7
|
||||
src.techknowlogick.com/xormigrate v1.5.0
|
||||
xorm.io/builder v0.3.12
|
||||
xorm.io/xorm v1.3.2
|
||||
)
|
||||
|
||||
require (
|
||||
|
@ -84,76 +85,70 @@ require (
|
|||
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/cenkalti/backoff/v3 v3.2.2 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/fsnotify/fsnotify v1.5.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // 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-chi/chi/v5 v5.0.8 // indirect
|
||||
github.com/go-jose/go-jose/v3 v3.0.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
github.com/go-openapi/jsonreference v0.19.6 // indirect
|
||||
github.com/go-openapi/spec v0.20.4 // indirect
|
||||
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/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.0.1 // 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.12 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
github.com/lithammer/shortuuid/v3 v3.0.7 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.9 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
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.1 // indirect
|
||||
github.com/onsi/ginkgo v1.16.4 // indirect
|
||||
github.com/onsi/gomega v1.16.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_model v0.2.0 // indirect
|
||||
github.com/prometheus/common v0.32.1 // indirect
|
||||
github.com/prometheus/procfs v0.7.3 // indirect
|
||||
github.com/prometheus/client_model v0.3.0 // indirect
|
||||
github.com/prometheus/common v0.39.0 // indirect
|
||||
github.com/prometheus/procfs v0.9.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.3.0 // indirect
|
||||
github.com/subosito/gotenv v1.4.2 // indirect
|
||||
github.com/syndtr/goleveldb v1.0.0 // indirect
|
||||
github.com/urfave/cli/v2 v2.3.0 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasttemplate v1.2.1 // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 // indirect
|
||||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e // 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-20220517211312-f3a8303e98df // indirect
|
||||
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||
golang.org/x/mod v0.8.0 // indirect
|
||||
golang.org/x/net v0.8.0 // indirect
|
||||
golang.org/x/text v0.8.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
golang.org/x/tools v0.6.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/protobuf v1.28.0 // indirect
|
||||
gopkg.in/ini.v1 v1.66.4 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.5.1 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
)
|
||||
|
||||
replace (
|
||||
github.com/coreos/bbolt => go.etcd.io/bbolt v1.3.4
|
||||
github.com/coreos/go-systemd => github.com/coreos/go-systemd/v22 v22.0.0
|
||||
github.com/hpcloud/tail => github.com/jeffbean/tail v1.0.1 // See https://github.com/hpcloud/tail/pull/159
|
||||
github.com/samedi/caldav-go => github.com/kolaente/caldav-go v3.0.1-0.20190524174923-9e5cd1688227+incompatible // Branch: feature/dynamic-supported-components, PR: https://github.com/samedi/caldav-go/pull/6 and https://github.com/samedi/caldav-go/pull/7
|
||||
gopkg.in/fsnotify.v1 => github.com/kolaente/fsnotify v1.4.10-0.20200411160148-1bc3c8ff4048 // See https://github.com/fsnotify/fsnotify/issues/328 and https://github.com/golang/go/issues/26904
|
||||
)
|
||||
replace github.com/samedi/caldav-go => github.com/kolaente/caldav-go v3.0.1-0.20190610114120-2a4eb8b5dcc9+incompatible // Branch: feature/dynamic-supported-components, PR: https://github.com/samedi/caldav-go/pull/6 and https://github.com/samedi/caldav-go/pull/7
|
||||
|
||||
go 1.19
|
||||
go 1.20
|
||||
|
|
37
magefile.go
37
magefile.go
|
@ -27,7 +27,6 @@ import (
|
|||
"fmt"
|
||||
"github.com/iancoleman/strcase"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
@ -406,7 +405,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.47.3")
|
||||
fmt.Println("curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.51.2")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
@ -547,6 +546,20 @@ func (Release) Darwin() error {
|
|||
return runXgo("darwin-10.15/*")
|
||||
}
|
||||
|
||||
func (Release) Xgo(target string) error {
|
||||
parts := strings.Split(target, "/")
|
||||
if len(parts) < 2 {
|
||||
return fmt.Errorf("invalid target")
|
||||
}
|
||||
|
||||
variant := ""
|
||||
if len(parts) > 2 && parts[2] != "" {
|
||||
variant = "-" + strings.ReplaceAll(parts[2], "v", "")
|
||||
}
|
||||
|
||||
return runXgo(parts[0] + "/" + parts[1] + variant)
|
||||
}
|
||||
|
||||
// Compresses the built binaries in dist/binaries/ to reduce their filesize
|
||||
func (Release) Compress(ctx context.Context) error {
|
||||
// $(foreach file,$(filter-out $(wildcard $(wildcard $(DIST)/binaries/$(EXECUTABLE)-*mips*)),$(wildcard $(DIST)/binaries/$(EXECUTABLE)-*)), upx -9 $(file);)
|
||||
|
@ -559,7 +572,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
|
||||
}
|
||||
|
||||
|
@ -663,7 +678,7 @@ func (Release) Zip() error {
|
|||
|
||||
fmt.Printf("Zipping %s...\n", info.Name())
|
||||
|
||||
c := exec.Command("zip", "-r", RootPath+"/"+DIST+"/zip/"+info.Name(), ".", "-i", "*")
|
||||
c := exec.Command("zip", "-r", RootPath+"/"+DIST+"/zip/"+info.Name()+".zip", ".", "-i", "*")
|
||||
c.Dir = path
|
||||
out, err := c.Output()
|
||||
fmt.Print(string(out))
|
||||
|
@ -688,7 +703,7 @@ func (Release) Packages() error {
|
|||
binpath := "nfpm"
|
||||
err = exec.Command(binpath).Run()
|
||||
if err != nil && strings.Contains(err.Error(), "executable file not found") {
|
||||
binpath = "/nfpm"
|
||||
binpath = "/usr/bin/nfpm"
|
||||
err = exec.Command(binpath).Run()
|
||||
}
|
||||
if err != nil && strings.Contains(err.Error(), "executable file not found") {
|
||||
|
@ -699,14 +714,14 @@ func (Release) Packages() error {
|
|||
|
||||
// Because nfpm does not support templating, we replace the values in the config file and restore it after running
|
||||
nfpmConfigPath := RootPath + "/nfpm.yaml"
|
||||
nfpmconfig, err := ioutil.ReadFile(nfpmConfigPath)
|
||||
nfpmconfig, err := os.ReadFile(nfpmConfigPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fixedConfig := strings.ReplaceAll(string(nfpmconfig), "<version>", VersionNumber)
|
||||
fixedConfig = strings.ReplaceAll(fixedConfig, "<binlocation>", BinLocation)
|
||||
if err := ioutil.WriteFile(nfpmConfigPath, []byte(fixedConfig), 0); err != nil {
|
||||
if err := os.WriteFile(nfpmConfigPath, []byte(fixedConfig), 0); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -719,7 +734,7 @@ func (Release) Packages() error {
|
|||
runAndStreamOutput(binpath, "pkg", "--packager", "rpm", "--target", releasePath)
|
||||
runAndStreamOutput(binpath, "pkg", "--packager", "apk", "--target", releasePath)
|
||||
|
||||
return ioutil.WriteFile(nfpmConfigPath, nfpmconfig, 0)
|
||||
return os.WriteFile(nfpmConfigPath, nfpmconfig, 0)
|
||||
}
|
||||
|
||||
type Dev mg.Namespace
|
||||
|
@ -883,7 +898,7 @@ func (s *` + name + `) Handle(msg *message.Message) (err error) {
|
|||
if _, err := f.Seek(idx, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
remainder, err := ioutil.ReadAll(f)
|
||||
remainder, err := io.ReadAll(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -1072,7 +1087,7 @@ const (
|
|||
// Generates the config docs from a commented config.yml.sample file in the repo root.
|
||||
func GenerateDocs() error {
|
||||
|
||||
config, err := ioutil.ReadFile("config.yml.sample")
|
||||
config, err := os.ReadFile("config.yml.sample")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -1122,7 +1137,7 @@ func GenerateDocs() error {
|
|||
|
||||
// We write the full file to prevent old content leftovers at the end
|
||||
// I know, there are probably better ways to do this.
|
||||
if err := ioutil.WriteFile(configDocPath, []byte(fullConfig), 0); err != nil {
|
||||
if err := os.WriteFile(configDocPath, []byte(fullConfig), 0); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
@ -22,7 +22,8 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"code.vikunja.io/api/pkg/utils"
|
||||
)
|
||||
|
@ -57,11 +58,13 @@ type Todo struct {
|
|||
Priority int64 // 0-9, 1 is highest
|
||||
RelatedToUID string
|
||||
Color string
|
||||
|
||||
Categories []string
|
||||
Start time.Time
|
||||
End time.Time
|
||||
DueDate time.Time
|
||||
Duration time.Duration
|
||||
RepeatAfter int64
|
||||
RepeatMode models.TaskRepeatMode
|
||||
|
||||
Created time.Time
|
||||
Updated time.Time // last-mod
|
||||
|
@ -181,6 +184,10 @@ SUMMARY:` + t.Summary + getCaldavColor(t.Color)
|
|||
if t.Start.Unix() > 0 {
|
||||
caldavtodos += `
|
||||
DTSTART:` + makeCalDavTimeFromTimeStamp(t.Start)
|
||||
if t.Duration != 0 && t.DueDate.Unix() == 0 {
|
||||
caldavtodos += `
|
||||
DURATION:PT` + formatDuration(t.Duration)
|
||||
}
|
||||
}
|
||||
if t.End.Unix() > 0 {
|
||||
caldavtodos += `
|
||||
|
@ -217,16 +224,26 @@ DUE:` + makeCalDavTimeFromTimeStamp(t.DueDate)
|
|||
CREATED:` + makeCalDavTimeFromTimeStamp(t.Created)
|
||||
}
|
||||
|
||||
if t.Duration != 0 {
|
||||
caldavtodos += `
|
||||
DURATION:PT` + formatDuration(t.Duration)
|
||||
}
|
||||
|
||||
if t.Priority != 0 {
|
||||
caldavtodos += `
|
||||
PRIORITY:` + strconv.Itoa(mapPriorityToCaldav(t.Priority))
|
||||
}
|
||||
|
||||
if t.RepeatAfter > 0 || t.RepeatMode == models.TaskRepeatModeMonth {
|
||||
if t.RepeatMode == models.TaskRepeatModeMonth {
|
||||
caldavtodos += `
|
||||
RRULE:FREQ=MONTHLY;BYMONTHDAY=` + t.DueDate.Format("02") // Day of the month
|
||||
} else {
|
||||
caldavtodos += `
|
||||
RRULE:FREQ=SECONDLY;INTERVAL=` + strconv.FormatInt(t.RepeatAfter, 10)
|
||||
}
|
||||
}
|
||||
|
||||
if len(t.Categories) > 0 {
|
||||
caldavtodos += `
|
||||
CATEGORIES:` + strings.Join(t.Categories, ",")
|
||||
}
|
||||
|
||||
caldavtodos += `
|
||||
LAST-MODIFIED:` + makeCalDavTimeFromTimeStamp(t.Updated)
|
||||
|
||||
|
@ -241,7 +258,7 @@ END:VCALENDAR` // Need a line break
|
|||
}
|
||||
|
||||
func makeCalDavTimeFromTimeStamp(ts time.Time) (caldavtime string) {
|
||||
return ts.In(config.GetTimeZone()).Format(DateFormat)
|
||||
return ts.In(time.UTC).Format(DateFormat) + "Z"
|
||||
}
|
||||
|
||||
func calcAlarmDateFromReminder(eventStart, reminder time.Time) (alarmTime string) {
|
||||
|
|
|
@ -20,6 +20,8 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
@ -84,25 +86,25 @@ X-APPLE-CALENDAR-COLOR:#affffeFF
|
|||
X-OUTLOOK-COLOR:#affffeFF
|
||||
X-FUNAMBOL-COLOR:#affffeFF
|
||||
DESCRIPTION:Lorem Ipsum
|
||||
DTSTAMP:20181201T011204
|
||||
DTSTART:20181201T011204
|
||||
DTEND:20181201T013024
|
||||
DTSTAMP:20181201T011204Z
|
||||
DTSTART:20181201T011204Z
|
||||
DTEND:20181201T013024Z
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:randommduidd
|
||||
SUMMARY:Event #2
|
||||
DESCRIPTION:
|
||||
DTSTAMP:20181202T045844
|
||||
DTSTART:20181202T045844
|
||||
DTEND:20181202T081844
|
||||
DTSTAMP:20181202T045844Z
|
||||
DTSTART:20181202T045844Z
|
||||
DTEND:20181202T081844Z
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:20181202T0600242aaef4a81d770c1e775e26bc5abebc87f1d3d7bffaa83
|
||||
SUMMARY:Event #3 with empty uid
|
||||
DESCRIPTION:
|
||||
DTSTAMP:20181202T050024
|
||||
DTSTART:20181202T050024
|
||||
DTEND:20181202T050320
|
||||
DTSTAMP:20181202T050024Z
|
||||
DTSTART:20181202T050024Z
|
||||
DTEND:20181202T050320Z
|
||||
END:VEVENT
|
||||
END:VCALENDAR`,
|
||||
},
|
||||
|
@ -169,9 +171,9 @@ BEGIN:VEVENT
|
|||
UID:randommduid
|
||||
SUMMARY:Event #1
|
||||
DESCRIPTION:Lorem Ipsum
|
||||
DTSTAMP:20181201T011204
|
||||
DTSTART:20181201T011204
|
||||
DTEND:20181201T013024
|
||||
DTSTAMP:20181201T011204Z
|
||||
DTSTART:20181201T011204Z
|
||||
DTEND:20181201T013024Z
|
||||
BEGIN:VALARM
|
||||
TRIGGER:-PT3M20S
|
||||
ACTION:DISPLAY
|
||||
|
@ -192,9 +194,9 @@ BEGIN:VEVENT
|
|||
UID:randommduidd
|
||||
SUMMARY:Event #2
|
||||
DESCRIPTION:
|
||||
DTSTAMP:20181202T045844
|
||||
DTSTART:20181202T045844
|
||||
DTEND:20181202T081844
|
||||
DTSTAMP:20181202T045844Z
|
||||
DTSTART:20181202T045844Z
|
||||
DTEND:20181202T081844Z
|
||||
BEGIN:VALARM
|
||||
TRIGGER:-PT27H50M0S
|
||||
ACTION:DISPLAY
|
||||
|
@ -212,12 +214,12 @@ DESCRIPTION:Event #2
|
|||
END:VALARM
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:20181202T0500242aaef4a81d770c1e775e26bc5abebc87f1d3d7bffaa83
|
||||
UID:20181202T050024Z2aaef4a81d770c1e775e26bc5abebc87f1d3d7bffaa83
|
||||
SUMMARY:Event #3 with empty uid
|
||||
DESCRIPTION:
|
||||
DTSTAMP:20181202T050024
|
||||
DTSTART:20181202T050024
|
||||
DTEND:20181202T050320
|
||||
DTSTAMP:20181202T050024Z
|
||||
DTSTART:20181202T050024Z
|
||||
DTEND:20181202T050320Z
|
||||
BEGIN:VALARM
|
||||
TRIGGER:-PT27H51M40S
|
||||
ACTION:DISPLAY
|
||||
|
@ -240,12 +242,12 @@ DESCRIPTION:Event #3 with empty uid
|
|||
END:VALARM
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:20181202T050024ae7548ce9556df85038abe90dc674d4741a61ce74d1cf
|
||||
UID:20181202T050024Zae7548ce9556df85038abe90dc674d4741a61ce74d1cf
|
||||
SUMMARY:Event #4 without any
|
||||
DESCRIPTION:
|
||||
DTSTAMP:20181202T050024
|
||||
DTSTART:20181202T050024
|
||||
DTEND:20181202T050320
|
||||
DTSTAMP:20181202T050024Z
|
||||
DTSTART:20181202T050024Z
|
||||
DTEND:20181202T050320Z
|
||||
END:VEVENT
|
||||
END:VCALENDAR`,
|
||||
},
|
||||
|
@ -278,9 +280,9 @@ BEGIN:VEVENT
|
|||
UID:randommduid
|
||||
SUMMARY:Event #1
|
||||
DESCRIPTION:Lorem Ipsum\nDolor sit amet
|
||||
DTSTAMP:20181201T011204
|
||||
DTSTART:20181201T011204
|
||||
DTEND:20181201T013024
|
||||
DTSTAMP:20181201T011204Z
|
||||
DTSTART:20181201T011204Z
|
||||
DTEND:20181201T013024Z
|
||||
END:VEVENT
|
||||
END:VCALENDAR`,
|
||||
},
|
||||
|
@ -333,13 +335,13 @@ X-OUTLOOK-COLOR:#ffffffFF
|
|||
X-FUNAMBOL-COLOR:#ffffffFF
|
||||
BEGIN:VTODO
|
||||
UID:randommduid
|
||||
DTSTAMP:20181201T011204
|
||||
DTSTAMP:20181201T011204Z
|
||||
SUMMARY:Todo #1
|
||||
X-APPLE-CALENDAR-COLOR:#affffeFF
|
||||
X-OUTLOOK-COLOR:#affffeFF
|
||||
X-FUNAMBOL-COLOR:#affffeFF
|
||||
DESCRIPTION:Lorem Ipsum\nDolor sit amet
|
||||
LAST-MODIFIED:00010101T000000
|
||||
LAST-MODIFIED:00010101T000000Z
|
||||
END:VTODO
|
||||
END:VCALENDAR`,
|
||||
},
|
||||
|
@ -368,12 +370,12 @@ X-WR-CALNAME:test
|
|||
PRODID:-//RandomProdID which is not random//EN
|
||||
BEGIN:VTODO
|
||||
UID:randommduid
|
||||
DTSTAMP:20181201T011204
|
||||
DTSTAMP:20181201T011204Z
|
||||
SUMMARY:Todo #1
|
||||
DESCRIPTION:Lorem Ipsum
|
||||
COMPLETED:20181201T013024
|
||||
COMPLETED:20181201T013024Z
|
||||
STATUS:COMPLETED
|
||||
LAST-MODIFIED:00010101T000000
|
||||
LAST-MODIFIED:00010101T000000Z
|
||||
END:VTODO
|
||||
END:VCALENDAR`,
|
||||
},
|
||||
|
@ -402,11 +404,121 @@ X-WR-CALNAME:test
|
|||
PRODID:-//RandomProdID which is not random//EN
|
||||
BEGIN:VTODO
|
||||
UID:randommduid
|
||||
DTSTAMP:20181201T011204
|
||||
DTSTAMP:20181201T011204Z
|
||||
SUMMARY:Todo #1
|
||||
DESCRIPTION:Lorem Ipsum
|
||||
PRIORITY:9
|
||||
LAST-MODIFIED:00010101T000000
|
||||
LAST-MODIFIED:00010101T000000Z
|
||||
END:VTODO
|
||||
END:VCALENDAR`,
|
||||
},
|
||||
{
|
||||
name: "with repeating monthly",
|
||||
args: args{
|
||||
config: &Config{
|
||||
Name: "test",
|
||||
ProdID: "RandomProdID which is not random",
|
||||
},
|
||||
todos: []*Todo{
|
||||
{
|
||||
Summary: "Todo #1",
|
||||
Description: "Lorem Ipsum",
|
||||
UID: "randommduid",
|
||||
Timestamp: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||
RepeatMode: models.TaskRepeatModeMonth,
|
||||
DueDate: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantCaldavtasks: `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
X-PUBLISHED-TTL:PT4H
|
||||
X-WR-CALNAME:test
|
||||
PRODID:-//RandomProdID which is not random//EN
|
||||
BEGIN:VTODO
|
||||
UID:randommduid
|
||||
DTSTAMP:20181201T011204Z
|
||||
SUMMARY:Todo #1
|
||||
DESCRIPTION:Lorem Ipsum
|
||||
DUE:20181201T011204Z
|
||||
RRULE:FREQ=MONTHLY;BYMONTHDAY=01
|
||||
LAST-MODIFIED:00010101T000000Z
|
||||
END:VTODO
|
||||
END:VCALENDAR`,
|
||||
},
|
||||
{
|
||||
name: "with repeat mode default",
|
||||
args: args{
|
||||
config: &Config{
|
||||
Name: "test",
|
||||
ProdID: "RandomProdID which is not random",
|
||||
},
|
||||
todos: []*Todo{
|
||||
{
|
||||
Summary: "Todo #1",
|
||||
Description: "Lorem Ipsum",
|
||||
UID: "randommduid",
|
||||
Timestamp: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||
RepeatMode: models.TaskRepeatModeDefault,
|
||||
DueDate: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||
RepeatAfter: 435,
|
||||
},
|
||||
},
|
||||
},
|
||||
wantCaldavtasks: `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
X-PUBLISHED-TTL:PT4H
|
||||
X-WR-CALNAME:test
|
||||
PRODID:-//RandomProdID which is not random//EN
|
||||
BEGIN:VTODO
|
||||
UID:randommduid
|
||||
DTSTAMP:20181201T011204Z
|
||||
SUMMARY:Todo #1
|
||||
DESCRIPTION:Lorem Ipsum
|
||||
DUE:20181201T011204Z
|
||||
RRULE:FREQ=SECONDLY;INTERVAL=435
|
||||
LAST-MODIFIED:00010101T000000Z
|
||||
END:VTODO
|
||||
END:VCALENDAR`,
|
||||
},
|
||||
{
|
||||
name: "with categories",
|
||||
args: args{
|
||||
config: &Config{
|
||||
Name: "test",
|
||||
ProdID: "RandomProdID which is not random",
|
||||
Color: "ffffff",
|
||||
},
|
||||
todos: []*Todo{
|
||||
{
|
||||
Summary: "Todo #1",
|
||||
UID: "randommduid",
|
||||
Timestamp: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||
Color: "affffe",
|
||||
Categories: []string{"label1", "label2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantCaldavtasks: `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
X-PUBLISHED-TTL:PT4H
|
||||
X-WR-CALNAME:test
|
||||
PRODID:-//RandomProdID which is not random//EN
|
||||
X-APPLE-CALENDAR-COLOR:#ffffffFF
|
||||
X-OUTLOOK-COLOR:#ffffffFF
|
||||
X-FUNAMBOL-COLOR:#ffffffFF
|
||||
BEGIN:VTODO
|
||||
UID:randommduid
|
||||
DTSTAMP:20181201T011204Z
|
||||
SUMMARY:Todo #1
|
||||
X-APPLE-CALENDAR-COLOR:#affffeFF
|
||||
X-OUTLOOK-COLOR:#affffeFF
|
||||
X-FUNAMBOL-COLOR:#affffeFF
|
||||
CATEGORIES:label1,label2
|
||||
LAST-MODIFIED:00010101T000000Z
|
||||
END:VTODO
|
||||
END:VCALENDAR`,
|
||||
},
|
||||
|
|
|
@ -27,13 +27,17 @@ import (
|
|||
ics "github.com/arran4/golang-ical"
|
||||
)
|
||||
|
||||
func GetCaldavTodosForTasks(list *models.ListWithTasksAndBuckets, listTasks []*models.TaskWithComments) string {
|
||||
func GetCaldavTodosForTasks(project *models.ProjectWithTasksAndBuckets, projectTasks []*models.TaskWithComments) string {
|
||||
|
||||
// Make caldav todos from Vikunja todos
|
||||
var caldavtodos []*Todo
|
||||
for _, t := range listTasks {
|
||||
for _, t := range projectTasks {
|
||||
|
||||
duration := t.EndDate.Sub(t.StartDate)
|
||||
var categories []string
|
||||
for _, label := range t.Labels {
|
||||
categories = append(categories, label.Title)
|
||||
}
|
||||
|
||||
caldavtodos = append(caldavtodos, &Todo{
|
||||
Timestamp: t.Updated,
|
||||
|
@ -42,6 +46,7 @@ func GetCaldavTodosForTasks(list *models.ListWithTasksAndBuckets, listTasks []*m
|
|||
Description: t.Description,
|
||||
Completed: t.DoneAt,
|
||||
// Organizer: &t.CreatedBy, // Disabled until we figure out how this works
|
||||
Categories: categories,
|
||||
Priority: t.Priority,
|
||||
Start: t.StartDate,
|
||||
End: t.EndDate,
|
||||
|
@ -49,11 +54,13 @@ func GetCaldavTodosForTasks(list *models.ListWithTasksAndBuckets, listTasks []*m
|
|||
Updated: t.Updated,
|
||||
DueDate: t.DueDate,
|
||||
Duration: duration,
|
||||
RepeatAfter: t.RepeatAfter,
|
||||
RepeatMode: t.RepeatMode,
|
||||
})
|
||||
}
|
||||
|
||||
caldavConfig := &Config{
|
||||
Name: list.Title,
|
||||
Name: project.Title,
|
||||
ProdID: "Vikunja Todo App",
|
||||
}
|
||||
|
||||
|
@ -89,11 +96,23 @@ func ParseTaskFromVTODO(content string) (vTask *models.Task, err error) {
|
|||
description := strings.ReplaceAll(task["DESCRIPTION"], "\\,", ",")
|
||||
description = strings.ReplaceAll(description, "\\n", "\n")
|
||||
|
||||
var labels []*models.Label
|
||||
if val, ok := task["CATEGORIES"]; ok {
|
||||
categories := strings.Split(val, ",")
|
||||
labels = make([]*models.Label, 0, len(categories))
|
||||
for _, category := range categories {
|
||||
labels = append(labels, &models.Label{
|
||||
Title: category,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
vTask = &models.Task{
|
||||
UID: task["UID"],
|
||||
Title: task["SUMMARY"],
|
||||
Description: description,
|
||||
Priority: priority,
|
||||
Labels: labels,
|
||||
DueDate: caldavTimeToTimestamp(task["DUE"]),
|
||||
Updated: caldavTimeToTimestamp(task["DTSTAMP"]),
|
||||
StartDate: caldavTimeToTimestamp(task["DTSTART"]),
|
||||
|
|
|
@ -85,6 +85,39 @@ END:VCALENDAR`,
|
|||
Updated: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "With categories",
|
||||
args: args{content: `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
X-PUBLISHED-TTL:PT4H
|
||||
X-WR-CALNAME:test
|
||||
PRODID:-//RandomProdID which is not random//EN
|
||||
BEGIN:VTODO
|
||||
UID:randomuid
|
||||
DTSTAMP:20181201T011204
|
||||
SUMMARY:Todo #1
|
||||
DESCRIPTION:Lorem Ipsum
|
||||
CATEGORIES:cat1,cat2
|
||||
LAST-MODIFIED:00010101T000000
|
||||
END:VTODO
|
||||
END:VCALENDAR`,
|
||||
},
|
||||
wantVTask: &models.Task{
|
||||
Title: "Todo #1",
|
||||
UID: "randomuid",
|
||||
Description: "Lorem Ipsum",
|
||||
Labels: []*models.Label{
|
||||
{
|
||||
Title: "cat1",
|
||||
},
|
||||
{
|
||||
Title: "cat2",
|
||||
},
|
||||
},
|
||||
Updated: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
@ -99,3 +132,84 @@ END:VCALENDAR`,
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCaldavTodosForTasks(t *testing.T) {
|
||||
type args struct {
|
||||
list *models.ProjectWithTasksAndBuckets
|
||||
tasks []*models.TaskWithComments
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantCaldav string
|
||||
}{
|
||||
{
|
||||
name: "Format single Task as Caldav",
|
||||
args: args{
|
||||
list: &models.ProjectWithTasksAndBuckets{
|
||||
Project: models.Project{
|
||||
Title: "List title",
|
||||
},
|
||||
},
|
||||
tasks: []*models.TaskWithComments{
|
||||
{
|
||||
Task: models.Task{
|
||||
Title: "Task 1",
|
||||
UID: "randomuid",
|
||||
Description: "Description",
|
||||
Priority: 3,
|
||||
Created: time.Unix(1543626721, 0).In(config.GetTimeZone()),
|
||||
DueDate: time.Unix(1543626722, 0).In(config.GetTimeZone()),
|
||||
StartDate: time.Unix(1543626723, 0).In(config.GetTimeZone()),
|
||||
EndDate: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||
Updated: time.Unix(1543626725, 0).In(config.GetTimeZone()),
|
||||
DoneAt: time.Unix(1543626726, 0).In(config.GetTimeZone()),
|
||||
RepeatAfter: 86400,
|
||||
Labels: []*models.Label{
|
||||
{
|
||||
ID: 1,
|
||||
Title: "label1",
|
||||
},
|
||||
{
|
||||
ID: 2,
|
||||
Title: "label2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantCaldav: `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
X-PUBLISHED-TTL:PT4H
|
||||
X-WR-CALNAME:List title
|
||||
PRODID:-//Vikunja Todo App//EN
|
||||
BEGIN:VTODO
|
||||
UID:randomuid
|
||||
DTSTAMP:20181201T011205Z
|
||||
SUMMARY:Task 1
|
||||
DTSTART:20181201T011203Z
|
||||
DTEND:20181201T011204Z
|
||||
DESCRIPTION:Description
|
||||
COMPLETED:20181201T011206Z
|
||||
STATUS:COMPLETED
|
||||
DUE:20181201T011202Z
|
||||
CREATED:20181201T011201Z
|
||||
PRIORITY:3
|
||||
RRULE:FREQ=SECONDLY;INTERVAL=86400
|
||||
CATEGORIES:label1,label2
|
||||
LAST-MODIFIED:20181201T011205Z
|
||||
END:VTODO
|
||||
END:VCALENDAR`,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := GetCaldavTodosForTasks(tt.args.list, tt.args.tasks)
|
||||
if diff, equal := messagediff.PrettyDiff(got, tt.wantCaldav); !equal {
|
||||
t.Errorf("GetCaldavTodosForTasks() gotVTask = %v, want %v, diff = %s", got, tt.wantCaldav, diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -73,7 +73,7 @@ func init() {
|
|||
// User deletion flags
|
||||
userDeleteCmd.Flags().BoolVarP(&userFlagDeleteNow, "now", "n", false, "If provided, deletes the user immediately instead of sending them an email first.")
|
||||
|
||||
userCmd.AddCommand(userListCmd, userCreateCmd, userUpdateCmd, userResetPasswordCmd, userChangeEnabledCmd, userDeleteCmd)
|
||||
userCmd.AddCommand(userProjectCmd, userCreateCmd, userUpdateCmd, userResetPasswordCmd, userChangeEnabledCmd, userDeleteCmd)
|
||||
rootCmd.AddCommand(userCmd)
|
||||
}
|
||||
|
||||
|
@ -117,9 +117,9 @@ var userCmd = &cobra.Command{
|
|||
Short: "Manage users locally through the cli.",
|
||||
}
|
||||
|
||||
var userListCmd = &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "Shows a list of all users.",
|
||||
var userProjectCmd = &cobra.Command{
|
||||
Use: "project",
|
||||
Short: "Shows a project of all users.",
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
initialize.FullInit()
|
||||
},
|
||||
|
@ -127,7 +127,7 @@ var userListCmd = &cobra.Command{
|
|||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
users, err := user.ListAllUsers(s)
|
||||
users, err := user.ProjectAllUsers(s)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
log.Fatalf("Error getting users: %s", err)
|
||||
|
@ -225,7 +225,7 @@ var userUpdateCmd = &cobra.Command{
|
|||
u.AvatarProvider = userFlagAvatar
|
||||
}
|
||||
|
||||
_, err := user.UpdateUser(s, u)
|
||||
_, err := user.UpdateUser(s, u, false)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
log.Fatalf("Error updating the user: %s", err)
|
||||
|
@ -299,7 +299,7 @@ var userChangeEnabledCmd = &cobra.Command{
|
|||
u.Status = user.StatusActive
|
||||
}
|
||||
}
|
||||
_, err := user.UpdateUser(s, u)
|
||||
_, err := user.UpdateUser(s, u, false)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
log.Fatalf("Could not enable the user")
|
||||
|
|
|
@ -128,10 +128,6 @@ const (
|
|||
FilesBasePath Key = `files.basepath`
|
||||
FilesMaxSize Key = `files.maxsize`
|
||||
|
||||
MigrationWunderlistEnable Key = `migration.wunderlist.enable`
|
||||
MigrationWunderlistClientID Key = `migration.wunderlist.clientid`
|
||||
MigrationWunderlistClientSecret Key = `migration.wunderlist.clientsecret`
|
||||
MigrationWunderlistRedirectURL Key = `migration.wunderlist.redirecturl`
|
||||
MigrationTodoistEnable Key = `migration.todoist.enable`
|
||||
MigrationTodoistClientID Key = `migration.todoist.clientid`
|
||||
MigrationTodoistClientSecret Key = `migration.todoist.clientsecret`
|
||||
|
@ -161,6 +157,18 @@ const (
|
|||
MetricsEnabled Key = `metrics.enabled`
|
||||
MetricsUsername Key = `metrics.username`
|
||||
MetricsPassword Key = `metrics.password`
|
||||
|
||||
DefaultSettingsAvatarProvider Key = `defaultsettings.avatar_provider`
|
||||
DefaultSettingsAvatarFileID Key = `defaultsettings.avatar_file_id`
|
||||
DefaultSettingsEmailRemindersEnabled Key = `defaultsettings.email_reminders_enabled`
|
||||
DefaultSettingsDiscoverableByName Key = `defaultsettings.discoverable_by_name`
|
||||
DefaultSettingsDiscoverableByEmail Key = `defaultsettings.discoverable_by_email`
|
||||
DefaultSettingsOverdueTaskRemindersEnabled Key = `defaultsettings.overdue_tasks_reminders_enabled`
|
||||
DefaultSettingsDefaultProjectID Key = `defaultsettings.default_project_id`
|
||||
DefaultSettingsWeekStart Key = `defaultsettings.week_start`
|
||||
DefaultSettingsLanguage Key = `defaultsettings.language`
|
||||
DefaultSettingsTimezone Key = `defaultsettings.timezone`
|
||||
DefaultSettingsOverdueTaskRemindersTime Key = `defaultsettings.overdue_tasks_reminders_time`
|
||||
)
|
||||
|
||||
// GetString returns a string config value
|
||||
|
@ -341,7 +349,7 @@ func InitDefaultConfig() {
|
|||
LogHTTP.setDefault("stdout")
|
||||
LogEcho.setDefault("off")
|
||||
LogPath.setDefault(ServiceRootpath.GetString() + "/logs")
|
||||
LogEvents.setDefault("stdout")
|
||||
LogEvents.setDefault("off")
|
||||
LogEventsLevel.setDefault("INFO")
|
||||
// Rate Limit
|
||||
RateLimitEnabled.setDefault(false)
|
||||
|
@ -357,13 +365,12 @@ func InitDefaultConfig() {
|
|||
CorsOrigins.setDefault([]string{"*"})
|
||||
CorsMaxAge.setDefault(0)
|
||||
// Migration
|
||||
MigrationWunderlistEnable.setDefault(false)
|
||||
MigrationTodoistEnable.setDefault(false)
|
||||
MigrationTrelloEnable.setDefault(false)
|
||||
MigrationMicrosoftTodoEnable.setDefault(false)
|
||||
// Avatar
|
||||
AvatarGravaterExpiration.setDefault(3600)
|
||||
// List Backgrounds
|
||||
// Project Backgrounds
|
||||
BackgroundsEnabled.setDefault(true)
|
||||
BackgroundsUploadEnabled.setDefault(true)
|
||||
BackgroundsUnsplashEnabled.setDefault(false)
|
||||
|
@ -371,6 +378,10 @@ func InitDefaultConfig() {
|
|||
KeyvalueType.setDefault("memory")
|
||||
// Metrics
|
||||
MetricsEnabled.setDefault(false)
|
||||
// Settings
|
||||
DefaultSettingsAvatarProvider.setDefault("initials")
|
||||
DefaultSettingsOverdueTaskRemindersEnabled.setDefault(true)
|
||||
DefaultSettingsOverdueTaskRemindersTime.setDefault("9:00")
|
||||
}
|
||||
|
||||
// InitConfig initializes the config, sets defaults etc.
|
||||
|
@ -438,6 +449,10 @@ func InitConfig() {
|
|||
MigrationMicrosoftTodoRedirectURL.Set(ServiceFrontendurl.GetString() + "migrate/microsoft-todo")
|
||||
}
|
||||
|
||||
if DefaultSettingsTimezone.GetString() == "" {
|
||||
DefaultSettingsTimezone.Set(ServiceTimeZone.GetString())
|
||||
}
|
||||
|
||||
if ServiceEnableMetrics.GetBool() {
|
||||
log.Println("WARNING: service.enablemetrics is deprecated and will be removed in a future release. Please use metrics.enable.")
|
||||
MetricsEnabled.Set(true)
|
||||
|
|
28
pkg/db/db.go
28
pkg/db/db.go
|
@ -19,6 +19,7 @@ package db
|
|||
import (
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
@ -27,9 +28,9 @@ import (
|
|||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
xrc "gitea.com/xorm/xorm-redis-cache"
|
||||
"xorm.io/core"
|
||||
"xorm.io/xorm"
|
||||
"xorm.io/xorm/caches"
|
||||
"xorm.io/xorm/names"
|
||||
"xorm.io/xorm/schemas"
|
||||
|
||||
_ "github.com/go-sql-driver/mysql" // Because.
|
||||
|
@ -80,7 +81,7 @@ func CreateDBEngine() (engine *xorm.Engine, err error) {
|
|||
log.Fatalf("Error parsing time zone: %s", err)
|
||||
}
|
||||
engine.SetTZDatabase(loc)
|
||||
engine.SetMapper(core.GonicMapper{})
|
||||
engine.SetMapper(names.GonicMapper{})
|
||||
logger := log.NewXormLogger("")
|
||||
engine.SetLogger(logger)
|
||||
|
||||
|
@ -147,11 +148,26 @@ func parsePostgreSQLHostPort(info string) (string, string) {
|
|||
return host, port
|
||||
}
|
||||
|
||||
// Copied and adopted from https://github.com/go-gitea/gitea/blob/f337c32e868381c6d2d948221aca0c59f8420c13/modules/setting/database.go#L176-L186
|
||||
func getPostgreSQLConnectionString(dbHost, dbUser, dbPasswd, dbName, dbSslMode, dbSslCert, dbSslKey, dbSslRootCert string) (connStr string) {
|
||||
dbParam := "?"
|
||||
if strings.Contains(dbName, dbParam) {
|
||||
dbParam = "&"
|
||||
}
|
||||
host, port := parsePostgreSQLHostPort(dbHost)
|
||||
if host[0] == '/' { // looks like a unix socket
|
||||
connStr = fmt.Sprintf("postgres://%s:%s@:%s/%s%ssslmode=%s&sslcert=%s&sslkey=%s&sslrootcert=%s&host=%s",
|
||||
url.PathEscape(dbUser), url.PathEscape(dbPasswd), port, dbName, dbParam, dbSslMode, dbSslCert, dbSslKey, dbSslRootCert, host)
|
||||
} else {
|
||||
connStr = fmt.Sprintf("postgres://%s:%s@%s:%s/%s%ssslmode=%s&sslcert=%s&sslkey=%s&sslrootcert=%s",
|
||||
url.PathEscape(dbUser), url.PathEscape(dbPasswd), host, port, dbName, dbParam, dbSslMode, dbSslCert, dbSslKey, dbSslRootCert)
|
||||
}
|
||||
return connStr
|
||||
}
|
||||
|
||||
func initPostgresEngine() (engine *xorm.Engine, err error) {
|
||||
host, port := parsePostgreSQLHostPort(config.DatabaseHost.GetString())
|
||||
connStr := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=%s sslcert=%s sslkey=%s sslrootcert=%s",
|
||||
host,
|
||||
port,
|
||||
connStr := getPostgreSQLConnectionString(
|
||||
config.DatabaseHost.GetString(),
|
||||
config.DatabaseUser.GetString(),
|
||||
config.DatabasePassword.GetString(),
|
||||
config.DatabaseDatabase.GetString(),
|
||||
|
|
|
@ -18,6 +18,7 @@ package db
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
|
||||
|
@ -53,7 +54,35 @@ func Restore(table string, contents []map[string]interface{}) (err error) {
|
|||
return err
|
||||
}
|
||||
|
||||
meta, err := x.DBMetas()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var metaForCurrentTable *schemas.Table
|
||||
for _, m := range meta {
|
||||
if m.Name == table {
|
||||
metaForCurrentTable = m
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if metaForCurrentTable == nil {
|
||||
log.Fatalf("Could not find table definition for table %s", table)
|
||||
}
|
||||
|
||||
for _, content := range contents {
|
||||
for colName, value := range content {
|
||||
// Date fields might get restored as 0001-01-01 from null dates. This can have unintended side-effects like
|
||||
// users being scheduled for deletion after a restore.
|
||||
// To avoid this, we set these dates to nil so that they'll end up as null in the db.
|
||||
col := metaForCurrentTable.GetColumn(colName)
|
||||
strVal, is := value.(string)
|
||||
if is && col.SQLType.IsTime() && (strVal == "" || strings.HasPrefix(strVal, "0001-")) {
|
||||
content[colName] = nil
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := x.Table(table).Insert(content); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -1,30 +1,30 @@
|
|||
- id: 1
|
||||
title: testbucket1
|
||||
list_id: 1
|
||||
project_id: 1
|
||||
created_by_id: 1
|
||||
limit: 9999999 # This bucket has a limit we will never exceed in the tests to make sure the logic allows for buckets with limits
|
||||
position: 2
|
||||
position: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 2
|
||||
title: testbucket2
|
||||
list_id: 1
|
||||
project_id: 1
|
||||
created_by_id: 1
|
||||
limit: 3
|
||||
position: 1
|
||||
position: 2
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 3
|
||||
title: testbucket3
|
||||
list_id: 1
|
||||
project_id: 1
|
||||
created_by_id: 1
|
||||
is_done_bucket: 1
|
||||
position: 3
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 4
|
||||
title: testbucket4 - other list
|
||||
list_id: 2
|
||||
title: testbucket4 - other project
|
||||
project_id: 2
|
||||
created_by_id: 1
|
||||
is_done_bucket: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
|
@ -32,189 +32,195 @@
|
|||
# The following are not or only partly owned by user 1
|
||||
- id: 5
|
||||
title: testbucket5
|
||||
list_id: 20
|
||||
project_id: 20
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 6
|
||||
title: testbucket6
|
||||
list_id: 6
|
||||
project_id: 6
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 7
|
||||
title: testbucket7
|
||||
list_id: 7
|
||||
project_id: 7
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 8
|
||||
title: testbucket8
|
||||
list_id: 8
|
||||
project_id: 8
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 9
|
||||
title: testbucket9
|
||||
list_id: 9
|
||||
project_id: 9
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 10
|
||||
title: testbucket10
|
||||
list_id: 10
|
||||
project_id: 10
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 11
|
||||
title: testbucket11
|
||||
list_id: 11
|
||||
project_id: 11
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 12
|
||||
title: testbucket13
|
||||
list_id: 12
|
||||
project_id: 12
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 13
|
||||
title: testbucket13
|
||||
list_id: 13
|
||||
project_id: 13
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 14
|
||||
title: testbucket14
|
||||
list_id: 14
|
||||
project_id: 14
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 15
|
||||
title: testbucket15
|
||||
list_id: 15
|
||||
project_id: 15
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 16
|
||||
title: testbucket16
|
||||
list_id: 16
|
||||
project_id: 16
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 17
|
||||
title: testbucket17
|
||||
list_id: 17
|
||||
project_id: 17
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 18
|
||||
title: testbucket18
|
||||
list_id: 5
|
||||
project_id: 5
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 19
|
||||
title: testbucket19
|
||||
list_id: 21
|
||||
project_id: 21
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 20
|
||||
title: testbucket20
|
||||
list_id: 22
|
||||
project_id: 22
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 21
|
||||
title: testbucket21
|
||||
list_id: 3
|
||||
project_id: 3
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
# Duplicate buckets to make deletion of one of them possible
|
||||
- id: 22
|
||||
title: testbucket22
|
||||
list_id: 6
|
||||
project_id: 6
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 23
|
||||
title: testbucket23
|
||||
list_id: 7
|
||||
project_id: 7
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 24
|
||||
title: testbucket24
|
||||
list_id: 8
|
||||
project_id: 8
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 25
|
||||
title: testbucket25
|
||||
list_id: 9
|
||||
project_id: 9
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 26
|
||||
title: testbucket26
|
||||
list_id: 10
|
||||
project_id: 10
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 27
|
||||
title: testbucket27
|
||||
list_id: 11
|
||||
project_id: 11
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 28
|
||||
title: testbucket28
|
||||
list_id: 12
|
||||
project_id: 12
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 29
|
||||
title: testbucket29
|
||||
list_id: 13
|
||||
project_id: 13
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 30
|
||||
title: testbucket30
|
||||
list_id: 14
|
||||
project_id: 14
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 31
|
||||
title: testbucket31
|
||||
list_id: 15
|
||||
project_id: 15
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 32
|
||||
title: testbucket32
|
||||
list_id: 16
|
||||
project_id: 16
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 33
|
||||
title: testbucket33
|
||||
list_id: 17
|
||||
project_id: 17
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
# This bucket is the last one in its list
|
||||
# This bucket is the last one in its project
|
||||
- id: 34
|
||||
title: testbucket34
|
||||
list_id: 18
|
||||
project_id: 18
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 35
|
||||
title: testbucket35
|
||||
list_id: 23
|
||||
project_id: 23
|
||||
created_by_id: -2
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 36
|
||||
title: testbucket36
|
||||
project_id: 26
|
||||
created_by_id: 15
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
|
|
|
@ -14,3 +14,7 @@
|
|||
task_id: 36
|
||||
label_id: 4
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 5
|
||||
task_id: 39
|
||||
label_id: 4
|
||||
created: 2018-12-01 15:13:12
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
- id: 1
|
||||
hash: test
|
||||
list_id: 1
|
||||
project_id: 1
|
||||
right: 0
|
||||
sharing_type: 1
|
||||
shared_by_id: 1
|
||||
|
@ -8,7 +8,7 @@
|
|||
updated: 2018-12-02 15:13:12
|
||||
- id: 2
|
||||
hash: test2
|
||||
list_id: 2
|
||||
project_id: 2
|
||||
right: 1
|
||||
sharing_type: 1
|
||||
shared_by_id: 1
|
||||
|
@ -16,7 +16,7 @@
|
|||
updated: 2018-12-02 15:13:12
|
||||
- id: 3
|
||||
hash: test3
|
||||
list_id: 3
|
||||
project_id: 3
|
||||
right: 2
|
||||
sharing_type: 1
|
||||
shared_by_id: 1
|
||||
|
@ -24,7 +24,7 @@
|
|||
updated: 2018-12-02 15:13:12
|
||||
- id: 4
|
||||
hash: testWithPassword
|
||||
list_id: 1
|
||||
project_id: 1
|
||||
right: 0
|
||||
password: '$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.' # 1234
|
||||
sharing_type: 2
|
||||
|
|
|
@ -88,3 +88,9 @@
|
|||
owner_id: 12
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 18
|
||||
title: testnamespace18
|
||||
description: Lorem Ipsum
|
||||
owner_id: 15
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
|
|
|
@ -168,8 +168,8 @@
|
|||
position: 17
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
# This list is owned by user 7, and several other users have access to it via different methods.
|
||||
# It is used to test the listUsers method.
|
||||
# This project is owned by user 7, and several other users have access to it via different methods.
|
||||
# It is used to test the projectUsers method.
|
||||
-
|
||||
id: 18
|
||||
title: Test18
|
||||
|
@ -190,7 +190,7 @@
|
|||
position: 19
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
# User 1 does not have access to this list
|
||||
# User 1 does not have access to this project
|
||||
-
|
||||
id: 20
|
||||
title: Test20
|
||||
|
@ -242,3 +242,24 @@
|
|||
position: 7
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
-
|
||||
id: 25
|
||||
title: Test25 with background
|
||||
description: Lorem Ipsum
|
||||
identifier: test6
|
||||
owner_id: 6
|
||||
namespace_id: 6
|
||||
background_file_id: 1
|
||||
position: 8
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
-
|
||||
id: 26
|
||||
title: List 26 for Caldav tests
|
||||
description: Lorem Ipsum
|
||||
identifier: test26
|
||||
owner_id: 15
|
||||
namespace_id: 18
|
||||
position: 1
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
|
@ -9,13 +9,13 @@
|
|||
user_id: 6
|
||||
created: 2021-02-01 15:13:12
|
||||
- id: 3
|
||||
entity_type: 2 # List
|
||||
entity_type: 2 # project
|
||||
entity_id: 12 # belongs to namespace 7
|
||||
user_id: 6
|
||||
created: 2021-02-01 15:13:12
|
||||
- id: 4
|
||||
entity_type: 3 # Task
|
||||
entity_id: 22 # belongs to list 13 which belongs to namespace 8
|
||||
entity_id: 22 # belongs to project 13 which belongs to namespace 8
|
||||
user_id: 6
|
||||
created: 2021-02-01 15:13:12
|
||||
- id: 5
|
||||
|
@ -24,7 +24,7 @@
|
|||
user_id: 6
|
||||
created: 2021-02-01 15:13:12
|
||||
- id: 6
|
||||
entity_type: 2 # List
|
||||
entity_type: 2 # project
|
||||
entity_id: 13
|
||||
user_id: 6
|
||||
created: 2021-02-01 15:13:12
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
description: 'Lorem Ipsum'
|
||||
done: false
|
||||
created_by_id: 1
|
||||
list_id: 1
|
||||
project_id: 1
|
||||
index: 1
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
|
@ -13,7 +13,7 @@
|
|||
title: 'task #2 done'
|
||||
done: true
|
||||
created_by_id: 1
|
||||
list_id: 1
|
||||
project_id: 1
|
||||
index: 2
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
|
@ -23,7 +23,7 @@
|
|||
title: 'task #3 high prio'
|
||||
done: false
|
||||
created_by_id: 1
|
||||
list_id: 1
|
||||
project_id: 1
|
||||
index: 3
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
|
@ -33,7 +33,7 @@
|
|||
title: 'task #4 low prio'
|
||||
done: false
|
||||
created_by_id: 1
|
||||
list_id: 1
|
||||
project_id: 1
|
||||
index: 4
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
|
@ -43,7 +43,7 @@
|
|||
title: 'task #5 higher due date'
|
||||
done: false
|
||||
created_by_id: 1
|
||||
list_id: 1
|
||||
project_id: 1
|
||||
index: 5
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
|
@ -53,7 +53,7 @@
|
|||
title: 'task #6 lower due date'
|
||||
done: false
|
||||
created_by_id: 1
|
||||
list_id: 1
|
||||
project_id: 1
|
||||
index: 6
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
|
@ -63,7 +63,7 @@
|
|||
title: 'task #7 with start date'
|
||||
done: false
|
||||
created_by_id: 1
|
||||
list_id: 1
|
||||
project_id: 1
|
||||
index: 7
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
|
@ -73,7 +73,7 @@
|
|||
title: 'task #8 with end date'
|
||||
done: false
|
||||
created_by_id: 1
|
||||
list_id: 1
|
||||
project_id: 1
|
||||
index: 8
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
|
@ -83,7 +83,7 @@
|
|||
title: 'task #9 with start and end date'
|
||||
done: false
|
||||
created_by_id: 1
|
||||
list_id: 1
|
||||
project_id: 1
|
||||
index: 9
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
|
@ -94,7 +94,7 @@
|
|||
title: 'task #10 basic'
|
||||
done: false
|
||||
created_by_id: 1
|
||||
list_id: 1
|
||||
project_id: 1
|
||||
index: 10
|
||||
bucket_id: 1
|
||||
created: 2018-12-01 01:12:04
|
||||
|
@ -103,7 +103,7 @@
|
|||
title: 'task #11 basic'
|
||||
done: false
|
||||
created_by_id: 1
|
||||
list_id: 1
|
||||
project_id: 1
|
||||
index: 11
|
||||
bucket_id: 1
|
||||
created: 2018-12-01 01:12:04
|
||||
|
@ -112,25 +112,25 @@
|
|||
title: 'task #12 basic'
|
||||
done: false
|
||||
created_by_id: 1
|
||||
list_id: 1
|
||||
project_id: 1
|
||||
index: 12
|
||||
bucket_id: 1
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
- id: 13
|
||||
title: 'task #13 basic other list'
|
||||
title: 'task #13 basic other project'
|
||||
done: false
|
||||
created_by_id: 1
|
||||
list_id: 2
|
||||
project_id: 2
|
||||
index: 1
|
||||
bucket_id: 4
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
- id: 14
|
||||
title: 'task #14 basic other list'
|
||||
title: 'task #14 basic other project'
|
||||
done: false
|
||||
created_by_id: 5
|
||||
list_id: 5
|
||||
project_id: 5
|
||||
index: 1
|
||||
bucket_id: 18
|
||||
created: 2018-12-01 01:12:04
|
||||
|
@ -139,7 +139,7 @@
|
|||
title: 'task #15'
|
||||
done: false
|
||||
created_by_id: 6
|
||||
list_id: 6
|
||||
project_id: 6
|
||||
index: 1
|
||||
bucket_id: 6
|
||||
created: 2018-12-01 01:12:04
|
||||
|
@ -148,7 +148,7 @@
|
|||
title: 'task #16'
|
||||
done: false
|
||||
created_by_id: 6
|
||||
list_id: 7
|
||||
project_id: 7
|
||||
index: 1
|
||||
bucket_id: 7
|
||||
created: 2018-12-01 01:12:04
|
||||
|
@ -157,7 +157,7 @@
|
|||
title: 'task #17'
|
||||
done: false
|
||||
created_by_id: 6
|
||||
list_id: 8
|
||||
project_id: 8
|
||||
index: 1
|
||||
bucket_id: 8
|
||||
created: 2018-12-01 01:12:04
|
||||
|
@ -166,7 +166,7 @@
|
|||
title: 'task #18'
|
||||
done: false
|
||||
created_by_id: 6
|
||||
list_id: 9
|
||||
project_id: 9
|
||||
index: 1
|
||||
bucket_id: 9
|
||||
created: 2018-12-01 01:12:04
|
||||
|
@ -175,7 +175,7 @@
|
|||
title: 'task #19'
|
||||
done: false
|
||||
created_by_id: 6
|
||||
list_id: 10
|
||||
project_id: 10
|
||||
index: 1
|
||||
bucket_id: 10
|
||||
created: 2018-12-01 01:12:04
|
||||
|
@ -184,7 +184,7 @@
|
|||
title: 'task #20'
|
||||
done: false
|
||||
created_by_id: 6
|
||||
list_id: 11
|
||||
project_id: 11
|
||||
index: 1
|
||||
bucket_id: 11
|
||||
created: 2018-12-01 01:12:04
|
||||
|
@ -193,7 +193,7 @@
|
|||
title: 'task #21'
|
||||
done: false
|
||||
created_by_id: 6
|
||||
list_id: 12
|
||||
project_id: 12
|
||||
index: 1
|
||||
bucket_id: 12
|
||||
created: 2018-12-01 01:12:04
|
||||
|
@ -202,7 +202,7 @@
|
|||
title: 'task #22'
|
||||
done: false
|
||||
created_by_id: 6
|
||||
list_id: 13
|
||||
project_id: 13
|
||||
index: 1
|
||||
bucket_id: 13
|
||||
created: 2018-12-01 01:12:04
|
||||
|
@ -211,7 +211,7 @@
|
|||
title: 'task #23'
|
||||
done: false
|
||||
created_by_id: 6
|
||||
list_id: 14
|
||||
project_id: 14
|
||||
index: 1
|
||||
bucket_id: 14
|
||||
created: 2018-12-01 01:12:04
|
||||
|
@ -220,7 +220,7 @@
|
|||
title: 'task #24'
|
||||
done: false
|
||||
created_by_id: 6
|
||||
list_id: 15
|
||||
project_id: 15
|
||||
index: 1
|
||||
bucket_id: 15
|
||||
created: 2018-12-01 01:12:04
|
||||
|
@ -229,7 +229,7 @@
|
|||
title: 'task #25'
|
||||
done: false
|
||||
created_by_id: 6
|
||||
list_id: 16
|
||||
project_id: 16
|
||||
index: 1
|
||||
bucket_id: 16
|
||||
created: 2018-12-01 01:12:04
|
||||
|
@ -238,7 +238,7 @@
|
|||
title: 'task #26'
|
||||
done: false
|
||||
created_by_id: 6
|
||||
list_id: 17
|
||||
project_id: 17
|
||||
index: 1
|
||||
bucket_id: 17
|
||||
created: 2018-12-01 01:12:04
|
||||
|
@ -247,7 +247,7 @@
|
|||
title: 'task #27 with reminders'
|
||||
done: false
|
||||
created_by_id: 1
|
||||
list_id: 1
|
||||
project_id: 1
|
||||
index: 12
|
||||
bucket_id: 1
|
||||
created: 2018-12-01 01:12:04
|
||||
|
@ -257,7 +257,7 @@
|
|||
done: false
|
||||
created_by_id: 1
|
||||
repeat_after: 3600
|
||||
list_id: 1
|
||||
project_id: 1
|
||||
index: 13
|
||||
bucket_id: 1
|
||||
created: 2018-12-01 01:12:04
|
||||
|
@ -266,7 +266,7 @@
|
|||
title: 'task #29 with parent task (1)'
|
||||
done: false
|
||||
created_by_id: 1
|
||||
list_id: 1
|
||||
project_id: 1
|
||||
index: 14
|
||||
bucket_id: 1
|
||||
created: 2018-12-01 01:12:04
|
||||
|
@ -275,7 +275,7 @@
|
|||
title: 'task #30 with assignees'
|
||||
done: false
|
||||
created_by_id: 1
|
||||
list_id: 1
|
||||
project_id: 1
|
||||
index: 15
|
||||
bucket_id: 1
|
||||
created: 2018-12-01 01:12:04
|
||||
|
@ -284,7 +284,7 @@
|
|||
title: 'task #31 with color'
|
||||
done: false
|
||||
created_by_id: 1
|
||||
list_id: 1
|
||||
project_id: 1
|
||||
index: 16
|
||||
hex_color: f0f0f0
|
||||
bucket_id: 1
|
||||
|
@ -294,7 +294,7 @@
|
|||
title: 'task #32'
|
||||
done: false
|
||||
created_by_id: 1
|
||||
list_id: 3
|
||||
project_id: 3
|
||||
index: 1
|
||||
bucket_id: 21
|
||||
created: 2018-12-01 01:12:04
|
||||
|
@ -303,7 +303,7 @@
|
|||
title: 'task #33 with percent done'
|
||||
done: false
|
||||
created_by_id: 1
|
||||
list_id: 1
|
||||
project_id: 1
|
||||
index: 17
|
||||
percent_done: 0.5
|
||||
bucket_id: 1
|
||||
|
@ -314,7 +314,7 @@
|
|||
title: 'task #34'
|
||||
done: false
|
||||
created_by_id: 13
|
||||
list_id: 20
|
||||
project_id: 20
|
||||
index: 20
|
||||
bucket_id: 5
|
||||
created: 2018-12-01 01:12:04
|
||||
|
@ -323,7 +323,7 @@
|
|||
title: 'task #35'
|
||||
done: false
|
||||
created_by_id: 1
|
||||
list_id: 21
|
||||
project_id: 21
|
||||
index: 1
|
||||
bucket_id: 19
|
||||
created: 2018-12-01 01:12:04
|
||||
|
@ -332,16 +332,17 @@
|
|||
title: 'task #36'
|
||||
done: false
|
||||
created_by_id: 1
|
||||
list_id: 22
|
||||
project_id: 22
|
||||
index: 1
|
||||
bucket_id: 20
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
due_date: 2018-10-30 22:25:24
|
||||
- id: 37
|
||||
title: 'task #37'
|
||||
done: false
|
||||
created_by_id: -2
|
||||
list_id: 2
|
||||
project_id: 2
|
||||
index: 2
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
|
@ -349,8 +350,22 @@
|
|||
title: 'task #37 done with due date'
|
||||
done: true
|
||||
created_by_id: 1
|
||||
list_id: 22
|
||||
project_id: 22
|
||||
index: 2
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
due_date: 2018-10-30 22:25:24
|
||||
- id: 39
|
||||
uid: 'uid-caldav-test'
|
||||
title: 'Title Caldav Test'
|
||||
description: 'Description Caldav Test'
|
||||
priority: 3
|
||||
done: false
|
||||
created_by_id: 15
|
||||
project_id: 26
|
||||
index: 39
|
||||
due_date: 2023-03-01 15:00:00
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
bucket_id: 1
|
||||
position: 39
|
||||
|
|
|
@ -1,54 +1,54 @@
|
|||
- id: 1
|
||||
team_id: 1
|
||||
list_id: 3
|
||||
project_id: 3
|
||||
right: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
# This team has read only access on list 6
|
||||
# This team has read only access on project 6
|
||||
- id: 2
|
||||
team_id: 2
|
||||
list_id: 6
|
||||
project_id: 6
|
||||
right: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
# This team has write access on list 7
|
||||
# This team has write access on project 7
|
||||
- id: 3
|
||||
team_id: 3
|
||||
list_id: 7
|
||||
project_id: 7
|
||||
right: 1
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
# This team has admin access on list 8
|
||||
# This team has admin access on project 8
|
||||
- id: 4
|
||||
team_id: 4
|
||||
list_id: 8
|
||||
project_id: 8
|
||||
right: 2
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
# Readonly acces on list 19
|
||||
# Readonly acces on project 19
|
||||
- id: 5
|
||||
team_id: 8
|
||||
list_id: 19
|
||||
project_id: 19
|
||||
right: 2
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
# Write acces on list 19
|
||||
# Write acces on project 19
|
||||
- id: 6
|
||||
team_id: 9
|
||||
list_id: 19
|
||||
project_id: 19
|
||||
right: 1
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
# Admin acces on list 19
|
||||
# Admin acces on project 19
|
||||
- id: 7
|
||||
team_id: 10
|
||||
list_id: 19
|
||||
project_id: 19
|
||||
right: 2
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 8
|
||||
team_id: 1
|
||||
list_id: 21
|
||||
project_id: 21
|
||||
right: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
|
@ -3,13 +3,13 @@
|
|||
description: Lorem Ipsum
|
||||
created_by_id: 1
|
||||
- id: 2
|
||||
name: testteam2_read_only_on_list6
|
||||
name: testteam2_read_only_on_project6
|
||||
created_by_id: 1
|
||||
- id: 3
|
||||
name: testteam3_write_on_list7
|
||||
name: testteam3_write_on_project7
|
||||
created_by_id: 1
|
||||
- id: 4
|
||||
name: testteam4_admin_on_list8
|
||||
name: testteam4_admin_on_project8
|
||||
created_by_id: 1
|
||||
- id: 5
|
||||
name: testteam2_read_only_on_namespace7
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
issuer: local
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
# This use is used to create a whole bunch of lists which are then shared directly with a user
|
||||
# This use is used to create a whole bunch of projects which are then shared directly with a user
|
||||
- id: 6
|
||||
username: 'user6'
|
||||
password: '$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.' # 1234
|
||||
|
@ -109,3 +109,10 @@
|
|||
subject: '12345'
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 15
|
||||
username: 'user15'
|
||||
password: '$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.' # 1234
|
||||
email: 'user15@example.com'
|
||||
issuer: local
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
|
|
|
@ -1,48 +1,54 @@
|
|||
- id: 1
|
||||
user_id: 1
|
||||
list_id: 3
|
||||
project_id: 3
|
||||
right: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 2
|
||||
user_id: 2
|
||||
list_id: 3
|
||||
project_id: 3
|
||||
right: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 3
|
||||
user_id: 1
|
||||
list_id: 9
|
||||
project_id: 9
|
||||
right: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 4
|
||||
user_id: 1
|
||||
list_id: 10
|
||||
project_id: 10
|
||||
right: 1
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 5
|
||||
user_id: 1
|
||||
list_id: 11
|
||||
project_id: 11
|
||||
right: 2
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 6
|
||||
user_id: 4
|
||||
list_id: 19
|
||||
project_id: 19
|
||||
right: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 7
|
||||
user_id: 5
|
||||
list_id: 19
|
||||
project_id: 19
|
||||
right: 1
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 8
|
||||
user_id: 6
|
||||
list_id: 19
|
||||
project_id: 19
|
||||
right: 2
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 9
|
||||
user_id: 15
|
||||
project_id: 26
|
||||
right: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
|
@ -23,9 +23,11 @@ import (
|
|||
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"xorm.io/core"
|
||||
"xorm.io/builder"
|
||||
"xorm.io/xorm"
|
||||
"xorm.io/xorm/names"
|
||||
)
|
||||
|
||||
// CreateTestEngine creates an instance of the db engine which lives in memory
|
||||
|
@ -48,7 +50,7 @@ func CreateTestEngine() (engine *xorm.Engine, err error) {
|
|||
}
|
||||
}
|
||||
|
||||
engine.SetMapper(core.GonicMapper{})
|
||||
engine.SetMapper(names.GonicMapper{})
|
||||
logger := log.NewXormLogger("DEBUG")
|
||||
logger.ShowSQL(os.Getenv("UNIT_TESTS_VERBOSE") == "1")
|
||||
engine.SetLogger(logger)
|
||||
|
@ -101,3 +103,10 @@ func AssertMissing(t *testing.T, table string, values map[string]interface{}) {
|
|||
assert.NoError(t, err, fmt.Sprintf("Failed to assert entries don't exist in db, error was: %s", err))
|
||||
assert.False(t, exists, fmt.Sprintf("Entries %v exist in table %s", values, table))
|
||||
}
|
||||
|
||||
// AssertCount checks if a number of entries exists in the database
|
||||
func AssertCount(t *testing.T, table string, where builder.Cond, count int64) {
|
||||
dbCount, err := x.Table(table).Where(where).Count()
|
||||
assert.NoError(t, err, fmt.Sprintf("Failed to assert count in db, error was: %s", err))
|
||||
assert.Equal(t, count, dbCount, fmt.Sprintf("Found %d entries instead of expected %d in table %s", dbCount, count, table))
|
||||
}
|
||||
|
|
|
@ -83,7 +83,7 @@ func CreateWithMime(f io.Reader, realname string, realsize uint64, a web.Auth, m
|
|||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
file, err = CreateWithMimeAndSession(s, f, realname, realsize, a, mime)
|
||||
file, err = CreateWithMimeAndSession(s, f, realname, realsize, a, mime, true)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return
|
||||
|
@ -91,14 +91,14 @@ func CreateWithMime(f io.Reader, realname string, realsize uint64, a web.Auth, m
|
|||
return
|
||||
}
|
||||
|
||||
func CreateWithMimeAndSession(s *xorm.Session, f io.Reader, realname string, realsize uint64, a web.Auth, mime string) (file *File, err error) {
|
||||
func CreateWithMimeAndSession(s *xorm.Session, f io.Reader, realname string, realsize uint64, a web.Auth, mime string, checkFileSizeLimit bool) (file *File, err error) {
|
||||
// Get and parse the configured file size
|
||||
var maxSize datasize.ByteSize
|
||||
err = maxSize.UnmarshalText([]byte(config.FilesMaxSize.GetString()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if realsize > maxSize.Bytes() {
|
||||
if realsize > maxSize.Bytes() && checkFileSizeLimit {
|
||||
return nil, ErrFileIsTooLarge{Size: realsize}
|
||||
}
|
||||
|
||||
|
@ -140,7 +140,7 @@ func (f *File) Delete() (err error) {
|
|||
var perr *os.PathError
|
||||
if errors.As(err, &perr) {
|
||||
// Don't fail when removing the file failed
|
||||
log.Errorf("Error deleting file %d: %w", err)
|
||||
log.Errorf("Error deleting file %d: %w", f.ID, err)
|
||||
return s.Commit()
|
||||
}
|
||||
|
||||
|
|
|
@ -28,37 +28,37 @@ import (
|
|||
// This tests the following behaviour:
|
||||
// 1. A namespace should not be editable if it is archived.
|
||||
// 1. With the exception being to un-archive it.
|
||||
// 2. A list which belongs to an archived namespace cannot be edited.
|
||||
// 3. An archived list should not be editable.
|
||||
// 2. A project which belongs to an archived namespace cannot be edited.
|
||||
// 3. An archived project should not be editable.
|
||||
// 1. Except for un-archiving it.
|
||||
// 4. It is not possible to un-archive a list individually if its namespace is archived.
|
||||
// 5. Creating new lists on an archived namespace should not work.
|
||||
// 6. Creating new tasks on an archived list should not work.
|
||||
// 7. Creating new tasks on a list who's namespace is archived should not work.
|
||||
// 8. Editing tasks on an archived list should not work.
|
||||
// 9. Editing tasks on a list who's namespace is archived should not work.
|
||||
// 10. Archived namespaces should not appear in the list with all namespaces.
|
||||
// 11. Archived lists should not appear in the list with all lists.
|
||||
// 12. Lists who's namespace is archived should not appear in the list with all lists.
|
||||
// 4. It is not possible to un-archive a project individually if its namespace is archived.
|
||||
// 5. Creating new projects on an archived namespace should not work.
|
||||
// 6. Creating new tasks on an archived project should not work.
|
||||
// 7. Creating new tasks on a project who's namespace is archived should not work.
|
||||
// 8. Editing tasks on an archived project should not work.
|
||||
// 9. Editing tasks on a project who's namespace is archived should not work.
|
||||
// 10. Archived namespaces should not appear in the project with all namespaces.
|
||||
// 11. Archived projects should not appear in the project with all projects.
|
||||
// 12. Projects who's namespace is archived should not appear in the project with all projects.
|
||||
//
|
||||
// All of this is tested through integration tests because it's not yet clear if this will be implemented directly
|
||||
// or with some kind of middleware.
|
||||
//
|
||||
// Maybe the inheritance of lists from namespaces could be solved with some kind of is_archived_inherited flag -
|
||||
// that way I'd only need to implement the checking on a list level and update the flag for all lists once the
|
||||
// namespace is archived. The archived flag would then be used to not accedentially unarchive lists which were
|
||||
// Maybe the inheritance of projects from namespaces could be solved with some kind of is_archived_inherited flag -
|
||||
// that way I'd only need to implement the checking on a project level and update the flag for all projects once the
|
||||
// namespace is archived. The archived flag would then be used to not accedentially unarchive projects which were
|
||||
// already individually archived when the namespace was archived.
|
||||
// Should still test it all though.
|
||||
//
|
||||
// Namespace 16 is archived
|
||||
// List 21 belongs to namespace 16
|
||||
// List 22 is archived individually
|
||||
// Project 21 belongs to namespace 16
|
||||
// Project 22 is archived individually
|
||||
|
||||
func TestArchived(t *testing.T) {
|
||||
testListHandler := webHandlerTest{
|
||||
testProjectHandler := webHandlerTest{
|
||||
user: &testuser1,
|
||||
strFunc: func() handler.CObject {
|
||||
return &models.List{}
|
||||
return &models.Project{}
|
||||
},
|
||||
t: t,
|
||||
}
|
||||
|
@ -116,54 +116,54 @@ func TestArchived(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"is_archived":false`)
|
||||
})
|
||||
t.Run("no new lists", func(t *testing.T) {
|
||||
_, err := testListHandler.testCreateWithUser(nil, map[string]string{"namespace": "16"}, `{"title":"Lorem"}`)
|
||||
t.Run("no new projects", func(t *testing.T) {
|
||||
_, err := testProjectHandler.testCreateWithUser(nil, map[string]string{"namespace": "16"}, `{"title":"Lorem"}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeNamespaceIsArchived)
|
||||
})
|
||||
t.Run("should not appear in the list", func(t *testing.T) {
|
||||
t.Run("should not appear in the project", func(t *testing.T) {
|
||||
rec, err := testNamespaceHandler.testReadAllWithUser(nil, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.NotContains(t, rec.Body.String(), `"title":"Archived testnamespace16"`)
|
||||
})
|
||||
t.Run("should appear in the list if explicitly requested", func(t *testing.T) {
|
||||
t.Run("should appear in the project if explicitly requested", func(t *testing.T) {
|
||||
rec, err := testNamespaceHandler.testReadAllWithUser(url.Values{"is_archived": []string{"true"}}, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Archived testnamespace16"`)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("list", func(t *testing.T) {
|
||||
t.Run("project", func(t *testing.T) {
|
||||
|
||||
taskTests := func(taskID string, errCode int, t *testing.T) {
|
||||
t.Run("task", func(t *testing.T) {
|
||||
t.Run("edit task", func(t *testing.T) {
|
||||
_, err := testTaskHandler.testUpdateWithUser(nil, map[string]string{"listtask": taskID}, `{"title":"TestIpsum"}`)
|
||||
_, err := testTaskHandler.testUpdateWithUser(nil, map[string]string{"projecttask": taskID}, `{"title":"TestIpsum"}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, errCode)
|
||||
})
|
||||
t.Run("delete", func(t *testing.T) {
|
||||
_, err := testTaskHandler.testDeleteWithUser(nil, map[string]string{"listtask": taskID})
|
||||
_, err := testTaskHandler.testDeleteWithUser(nil, map[string]string{"projecttask": taskID})
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, errCode)
|
||||
})
|
||||
t.Run("add new labels", func(t *testing.T) {
|
||||
_, err := testLabelHandler.testCreateWithUser(nil, map[string]string{"listtask": taskID}, `{"label_id":1}`)
|
||||
_, err := testLabelHandler.testCreateWithUser(nil, map[string]string{"projecttask": taskID}, `{"label_id":1}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, errCode)
|
||||
})
|
||||
t.Run("remove lables", func(t *testing.T) {
|
||||
_, err := testLabelHandler.testDeleteWithUser(nil, map[string]string{"listtask": taskID, "label": "4"})
|
||||
_, err := testLabelHandler.testDeleteWithUser(nil, map[string]string{"projecttask": taskID, "label": "4"})
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, errCode)
|
||||
})
|
||||
t.Run("add assignees", func(t *testing.T) {
|
||||
_, err := testAssigneeHandler.testCreateWithUser(nil, map[string]string{"listtask": taskID}, `{"user_id":3}`)
|
||||
_, err := testAssigneeHandler.testCreateWithUser(nil, map[string]string{"projecttask": taskID}, `{"user_id":3}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, errCode)
|
||||
})
|
||||
t.Run("remove assignees", func(t *testing.T) {
|
||||
_, err := testAssigneeHandler.testDeleteWithUser(nil, map[string]string{"listtask": taskID, "user": "2"})
|
||||
_, err := testAssigneeHandler.testDeleteWithUser(nil, map[string]string{"projecttask": taskID, "user": "2"})
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, errCode)
|
||||
})
|
||||
|
@ -194,45 +194,45 @@ func TestArchived(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
// The list belongs to an archived namespace
|
||||
// The project belongs to an archived namespace
|
||||
t.Run("archived namespace", func(t *testing.T) {
|
||||
t.Run("not editable", func(t *testing.T) {
|
||||
_, err := testListHandler.testUpdateWithUser(nil, map[string]string{"list": "21"}, `{"title":"TestIpsum","is_archived":true}`)
|
||||
_, err := testProjectHandler.testUpdateWithUser(nil, map[string]string{"project": "21"}, `{"title":"TestIpsum","is_archived":true}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeNamespaceIsArchived)
|
||||
})
|
||||
t.Run("no new tasks", func(t *testing.T) {
|
||||
_, err := testTaskHandler.testCreateWithUser(nil, map[string]string{"list": "21"}, `{"title":"Lorem"}`)
|
||||
_, err := testTaskHandler.testCreateWithUser(nil, map[string]string{"project": "21"}, `{"title":"Lorem"}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeNamespaceIsArchived)
|
||||
})
|
||||
t.Run("not unarchivable", func(t *testing.T) {
|
||||
_, err := testListHandler.testUpdateWithUser(nil, map[string]string{"list": "21"}, `{"title":"LoremIpsum","is_archived":false}`)
|
||||
_, err := testProjectHandler.testUpdateWithUser(nil, map[string]string{"project": "21"}, `{"title":"LoremIpsum","is_archived":false}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeNamespaceIsArchived)
|
||||
})
|
||||
|
||||
taskTests("35", models.ErrCodeNamespaceIsArchived, t)
|
||||
})
|
||||
// The list itself is archived
|
||||
// The project itself is archived
|
||||
t.Run("archived individually", func(t *testing.T) {
|
||||
t.Run("not editable", func(t *testing.T) {
|
||||
_, err := testListHandler.testUpdateWithUser(nil, map[string]string{"list": "22"}, `{"title":"TestIpsum","is_archived":true}`)
|
||||
_, err := testProjectHandler.testUpdateWithUser(nil, map[string]string{"project": "22"}, `{"title":"TestIpsum","is_archived":true}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeListIsArchived)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeProjectIsArchived)
|
||||
})
|
||||
t.Run("no new tasks", func(t *testing.T) {
|
||||
_, err := testTaskHandler.testCreateWithUser(nil, map[string]string{"list": "22"}, `{"title":"Lorem"}`)
|
||||
_, err := testTaskHandler.testCreateWithUser(nil, map[string]string{"project": "22"}, `{"title":"Lorem"}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeListIsArchived)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeProjectIsArchived)
|
||||
})
|
||||
t.Run("unarchivable", func(t *testing.T) {
|
||||
rec, err := testListHandler.testUpdateWithUser(nil, map[string]string{"list": "22"}, `{"title":"LoremIpsum","is_archived":false}`)
|
||||
rec, err := testProjectHandler.testUpdateWithUser(nil, map[string]string{"project": "22"}, `{"title":"LoremIpsum","is_archived":false,"namespace_id":1}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"is_archived":false`)
|
||||
})
|
||||
|
||||
taskTests("36", models.ErrCodeListIsArchived, t)
|
||||
taskTests("36", models.ErrCodeProjectIsArchived, t)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
69
pkg/integrations/caldav_test.go
Normal file
69
pkg/integrations/caldav_test.go
Normal file
|
@ -0,0 +1,69 @@
|
|||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public Licensee as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public Licensee
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package integrations
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"code.vikunja.io/api/pkg/routes/caldav"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const vtodo = `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
X-PUBLISHED-TTL:PT4H
|
||||
X-WR-CALNAME:List 26 for Caldav tests
|
||||
PRODID:-//Vikunja Todo App//EN
|
||||
BEGIN:VTODO
|
||||
UID:uid
|
||||
DTSTAMP:20230301T073337Z
|
||||
SUMMARY:Caldav Task 1
|
||||
CATEGORIES:tag1,tag2,tag3
|
||||
CREATED:20230301T073337Z
|
||||
LAST-MODIFIED:20230301T073337Z
|
||||
END:VTODO
|
||||
END:VCALENDAR`
|
||||
|
||||
func TestCaldav(t *testing.T) {
|
||||
t.Run("Delivers VTODO for project", func(t *testing.T) {
|
||||
rec, err := newCaldavTestRequestWithUser(t, http.MethodGet, caldav.ProjectHandler, &testuser15, ``, nil, map[string]string{"project": "26"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), "BEGIN:VCALENDAR")
|
||||
assert.Contains(t, rec.Body.String(), "PRODID:-//Vikunja Todo App//EN")
|
||||
assert.Contains(t, rec.Body.String(), "X-WR-CALNAME:List 26 for Caldav tests")
|
||||
assert.Contains(t, rec.Body.String(), "BEGIN:VTODO")
|
||||
assert.Contains(t, rec.Body.String(), "END:VTODO")
|
||||
assert.Contains(t, rec.Body.String(), "END:VCALENDAR")
|
||||
})
|
||||
t.Run("Import VTODO", func(t *testing.T) {
|
||||
rec, err := newCaldavTestRequestWithUser(t, http.MethodPut, caldav.TaskHandler, &testuser15, vtodo, nil, map[string]string{"project": "26", "task": "uid"})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, rec.Result().StatusCode, 201)
|
||||
})
|
||||
t.Run("Export VTODO", func(t *testing.T) {
|
||||
rec, err := newCaldavTestRequestWithUser(t, http.MethodGet, caldav.TaskHandler, &testuser15, ``, nil, map[string]string{"project": "26", "task": "uid-caldav-test"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), "BEGIN:VCALENDAR")
|
||||
assert.Contains(t, rec.Body.String(), "SUMMARY:Title Caldav Test")
|
||||
assert.Contains(t, rec.Body.String(), "DESCRIPTION:Description Caldav Test")
|
||||
assert.Contains(t, rec.Body.String(), "DUE:20230301T150000Z")
|
||||
assert.Contains(t, rec.Body.String(), "PRIORITY:3")
|
||||
assert.Contains(t, rec.Body.String(), "CATEGORIES:Label #4")
|
||||
})
|
||||
}
|
|
@ -33,6 +33,7 @@ import (
|
|||
"code.vikunja.io/api/pkg/modules/auth"
|
||||
"code.vikunja.io/api/pkg/modules/keyvalue"
|
||||
"code.vikunja.io/api/pkg/routes"
|
||||
"code.vikunja.io/api/pkg/routes/caldav"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"code.vikunja.io/web"
|
||||
"code.vikunja.io/web/handler"
|
||||
|
@ -49,30 +50,11 @@ var (
|
|||
Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.",
|
||||
Email: "user1@example.com",
|
||||
}
|
||||
testuser2 = user.User{
|
||||
ID: 2,
|
||||
Username: "user2",
|
||||
testuser15 = user.User{
|
||||
ID: 15,
|
||||
Username: "user15",
|
||||
Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.",
|
||||
Email: "user2@example.com",
|
||||
}
|
||||
testuser3 = user.User{
|
||||
ID: 3,
|
||||
Username: "user3",
|
||||
Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.",
|
||||
Email: "user3@example.com",
|
||||
}
|
||||
testuser4 = user.User{
|
||||
ID: 4,
|
||||
Username: "user4",
|
||||
Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.",
|
||||
Email: "user4@example.com",
|
||||
}
|
||||
testuser5 = user.User{
|
||||
ID: 4,
|
||||
Username: "user5",
|
||||
Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.",
|
||||
Email: "user5@example.com",
|
||||
Status: user.StatusDisabled,
|
||||
Email: "user15@example.com",
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -170,6 +152,19 @@ func newTestRequestWithLinkShare(t *testing.T, method string, handler echo.Handl
|
|||
return
|
||||
}
|
||||
|
||||
func newCaldavTestRequestWithUser(t *testing.T, method string, handler echo.HandlerFunc, user *user.User, payload string, queryParams url.Values, urlParams map[string]string) (rec *httptest.ResponseRecorder, err error) {
|
||||
rec, c := testRequestSetup(t, method, payload, queryParams, urlParams)
|
||||
c.Request().Header.Set(echo.HeaderContentType, echo.MIMETextPlain)
|
||||
|
||||
result, _ := caldav.BasicAuth(user.Username, "1234", c)
|
||||
if !result {
|
||||
t.Error("BasicAuth for caldav failed")
|
||||
t.FailNow()
|
||||
}
|
||||
err = handler(c)
|
||||
return
|
||||
}
|
||||
|
||||
func assertHandlerErrorCode(t *testing.T, err error, expectedErrorCode int) {
|
||||
if err == nil {
|
||||
t.Error("Error is nil")
|
||||
|
|
|
@ -39,7 +39,7 @@ func TestBucket(t *testing.T) {
|
|||
linkShare: &models.LinkSharing{
|
||||
ID: 2,
|
||||
Hash: "test2",
|
||||
ListID: 2,
|
||||
ProjectID: 2,
|
||||
Right: models.RightWrite,
|
||||
SharingType: models.SharingTypeWithoutPassword,
|
||||
SharedByID: 1,
|
||||
|
@ -51,17 +51,17 @@ func TestBucket(t *testing.T) {
|
|||
}
|
||||
t.Run("ReadAll", func(t *testing.T) {
|
||||
t.Run("Normal", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(nil, map[string]string{"list": "1"})
|
||||
rec, err := testHandler.testReadAllWithUser(nil, map[string]string{"project": "1"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `testbucket1`)
|
||||
assert.Contains(t, rec.Body.String(), `testbucket2`)
|
||||
assert.Contains(t, rec.Body.String(), `testbucket3`)
|
||||
assert.NotContains(t, rec.Body.String(), `testbucket4`) // Different List
|
||||
assert.NotContains(t, rec.Body.String(), `testbucket4`) // Different Project
|
||||
})
|
||||
})
|
||||
t.Run("Update", func(t *testing.T) {
|
||||
t.Run("Normal", func(t *testing.T) {
|
||||
// Check the list was loaded successfully afterwards, see testReadOneWithUser
|
||||
// Check the project was loaded successfully afterwards, see testReadOneWithUser
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"bucket": "1"}, `{"title":"TestLoremIpsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
|
||||
|
@ -150,7 +150,7 @@ func TestBucket(t *testing.T) {
|
|||
})
|
||||
t.Run("Delete", func(t *testing.T) {
|
||||
t.Run("Normal", func(t *testing.T) {
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "1", "bucket": "1"})
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "1", "bucket": "1"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
|
||||
})
|
||||
|
@ -162,70 +162,70 @@ func TestBucket(t *testing.T) {
|
|||
t.Run("Rights check", func(t *testing.T) {
|
||||
t.Run("Forbidden", func(t *testing.T) {
|
||||
// Owned by user13
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "20", "bucket": "5"})
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "20", "bucket": "5"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via Team readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "6", "bucket": "6"})
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "6", "bucket": "6"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via Team write", func(t *testing.T) {
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "7", "bucket": "7"})
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "7", "bucket": "7"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
|
||||
})
|
||||
t.Run("Shared Via Team admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "8", "bucket": "8"})
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "8", "bucket": "8"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
|
||||
})
|
||||
|
||||
t.Run("Shared Via User readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "9", "bucket": "9"})
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "9", "bucket": "9"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via User write", func(t *testing.T) {
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "10", "bucket": "10"})
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "10", "bucket": "10"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
|
||||
})
|
||||
t.Run("Shared Via User admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "11", "bucket": "11"})
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "11", "bucket": "11"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
|
||||
})
|
||||
|
||||
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "12", "bucket": "12"})
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "12", "bucket": "12"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "13", "bucket": "13"})
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "13", "bucket": "13"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "14", "bucket": "14"})
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "14", "bucket": "14"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
|
||||
})
|
||||
|
||||
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "15", "bucket": "15"})
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "15", "bucket": "15"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "16", "bucket": "16"})
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "16", "bucket": "16"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "17", "bucket": "17"})
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "17", "bucket": "17"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
|
||||
})
|
||||
|
@ -233,92 +233,92 @@ func TestBucket(t *testing.T) {
|
|||
})
|
||||
t.Run("Create", func(t *testing.T) {
|
||||
t.Run("Normal", func(t *testing.T) {
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "1"}, `{"title":"Lorem Ipsum"}`)
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "1"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
|
||||
})
|
||||
t.Run("Nonexisting", func(t *testing.T) {
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "9999"}, `{"title":"Lorem Ipsum"}`)
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "9999"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeListDoesNotExist)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeProjectDoesNotExist)
|
||||
})
|
||||
t.Run("Rights check", func(t *testing.T) {
|
||||
t.Run("Forbidden", func(t *testing.T) {
|
||||
// Owned by user13
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "20"}, `{"title":"Lorem Ipsum"}`)
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "20"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via Team readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "6"}, `{"title":"Lorem Ipsum"}`)
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "6"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via Team write", func(t *testing.T) {
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "7"}, `{"title":"Lorem Ipsum"}`)
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "7"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
|
||||
})
|
||||
t.Run("Shared Via Team admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "8"}, `{"title":"Lorem Ipsum"}`)
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "8"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
|
||||
})
|
||||
|
||||
t.Run("Shared Via User readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "9"}, `{"title":"Lorem Ipsum"}`)
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "9"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via User write", func(t *testing.T) {
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "10"}, `{"title":"Lorem Ipsum"}`)
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "10"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
|
||||
})
|
||||
t.Run("Shared Via User admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "11"}, `{"title":"Lorem Ipsum"}`)
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "11"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
|
||||
})
|
||||
|
||||
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "12"}, `{"title":"Lorem Ipsum"}`)
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "12"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "13"}, `{"title":"Lorem Ipsum"}`)
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "13"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "14"}, `{"title":"Lorem Ipsum"}`)
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "14"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
|
||||
})
|
||||
|
||||
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "15"}, `{"title":"Lorem Ipsum"}`)
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "15"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "16"}, `{"title":"Lorem Ipsum"}`)
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "16"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "17"}, `{"title":"Lorem Ipsum"}`)
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "17"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
|
||||
})
|
||||
})
|
||||
t.Run("Link Share", func(t *testing.T) {
|
||||
rec, err := testHandlerLinkShareWrite.testCreateWithLinkShare(nil, map[string]string{"list": "2"}, `{"title":"Lorem Ipsum"}`)
|
||||
rec, err := testHandlerLinkShareWrite.testCreateWithLinkShare(nil, map[string]string{"project": "2"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
|
||||
db.AssertExists(t, "buckets", map[string]interface{}{
|
||||
"list_id": 2,
|
||||
"project_id": 2,
|
||||
"created_by_id": -2,
|
||||
"title": "Lorem Ipsum",
|
||||
}, false)
|
||||
|
|
|
@ -31,14 +31,14 @@ func TestLinkSharingAuth(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, rec.Code)
|
||||
assert.Contains(t, rec.Body.String(), `"token":"`)
|
||||
assert.Contains(t, rec.Body.String(), `"list_id":1`)
|
||||
assert.Contains(t, rec.Body.String(), `"project_id":1`)
|
||||
})
|
||||
t.Run("Without Password, Password Provided", func(t *testing.T) {
|
||||
rec, err := newTestRequest(t, http.MethodPost, apiv1.AuthenticateLinkShare, `{"password":"somethingsomething"}`, nil, map[string]string{"share": "test"})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, rec.Code)
|
||||
assert.Contains(t, rec.Body.String(), `"token":"`)
|
||||
assert.Contains(t, rec.Body.String(), `"list_id":1`)
|
||||
assert.Contains(t, rec.Body.String(), `"project_id":1`)
|
||||
})
|
||||
t.Run("With Password, No Password Provided", func(t *testing.T) {
|
||||
_, err := newTestRequest(t, http.MethodPost, apiv1.AuthenticateLinkShare, ``, nil, map[string]string{"share": "testWithPassword"})
|
||||
|
@ -50,7 +50,7 @@ func TestLinkSharingAuth(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
assert.Equal(t, http.StatusOK, rec.Code)
|
||||
assert.Contains(t, rec.Body.String(), `"token":"`)
|
||||
assert.Contains(t, rec.Body.String(), `"list_id":1`)
|
||||
assert.Contains(t, rec.Body.String(), `"project_id":1`)
|
||||
})
|
||||
t.Run("With Wrong Password", func(t *testing.T) {
|
||||
_, err := newTestRequest(t, http.MethodPost, apiv1.AuthenticateLinkShare, `{"password":"someWrongPassword"}`, nil, map[string]string{"share": "testWithPassword"})
|
||||
|
|
|
@ -31,7 +31,7 @@ func TestLinkSharing(t *testing.T) {
|
|||
linkshareRead := &models.LinkSharing{
|
||||
ID: 1,
|
||||
Hash: "test1",
|
||||
ListID: 1,
|
||||
ProjectID: 1,
|
||||
Right: models.RightRead,
|
||||
SharingType: models.SharingTypeWithoutPassword,
|
||||
SharedByID: 1,
|
||||
|
@ -40,7 +40,7 @@ func TestLinkSharing(t *testing.T) {
|
|||
linkShareWrite := &models.LinkSharing{
|
||||
ID: 2,
|
||||
Hash: "test2",
|
||||
ListID: 2,
|
||||
ProjectID: 2,
|
||||
Right: models.RightWrite,
|
||||
SharingType: models.SharingTypeWithoutPassword,
|
||||
SharedByID: 1,
|
||||
|
@ -49,7 +49,7 @@ func TestLinkSharing(t *testing.T) {
|
|||
linkShareAdmin := &models.LinkSharing{
|
||||
ID: 3,
|
||||
Hash: "test3",
|
||||
ListID: 3,
|
||||
ProjectID: 3,
|
||||
Right: models.RightAdmin,
|
||||
SharingType: models.SharingTypeWithoutPassword,
|
||||
SharedByID: 1,
|
||||
|
@ -65,102 +65,102 @@ func TestLinkSharing(t *testing.T) {
|
|||
}
|
||||
t.Run("Forbidden", func(t *testing.T) {
|
||||
t.Run("read only", func(t *testing.T) {
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "20"}, `{"right":0}`)
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "20"}, `{"right":0}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("write", func(t *testing.T) {
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "20"}, `{"right":1}`)
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "20"}, `{"right":1}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("admin", func(t *testing.T) {
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "20"}, `{"right":2}`)
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "20"}, `{"right":2}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
})
|
||||
t.Run("Read only access", func(t *testing.T) {
|
||||
t.Run("read only", func(t *testing.T) {
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "9"}, `{"right":0}`)
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "9"}, `{"right":0}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("write", func(t *testing.T) {
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "9"}, `{"right":1}`)
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "9"}, `{"right":1}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("admin", func(t *testing.T) {
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "9"}, `{"right":2}`)
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "9"}, `{"right":2}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
})
|
||||
t.Run("Write access", func(t *testing.T) {
|
||||
t.Run("read only", func(t *testing.T) {
|
||||
req, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "10"}, `{"right":0}`)
|
||||
req, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "10"}, `{"right":0}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, req.Body.String(), `"hash":`)
|
||||
})
|
||||
t.Run("write", func(t *testing.T) {
|
||||
req, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "10"}, `{"right":1}`)
|
||||
req, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "10"}, `{"right":1}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, req.Body.String(), `"hash":`)
|
||||
})
|
||||
t.Run("admin", func(t *testing.T) {
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "10"}, `{"right":2}`)
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "10"}, `{"right":2}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
})
|
||||
t.Run("Admin access", func(t *testing.T) {
|
||||
t.Run("read only", func(t *testing.T) {
|
||||
req, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "11"}, `{"right":0}`)
|
||||
req, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "11"}, `{"right":0}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, req.Body.String(), `"hash":`)
|
||||
})
|
||||
t.Run("write", func(t *testing.T) {
|
||||
req, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "11"}, `{"right":1}`)
|
||||
req, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "11"}, `{"right":1}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, req.Body.String(), `"hash":`)
|
||||
})
|
||||
t.Run("admin", func(t *testing.T) {
|
||||
req, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "11"}, `{"right":2}`)
|
||||
req, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "11"}, `{"right":2}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, req.Body.String(), `"hash":`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Lists", func(t *testing.T) {
|
||||
testHandlerListReadOnly := webHandlerTest{
|
||||
t.Run("Projects", func(t *testing.T) {
|
||||
testHandlerProjectReadOnly := webHandlerTest{
|
||||
linkShare: linkshareRead,
|
||||
strFunc: func() handler.CObject {
|
||||
return &models.List{}
|
||||
return &models.Project{}
|
||||
},
|
||||
t: t,
|
||||
}
|
||||
testHandlerListWrite := webHandlerTest{
|
||||
testHandlerProjectWrite := webHandlerTest{
|
||||
linkShare: linkShareWrite,
|
||||
strFunc: func() handler.CObject {
|
||||
return &models.List{}
|
||||
return &models.Project{}
|
||||
},
|
||||
t: t,
|
||||
}
|
||||
testHandlerListAdmin := webHandlerTest{
|
||||
testHandlerProjectAdmin := webHandlerTest{
|
||||
linkShare: linkShareAdmin,
|
||||
strFunc: func() handler.CObject {
|
||||
return &models.List{}
|
||||
return &models.Project{}
|
||||
},
|
||||
t: t,
|
||||
}
|
||||
|
||||
t.Run("ReadAll", func(t *testing.T) {
|
||||
t.Run("Normal", func(t *testing.T) {
|
||||
rec, err := testHandlerListReadOnly.testReadAllWithLinkShare(nil, nil)
|
||||
rec, err := testHandlerProjectReadOnly.testReadAllWithLinkShare(nil, nil)
|
||||
assert.NoError(t, err)
|
||||
// Should only return the shared list, nothing else
|
||||
// Should only return the shared project, nothing else
|
||||
assert.Contains(t, rec.Body.String(), `Test1`)
|
||||
assert.NotContains(t, rec.Body.String(), `Test2`)
|
||||
assert.NotContains(t, rec.Body.String(), `Test3`)
|
||||
|
@ -168,9 +168,9 @@ func TestLinkSharing(t *testing.T) {
|
|||
assert.NotContains(t, rec.Body.String(), `Test5`)
|
||||
})
|
||||
t.Run("Search", func(t *testing.T) {
|
||||
rec, err := testHandlerListReadOnly.testReadAllWithLinkShare(url.Values{"s": []string{"est1"}}, nil)
|
||||
rec, err := testHandlerProjectReadOnly.testReadAllWithLinkShare(url.Values{"s": []string{"est1"}}, nil)
|
||||
assert.NoError(t, err)
|
||||
// Should only return the shared list, nothing else
|
||||
// Should only return the shared project, nothing else
|
||||
assert.Contains(t, rec.Body.String(), `Test1`)
|
||||
assert.NotContains(t, rec.Body.String(), `Test2`)
|
||||
assert.NotContains(t, rec.Body.String(), `Test3`)
|
||||
|
@ -180,35 +180,35 @@ func TestLinkSharing(t *testing.T) {
|
|||
})
|
||||
t.Run("ReadOne", func(t *testing.T) {
|
||||
t.Run("Normal", func(t *testing.T) {
|
||||
rec, err := testHandlerListReadOnly.testReadOneWithLinkShare(nil, map[string]string{"list": "1"})
|
||||
rec, err := testHandlerProjectReadOnly.testReadOneWithLinkShare(nil, map[string]string{"project": "1"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Test1"`)
|
||||
assert.NotContains(t, rec.Body.String(), `"title":"Test2"`)
|
||||
})
|
||||
t.Run("Nonexisting", func(t *testing.T) {
|
||||
_, err := testHandlerListReadOnly.testReadOneWithLinkShare(nil, map[string]string{"list": "9999999"})
|
||||
_, err := testHandlerProjectReadOnly.testReadOneWithLinkShare(nil, map[string]string{"project": "9999999"})
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeListDoesNotExist)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeProjectDoesNotExist)
|
||||
})
|
||||
t.Run("Rights check", func(t *testing.T) {
|
||||
t.Run("Forbidden", func(t *testing.T) {
|
||||
// List 2, not shared with this token
|
||||
_, err := testHandlerListReadOnly.testReadOneWithLinkShare(nil, map[string]string{"list": "2"})
|
||||
// Project 2, not shared with this token
|
||||
_, err := testHandlerProjectReadOnly.testReadOneWithLinkShare(nil, map[string]string{"project": "2"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `You don't have the right to see this`)
|
||||
})
|
||||
t.Run("Shared readonly", func(t *testing.T) {
|
||||
rec, err := testHandlerListReadOnly.testReadOneWithLinkShare(nil, map[string]string{"list": "1"})
|
||||
rec, err := testHandlerProjectReadOnly.testReadOneWithLinkShare(nil, map[string]string{"project": "1"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Test1"`)
|
||||
})
|
||||
t.Run("Shared write", func(t *testing.T) {
|
||||
rec, err := testHandlerListWrite.testReadOneWithLinkShare(nil, map[string]string{"list": "2"})
|
||||
rec, err := testHandlerProjectWrite.testReadOneWithLinkShare(nil, map[string]string{"project": "2"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Test2"`)
|
||||
})
|
||||
t.Run("Shared admin", func(t *testing.T) {
|
||||
rec, err := testHandlerListAdmin.testReadOneWithLinkShare(nil, map[string]string{"list": "3"})
|
||||
rec, err := testHandlerProjectAdmin.testReadOneWithLinkShare(nil, map[string]string{"project": "3"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Test3"`)
|
||||
})
|
||||
|
@ -216,28 +216,28 @@ func TestLinkSharing(t *testing.T) {
|
|||
})
|
||||
t.Run("Update", func(t *testing.T) {
|
||||
t.Run("Nonexisting", func(t *testing.T) {
|
||||
_, err := testHandlerListReadOnly.testUpdateWithLinkShare(nil, map[string]string{"list": "9999999"}, `{"title":"TestLoremIpsum"}`)
|
||||
_, err := testHandlerProjectReadOnly.testUpdateWithLinkShare(nil, map[string]string{"project": "9999999"}, `{"title":"TestLoremIpsum"}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeListDoesNotExist)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeProjectDoesNotExist)
|
||||
})
|
||||
t.Run("Rights check", func(t *testing.T) {
|
||||
t.Run("Forbidden", func(t *testing.T) {
|
||||
_, err := testHandlerListReadOnly.testUpdateWithLinkShare(nil, map[string]string{"list": "2"}, `{"title":"TestLoremIpsum"}`)
|
||||
_, err := testHandlerProjectReadOnly.testUpdateWithLinkShare(nil, map[string]string{"project": "2"}, `{"title":"TestLoremIpsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared readonly", func(t *testing.T) {
|
||||
_, err := testHandlerListReadOnly.testUpdateWithLinkShare(nil, map[string]string{"list": "1"}, `{"title":"TestLoremIpsum"}`)
|
||||
_, err := testHandlerProjectReadOnly.testUpdateWithLinkShare(nil, map[string]string{"project": "1"}, `{"title":"TestLoremIpsum"}`)
|
||||
assert.Error(t, err)
|
||||
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 := testHandlerProjectWrite.testUpdateWithLinkShare(nil, map[string]string{"project": "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 := testHandlerProjectAdmin.testUpdateWithLinkShare(nil, map[string]string{"project": "3"}, `{"title":"TestLoremIpsum","namespace_id":2}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
|
||||
})
|
||||
|
@ -245,54 +245,54 @@ func TestLinkSharing(t *testing.T) {
|
|||
})
|
||||
t.Run("Delete", func(t *testing.T) {
|
||||
t.Run("Nonexisting", func(t *testing.T) {
|
||||
_, err := testHandlerListReadOnly.testDeleteWithLinkShare(nil, map[string]string{"list": "9999999"})
|
||||
_, err := testHandlerProjectReadOnly.testDeleteWithLinkShare(nil, map[string]string{"project": "9999999"})
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeListDoesNotExist)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeProjectDoesNotExist)
|
||||
})
|
||||
t.Run("Rights check", func(t *testing.T) {
|
||||
t.Run("Forbidden", func(t *testing.T) {
|
||||
_, err := testHandlerListReadOnly.testDeleteWithLinkShare(nil, map[string]string{"list": "1"})
|
||||
_, err := testHandlerProjectReadOnly.testDeleteWithLinkShare(nil, map[string]string{"project": "1"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared readonly", func(t *testing.T) {
|
||||
_, err := testHandlerListReadOnly.testDeleteWithLinkShare(nil, map[string]string{"list": "1"})
|
||||
_, err := testHandlerProjectReadOnly.testDeleteWithLinkShare(nil, map[string]string{"project": "1"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared write", func(t *testing.T) {
|
||||
_, err := testHandlerListWrite.testDeleteWithLinkShare(nil, map[string]string{"list": "2"})
|
||||
_, err := testHandlerProjectWrite.testDeleteWithLinkShare(nil, map[string]string{"project": "2"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared admin", func(t *testing.T) {
|
||||
rec, err := testHandlerListAdmin.testDeleteWithLinkShare(nil, map[string]string{"list": "3"})
|
||||
rec, err := testHandlerProjectAdmin.testDeleteWithLinkShare(nil, map[string]string{"project": "3"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// Creating a list should always be forbidden, since users need access to a namespace to create a list
|
||||
// Creating a project should always be forbidden, since users need access to a namespace to create a project
|
||||
t.Run("Create", func(t *testing.T) {
|
||||
t.Run("Nonexisting", func(t *testing.T) {
|
||||
_, err := testHandlerListReadOnly.testCreateWithLinkShare(nil, map[string]string{"namespace": "999999"}, `{"title":"Lorem"}`)
|
||||
_, err := testHandlerProjectReadOnly.testCreateWithLinkShare(nil, map[string]string{"namespace": "999999"}, `{"title":"Lorem"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Rights check", func(t *testing.T) {
|
||||
t.Run("Shared readonly", func(t *testing.T) {
|
||||
_, err := testHandlerListReadOnly.testCreateWithLinkShare(nil, map[string]string{"namespace": "1"}, `{"title":"Lorem","description":"Lorem Ipsum"}`)
|
||||
_, err := testHandlerProjectReadOnly.testCreateWithLinkShare(nil, map[string]string{"namespace": "1"}, `{"title":"Lorem","description":"Lorem Ipsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared write", func(t *testing.T) {
|
||||
_, err := testHandlerListWrite.testCreateWithLinkShare(nil, map[string]string{"namespace": "2"}, `{"title":"Lorem","description":"Lorem Ipsum"}`)
|
||||
_, err := testHandlerProjectWrite.testCreateWithLinkShare(nil, map[string]string{"namespace": "2"}, `{"title":"Lorem","description":"Lorem Ipsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared admin", func(t *testing.T) {
|
||||
_, err := testHandlerListAdmin.testCreateWithLinkShare(nil, map[string]string{"namespace": "3"}, `{"title":"Lorem","description":"Lorem Ipsum"}`)
|
||||
_, err := testHandlerProjectAdmin.testCreateWithLinkShare(nil, map[string]string{"namespace": "3"}, `{"title":"Lorem","description":"Lorem Ipsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
|
@ -301,74 +301,74 @@ func TestLinkSharing(t *testing.T) {
|
|||
|
||||
t.Run("Right Management", func(t *testing.T) {
|
||||
t.Run("Users", func(t *testing.T) {
|
||||
testHandlerListUserReadOnly := webHandlerTest{
|
||||
testHandlerProjectUserReadOnly := webHandlerTest{
|
||||
linkShare: linkshareRead,
|
||||
strFunc: func() handler.CObject {
|
||||
return &models.ListUser{}
|
||||
return &models.ProjectUser{}
|
||||
},
|
||||
t: t,
|
||||
}
|
||||
testHandlerListUserWrite := webHandlerTest{
|
||||
testHandlerProjectUserWrite := webHandlerTest{
|
||||
linkShare: linkShareWrite,
|
||||
strFunc: func() handler.CObject {
|
||||
return &models.ListUser{}
|
||||
return &models.ProjectUser{}
|
||||
},
|
||||
t: t,
|
||||
}
|
||||
testHandlerListUserAdmin := webHandlerTest{
|
||||
testHandlerProjectUserAdmin := webHandlerTest{
|
||||
linkShare: linkShareAdmin,
|
||||
strFunc: func() handler.CObject {
|
||||
return &models.ListUser{}
|
||||
return &models.ProjectUser{}
|
||||
},
|
||||
t: t,
|
||||
}
|
||||
t.Run("ReadAll", func(t *testing.T) {
|
||||
t.Run("Shared readonly", func(t *testing.T) {
|
||||
rec, err := testHandlerListUserReadOnly.testReadAllWithLinkShare(nil, map[string]string{"list": "1"})
|
||||
rec, err := testHandlerProjectUserReadOnly.testReadAllWithLinkShare(nil, map[string]string{"project": "1"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `[]`)
|
||||
})
|
||||
t.Run("Shared write", func(t *testing.T) {
|
||||
rec, err := testHandlerListUserWrite.testReadAllWithLinkShare(nil, map[string]string{"list": "2"})
|
||||
rec, err := testHandlerProjectUserWrite.testReadAllWithLinkShare(nil, map[string]string{"project": "2"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `[]`)
|
||||
})
|
||||
t.Run("Shared admin", func(t *testing.T) {
|
||||
rec, err := testHandlerListUserAdmin.testReadAllWithLinkShare(nil, map[string]string{"list": "3"})
|
||||
rec, err := testHandlerProjectUserAdmin.testReadAllWithLinkShare(nil, map[string]string{"project": "3"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"username":"user1"`)
|
||||
})
|
||||
})
|
||||
t.Run("Create", func(t *testing.T) {
|
||||
t.Run("Shared readonly", func(t *testing.T) {
|
||||
_, err := testHandlerListUserReadOnly.testCreateWithLinkShare(nil, map[string]string{"list": "1"}, `{"user_id":"user1"}`)
|
||||
_, err := testHandlerProjectUserReadOnly.testCreateWithLinkShare(nil, map[string]string{"project": "1"}, `{"user_id":"user1"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared write", func(t *testing.T) {
|
||||
_, err := testHandlerListUserWrite.testCreateWithLinkShare(nil, map[string]string{"list": "2"}, `{"user_id":"user1"}`)
|
||||
_, err := testHandlerProjectUserWrite.testCreateWithLinkShare(nil, map[string]string{"project": "2"}, `{"user_id":"user1"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared admin", func(t *testing.T) {
|
||||
_, err := testHandlerListUserAdmin.testCreateWithLinkShare(nil, map[string]string{"list": "3"}, `{"user_id":"user1"}`)
|
||||
_, err := testHandlerProjectUserAdmin.testCreateWithLinkShare(nil, map[string]string{"project": "3"}, `{"user_id":"user1"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
})
|
||||
t.Run("Update", func(t *testing.T) {
|
||||
t.Run("Shared readonly", func(t *testing.T) {
|
||||
_, err := testHandlerListUserReadOnly.testUpdateWithLinkShare(nil, map[string]string{"list": "1"}, `{"user_id":"user1"}`)
|
||||
_, err := testHandlerProjectUserReadOnly.testUpdateWithLinkShare(nil, map[string]string{"project": "1"}, `{"user_id":"user1"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared write", func(t *testing.T) {
|
||||
_, err := testHandlerListUserWrite.testUpdateWithLinkShare(nil, map[string]string{"list": "2"}, `{"user_id":"user1"}`)
|
||||
_, err := testHandlerProjectUserWrite.testUpdateWithLinkShare(nil, map[string]string{"project": "2"}, `{"user_id":"user1"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared admin", func(t *testing.T) {
|
||||
_, err := testHandlerListUserAdmin.testUpdateWithLinkShare(nil, map[string]string{"list": "3"}, `{"user_id":"user1"}`)
|
||||
_, err := testHandlerProjectUserAdmin.testUpdateWithLinkShare(nil, map[string]string{"project": "3"}, `{"user_id":"user1"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
|
@ -376,91 +376,91 @@ func TestLinkSharing(t *testing.T) {
|
|||
})
|
||||
t.Run("Delete", func(t *testing.T) {
|
||||
t.Run("Shared readonly", func(t *testing.T) {
|
||||
_, err := testHandlerListUserReadOnly.testDeleteWithLinkShare(nil, map[string]string{"list": "1"})
|
||||
_, err := testHandlerProjectUserReadOnly.testDeleteWithLinkShare(nil, map[string]string{"project": "1"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared write", func(t *testing.T) {
|
||||
_, err := testHandlerListUserWrite.testDeleteWithLinkShare(nil, map[string]string{"list": "2"})
|
||||
_, err := testHandlerProjectUserWrite.testDeleteWithLinkShare(nil, map[string]string{"project": "2"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared admin", func(t *testing.T) {
|
||||
_, err := testHandlerListUserAdmin.testDeleteWithLinkShare(nil, map[string]string{"list": "3"})
|
||||
_, err := testHandlerProjectUserAdmin.testDeleteWithLinkShare(nil, map[string]string{"project": "3"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
})
|
||||
})
|
||||
t.Run("Teams", func(t *testing.T) {
|
||||
testHandlerListTeamReadOnly := webHandlerTest{
|
||||
testHandlerProjectTeamReadOnly := webHandlerTest{
|
||||
linkShare: linkshareRead,
|
||||
strFunc: func() handler.CObject {
|
||||
return &models.TeamList{}
|
||||
return &models.TeamProject{}
|
||||
},
|
||||
t: t,
|
||||
}
|
||||
testHandlerListTeamWrite := webHandlerTest{
|
||||
testHandlerProjectTeamWrite := webHandlerTest{
|
||||
linkShare: linkShareWrite,
|
||||
strFunc: func() handler.CObject {
|
||||
return &models.TeamList{}
|
||||
return &models.TeamProject{}
|
||||
},
|
||||
t: t,
|
||||
}
|
||||
testHandlerListTeamAdmin := webHandlerTest{
|
||||
testHandlerProjectTeamAdmin := webHandlerTest{
|
||||
linkShare: linkShareAdmin,
|
||||
strFunc: func() handler.CObject {
|
||||
return &models.TeamList{}
|
||||
return &models.TeamProject{}
|
||||
},
|
||||
t: t,
|
||||
}
|
||||
t.Run("ReadAll", func(t *testing.T) {
|
||||
t.Run("Shared readonly", func(t *testing.T) {
|
||||
rec, err := testHandlerListTeamReadOnly.testReadAllWithLinkShare(nil, map[string]string{"list": "1"})
|
||||
rec, err := testHandlerProjectTeamReadOnly.testReadAllWithLinkShare(nil, map[string]string{"project": "1"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `[]`)
|
||||
})
|
||||
t.Run("Shared write", func(t *testing.T) {
|
||||
rec, err := testHandlerListTeamWrite.testReadAllWithLinkShare(nil, map[string]string{"list": "2"})
|
||||
rec, err := testHandlerProjectTeamWrite.testReadAllWithLinkShare(nil, map[string]string{"project": "2"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `[]`)
|
||||
})
|
||||
t.Run("Shared admin", func(t *testing.T) {
|
||||
rec, err := testHandlerListTeamAdmin.testReadAllWithLinkShare(nil, map[string]string{"list": "3"})
|
||||
rec, err := testHandlerProjectTeamAdmin.testReadAllWithLinkShare(nil, map[string]string{"project": "3"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"name":"testteam1"`)
|
||||
})
|
||||
})
|
||||
t.Run("Create", func(t *testing.T) {
|
||||
t.Run("Shared readonly", func(t *testing.T) {
|
||||
_, err := testHandlerListTeamReadOnly.testCreateWithLinkShare(nil, map[string]string{"list": "1"}, `{"team_id":1}`)
|
||||
_, err := testHandlerProjectTeamReadOnly.testCreateWithLinkShare(nil, map[string]string{"project": "1"}, `{"team_id":1}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared write", func(t *testing.T) {
|
||||
_, err := testHandlerListTeamWrite.testCreateWithLinkShare(nil, map[string]string{"list": "2"}, `{"team_id":1}`)
|
||||
_, err := testHandlerProjectTeamWrite.testCreateWithLinkShare(nil, map[string]string{"project": "2"}, `{"team_id":1}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared admin", func(t *testing.T) {
|
||||
_, err := testHandlerListTeamAdmin.testCreateWithLinkShare(nil, map[string]string{"list": "3"}, `{"team_id":1}`)
|
||||
_, err := testHandlerProjectTeamAdmin.testCreateWithLinkShare(nil, map[string]string{"project": "3"}, `{"team_id":1}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
})
|
||||
t.Run("Update", func(t *testing.T) {
|
||||
t.Run("Shared readonly", func(t *testing.T) {
|
||||
_, err := testHandlerListTeamReadOnly.testUpdateWithLinkShare(nil, map[string]string{"list": "1"}, `{"team_id":1}`)
|
||||
_, err := testHandlerProjectTeamReadOnly.testUpdateWithLinkShare(nil, map[string]string{"project": "1"}, `{"team_id":1}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared write", func(t *testing.T) {
|
||||
_, err := testHandlerListTeamWrite.testUpdateWithLinkShare(nil, map[string]string{"list": "2"}, `{"team_id":1}`)
|
||||
_, err := testHandlerProjectTeamWrite.testUpdateWithLinkShare(nil, map[string]string{"project": "2"}, `{"team_id":1}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared admin", func(t *testing.T) {
|
||||
_, err := testHandlerListTeamAdmin.testUpdateWithLinkShare(nil, map[string]string{"list": "3"}, `{"team_id":1}`)
|
||||
_, err := testHandlerProjectTeamAdmin.testUpdateWithLinkShare(nil, map[string]string{"project": "3"}, `{"team_id":1}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
|
@ -468,17 +468,17 @@ func TestLinkSharing(t *testing.T) {
|
|||
})
|
||||
t.Run("Delete", func(t *testing.T) {
|
||||
t.Run("Shared readonly", func(t *testing.T) {
|
||||
_, err := testHandlerListTeamReadOnly.testDeleteWithLinkShare(nil, map[string]string{"list": "1"})
|
||||
_, err := testHandlerProjectTeamReadOnly.testDeleteWithLinkShare(nil, map[string]string{"project": "1"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared write", func(t *testing.T) {
|
||||
_, err := testHandlerListTeamWrite.testDeleteWithLinkShare(nil, map[string]string{"list": "2"})
|
||||
_, err := testHandlerProjectTeamWrite.testDeleteWithLinkShare(nil, map[string]string{"project": "2"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared admin", func(t *testing.T) {
|
||||
_, err := testHandlerListTeamAdmin.testDeleteWithLinkShare(nil, map[string]string{"list": "3"})
|
||||
_, err := testHandlerProjectTeamAdmin.testDeleteWithLinkShare(nil, map[string]string{"project": "3"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
|
@ -586,34 +586,34 @@ func TestLinkSharing(t *testing.T) {
|
|||
})
|
||||
t.Run("Create", func(t *testing.T) {
|
||||
t.Run("Shared readonly", func(t *testing.T) {
|
||||
_, err := testHandlerTaskReadOnly.testCreateWithLinkShare(nil, map[string]string{"list": "1"}, `{"title":"Lorem Ipsum"}`)
|
||||
_, err := testHandlerTaskReadOnly.testCreateWithLinkShare(nil, map[string]string{"project": "1"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared write", func(t *testing.T) {
|
||||
rec, err := testHandlerTaskWrite.testCreateWithLinkShare(nil, map[string]string{"list": "2"}, `{"title":"Lorem Ipsum"}`)
|
||||
rec, err := testHandlerTaskWrite.testCreateWithLinkShare(nil, map[string]string{"project": "2"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
|
||||
})
|
||||
t.Run("Shared admin", func(t *testing.T) {
|
||||
rec, err := testHandlerTaskAdmin.testCreateWithLinkShare(nil, map[string]string{"list": "3"}, `{"title":"Lorem Ipsum"}`)
|
||||
rec, err := testHandlerTaskAdmin.testCreateWithLinkShare(nil, map[string]string{"project": "3"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
|
||||
})
|
||||
})
|
||||
t.Run("Update", func(t *testing.T) {
|
||||
t.Run("Shared readonly", func(t *testing.T) {
|
||||
_, err := testHandlerTaskReadOnly.testUpdateWithLinkShare(nil, map[string]string{"listtask": "1"}, `{"title":"Lorem Ipsum"}`)
|
||||
_, err := testHandlerTaskReadOnly.testUpdateWithLinkShare(nil, map[string]string{"projecttask": "1"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared write", func(t *testing.T) {
|
||||
rec, err := testHandlerTaskWrite.testUpdateWithLinkShare(nil, map[string]string{"listtask": "13"}, `{"title":"Lorem Ipsum"}`)
|
||||
rec, err := testHandlerTaskWrite.testUpdateWithLinkShare(nil, map[string]string{"projecttask": "13"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
|
||||
})
|
||||
t.Run("Shared admin", func(t *testing.T) {
|
||||
rec, err := testHandlerTaskAdmin.testUpdateWithLinkShare(nil, map[string]string{"listtask": "32"}, `{"title":"Lorem Ipsum"}`)
|
||||
rec, err := testHandlerTaskAdmin.testUpdateWithLinkShare(nil, map[string]string{"projecttask": "32"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
|
||||
})
|
||||
|
@ -621,17 +621,17 @@ func TestLinkSharing(t *testing.T) {
|
|||
})
|
||||
t.Run("Delete", func(t *testing.T) {
|
||||
t.Run("Shared readonly", func(t *testing.T) {
|
||||
_, err := testHandlerTaskReadOnly.testDeleteWithLinkShare(nil, map[string]string{"listtask": "1"})
|
||||
_, err := testHandlerTaskReadOnly.testDeleteWithLinkShare(nil, map[string]string{"projecttask": "1"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared write", func(t *testing.T) {
|
||||
rec, err := testHandlerTaskWrite.testDeleteWithLinkShare(nil, map[string]string{"listtask": "13"})
|
||||
rec, err := testHandlerTaskWrite.testDeleteWithLinkShare(nil, map[string]string{"projecttask": "13"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
|
||||
})
|
||||
t.Run("Shared admin", func(t *testing.T) {
|
||||
rec, err := testHandlerTaskAdmin.testDeleteWithLinkShare(nil, map[string]string{"listtask": "32"})
|
||||
rec, err := testHandlerTaskAdmin.testDeleteWithLinkShare(nil, map[string]string{"projecttask": "32"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
|
||||
})
|
||||
|
@ -738,34 +738,34 @@ func TestLinkSharing(t *testing.T) {
|
|||
}
|
||||
t.Run("ReadAll", func(t *testing.T) {
|
||||
t.Run("Shared readonly", func(t *testing.T) {
|
||||
rec, err := testHandlerLinkShareReadOnly.testReadAllWithLinkShare(nil, map[string]string{"list": "1"})
|
||||
rec, err := testHandlerLinkShareReadOnly.testReadAllWithLinkShare(nil, map[string]string{"project": "1"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"hash":"test"`)
|
||||
})
|
||||
t.Run("Shared write", func(t *testing.T) {
|
||||
rec, err := testHandlerLinkShareWrite.testReadAllWithLinkShare(nil, map[string]string{"list": "2"})
|
||||
rec, err := testHandlerLinkShareWrite.testReadAllWithLinkShare(nil, map[string]string{"project": "2"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"hash":"test2"`)
|
||||
})
|
||||
t.Run("Shared admin", func(t *testing.T) {
|
||||
rec, err := testHandlerLinkShareAdmin.testReadAllWithLinkShare(nil, map[string]string{"list": "3"})
|
||||
rec, err := testHandlerLinkShareAdmin.testReadAllWithLinkShare(nil, map[string]string{"project": "3"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"hash":"test3"`)
|
||||
})
|
||||
})
|
||||
t.Run("Create", func(t *testing.T) {
|
||||
t.Run("Shared readonly", func(t *testing.T) {
|
||||
_, err := testHandlerLinkShareReadOnly.testCreateWithLinkShare(nil, map[string]string{"list": "1"}, `{}`)
|
||||
_, err := testHandlerLinkShareReadOnly.testCreateWithLinkShare(nil, map[string]string{"project": "1"}, `{}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared write", func(t *testing.T) {
|
||||
_, err := testHandlerLinkShareWrite.testCreateWithLinkShare(nil, map[string]string{"list": "2"}, `{}`)
|
||||
_, err := testHandlerLinkShareWrite.testCreateWithLinkShare(nil, map[string]string{"project": "2"}, `{}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared admin", func(t *testing.T) {
|
||||
_, err := testHandlerLinkShareAdmin.testCreateWithLinkShare(nil, map[string]string{"list": "3"}, `{}`)
|
||||
_, err := testHandlerLinkShareAdmin.testCreateWithLinkShare(nil, map[string]string{"project": "3"}, `{}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
|
|
|
@ -26,11 +26,11 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
func TestProject(t *testing.T) {
|
||||
testHandler := webHandlerTest{
|
||||
user: &testuser1,
|
||||
strFunc: func() handler.CObject {
|
||||
return &models.List{}
|
||||
return &models.Project{}
|
||||
},
|
||||
t: t,
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ func TestList(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `Test1`)
|
||||
assert.NotContains(t, rec.Body.String(), `Test2"`)
|
||||
assert.Contains(t, rec.Body.String(), `Test3`) // Shared directly via users_list
|
||||
assert.Contains(t, rec.Body.String(), `Test3`) // Shared directly via users_project
|
||||
assert.Contains(t, rec.Body.String(), `Test4`) // Shared via namespace
|
||||
assert.NotContains(t, rec.Body.String(), `Test5`)
|
||||
assert.NotContains(t, rec.Body.String(), `Test21`) // Archived through namespace
|
||||
|
@ -55,12 +55,12 @@ func TestList(t *testing.T) {
|
|||
assert.NotContains(t, rec.Body.String(), `Test4`)
|
||||
assert.NotContains(t, rec.Body.String(), `Test5`)
|
||||
})
|
||||
t.Run("Normal with archived lists", func(t *testing.T) {
|
||||
t.Run("Normal with archived projects", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(url.Values{"is_archived": []string{"true"}}, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `Test1`)
|
||||
assert.NotContains(t, rec.Body.String(), `Test2"`)
|
||||
assert.Contains(t, rec.Body.String(), `Test3`) // Shared directly via users_list
|
||||
assert.Contains(t, rec.Body.String(), `Test3`) // Shared directly via users_project
|
||||
assert.Contains(t, rec.Body.String(), `Test4`) // Shared via namespace
|
||||
assert.NotContains(t, rec.Body.String(), `Test5`)
|
||||
assert.Contains(t, rec.Body.String(), `Test21`) // Archived through namespace
|
||||
|
@ -69,7 +69,7 @@ func TestList(t *testing.T) {
|
|||
})
|
||||
t.Run("ReadOne", func(t *testing.T) {
|
||||
t.Run("Normal", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "1"})
|
||||
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "1"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Test1"`)
|
||||
assert.NotContains(t, rec.Body.String(), `"title":"Test2"`)
|
||||
|
@ -79,89 +79,89 @@ func TestList(t *testing.T) {
|
|||
assert.Equal(t, "2", rec.Result().Header.Get("x-max-right")) // User 1 is owner so they should have admin rights.
|
||||
})
|
||||
t.Run("Nonexisting", func(t *testing.T) {
|
||||
_, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "9999"})
|
||||
_, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "9999"})
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeListDoesNotExist)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeProjectDoesNotExist)
|
||||
})
|
||||
t.Run("Rights check", func(t *testing.T) {
|
||||
t.Run("Forbidden", func(t *testing.T) {
|
||||
// Owned by user13
|
||||
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "20"})
|
||||
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "20"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `You don't have the right to see this`)
|
||||
assert.Empty(t, rec.Result().Header.Get("x-max-rights"))
|
||||
})
|
||||
t.Run("Shared Via Team readonly", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "6"})
|
||||
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "6"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Test6"`)
|
||||
assert.Equal(t, "0", rec.Result().Header.Get("x-max-right"))
|
||||
})
|
||||
t.Run("Shared Via Team write", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "7"})
|
||||
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "7"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Test7"`)
|
||||
assert.Equal(t, "1", rec.Result().Header.Get("x-max-right"))
|
||||
})
|
||||
t.Run("Shared Via Team admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "8"})
|
||||
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "8"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Test8"`)
|
||||
assert.Equal(t, "2", rec.Result().Header.Get("x-max-right"))
|
||||
})
|
||||
|
||||
t.Run("Shared Via User readonly", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "9"})
|
||||
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "9"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Test9"`)
|
||||
assert.Equal(t, "0", rec.Result().Header.Get("x-max-right"))
|
||||
})
|
||||
t.Run("Shared Via User write", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "10"})
|
||||
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "10"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Test10"`)
|
||||
assert.Equal(t, "1", rec.Result().Header.Get("x-max-right"))
|
||||
})
|
||||
t.Run("Shared Via User admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "11"})
|
||||
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "11"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Test11"`)
|
||||
assert.Equal(t, "2", rec.Result().Header.Get("x-max-right"))
|
||||
})
|
||||
|
||||
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "12"})
|
||||
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "12"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Test12"`)
|
||||
assert.Equal(t, "0", rec.Result().Header.Get("x-max-right"))
|
||||
})
|
||||
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "13"})
|
||||
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "13"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Test13"`)
|
||||
assert.Equal(t, "1", rec.Result().Header.Get("x-max-right"))
|
||||
})
|
||||
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "14"})
|
||||
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "14"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Test14"`)
|
||||
assert.Equal(t, "2", rec.Result().Header.Get("x-max-right"))
|
||||
})
|
||||
|
||||
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "15"})
|
||||
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "15"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Test15"`)
|
||||
assert.Equal(t, "0", rec.Result().Header.Get("x-max-right"))
|
||||
})
|
||||
t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "16"})
|
||||
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "16"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Test16"`)
|
||||
assert.Equal(t, "1", rec.Result().Header.Get("x-max-right"))
|
||||
})
|
||||
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "17"})
|
||||
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"project": "17"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Test17"`)
|
||||
assert.Equal(t, "2", rec.Result().Header.Get("x-max-right"))
|
||||
|
@ -170,101 +170,101 @@ func TestList(t *testing.T) {
|
|||
})
|
||||
t.Run("Update", func(t *testing.T) {
|
||||
t.Run("Normal", func(t *testing.T) {
|
||||
// Check the list was loaded successfully afterwards, see testReadOneWithUser
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "1"}, `{"title":"TestLoremIpsum"}`)
|
||||
// Check the project was loaded successfully afterwards, see testReadOneWithUser
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "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
|
||||
assert.Contains(t, rec.Body.String(), `description":"Lorem Ipsum`)
|
||||
})
|
||||
t.Run("Nonexisting", func(t *testing.T) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "9999"}, `{"title":"TestLoremIpsum"}`)
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "9999"}, `{"title":"TestLoremIpsum"}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeListDoesNotExist)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeProjectDoesNotExist)
|
||||
})
|
||||
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{"project": "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`)
|
||||
})
|
||||
t.Run("Empty title", func(t *testing.T) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "1"}, `{"title":""}`)
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "1"}, `{"title":""}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message.(models.ValidationHTTPError).InvalidFields, "title: non zero value required")
|
||||
})
|
||||
t.Run("Title too long", func(t *testing.T) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "1"}, `{"title":"Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea taki"}`)
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "1"}, `{"title":"Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea taki"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message.(models.ValidationHTTPError).InvalidFields[0], "does not validate as runelength(1|250)")
|
||||
})
|
||||
t.Run("Rights check", func(t *testing.T) {
|
||||
t.Run("Forbidden", func(t *testing.T) {
|
||||
// Owned by user13
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "20"}, `{"title":"TestLoremIpsum"}`)
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "20"}, `{"title":"TestLoremIpsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via Team readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "6"}, `{"title":"TestLoremIpsum"}`)
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "6"}, `{"title":"TestLoremIpsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via Team write", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "7"}, `{"title":"TestLoremIpsum"}`)
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "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{"project": "8"}, `{"title":"TestLoremIpsum","namespace_id":6}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
|
||||
})
|
||||
|
||||
t.Run("Shared Via User readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "9"}, `{"title":"TestLoremIpsum"}`)
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "9"}, `{"title":"TestLoremIpsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via User write", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "10"}, `{"title":"TestLoremIpsum"}`)
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "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{"project": "11"}, `{"title":"TestLoremIpsum","namespace_id":6}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
|
||||
})
|
||||
|
||||
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "12"}, `{"title":"TestLoremIpsum"}`)
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "12"}, `{"title":"TestLoremIpsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "13"}, `{"title":"TestLoremIpsum"}`)
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "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{"project": "14"}, `{"title":"TestLoremIpsum","namespace_id":9}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
|
||||
})
|
||||
|
||||
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "15"}, `{"title":"TestLoremIpsum"}`)
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "15"}, `{"title":"TestLoremIpsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "16"}, `{"title":"TestLoremIpsum"}`)
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"project": "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{"project": "17"}, `{"title":"TestLoremIpsum","namespace_id":12}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
|
||||
})
|
||||
|
@ -272,82 +272,82 @@ func TestList(t *testing.T) {
|
|||
})
|
||||
t.Run("Delete", func(t *testing.T) {
|
||||
t.Run("Normal", func(t *testing.T) {
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "1"})
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "1"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
|
||||
})
|
||||
t.Run("Nonexisting", func(t *testing.T) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "999"})
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "999"})
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeListDoesNotExist)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeProjectDoesNotExist)
|
||||
})
|
||||
t.Run("Rights check", func(t *testing.T) {
|
||||
t.Run("Forbidden", func(t *testing.T) {
|
||||
// Owned by user13
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "20"})
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "20"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via Team readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "6"})
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "6"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via Team write", func(t *testing.T) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "7"})
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "7"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via Team admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "8"})
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "8"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
|
||||
})
|
||||
|
||||
t.Run("Shared Via User readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "9"})
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "9"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via User write", func(t *testing.T) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "10"})
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "10"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via User admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "11"})
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "11"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
|
||||
})
|
||||
|
||||
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "12"})
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "12"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "13"})
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "13"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "14"})
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "14"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
|
||||
})
|
||||
|
||||
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "15"})
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "15"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "16"})
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "16"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "17"})
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"project": "17"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
|
||||
})
|
||||
|
@ -355,7 +355,7 @@ func TestList(t *testing.T) {
|
|||
})
|
||||
t.Run("Create", func(t *testing.T) {
|
||||
t.Run("Normal", func(t *testing.T) {
|
||||
// Check the list was loaded successfully after update, see testReadOneWithUser
|
||||
// Check the project was loaded successfully after update, see testReadOneWithUser
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"namespace": "1"}, `{"title":"Lorem"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Lorem"`)
|
|
@ -33,9 +33,9 @@ func TestTaskCollection(t *testing.T) {
|
|||
},
|
||||
t: t,
|
||||
}
|
||||
t.Run("ReadAll on list", func(t *testing.T) {
|
||||
t.Run("ReadAll on project", func(t *testing.T) {
|
||||
|
||||
urlParams := map[string]string{"list": "1"}
|
||||
urlParams := map[string]string{"project": "1"}
|
||||
|
||||
t.Run("Normal", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(nil, urlParams)
|
||||
|
@ -113,49 +113,49 @@ func TestTaskCollection(t *testing.T) {
|
|||
t.Run("by priority", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}}, urlParams)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`)
|
||||
assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`)
|
||||
})
|
||||
t.Run("by priority desc", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"desc"}}, urlParams)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `[{"id":3,"title":"task #3 high prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":4,"title":"task #4 low prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":1`)
|
||||
assert.Contains(t, rec.Body.String(), `[{"id":3,"title":"task #3 high prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":4,"title":"task #4 low prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":1`)
|
||||
})
|
||||
t.Run("by priority asc", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"asc"}}, urlParams)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`)
|
||||
assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`)
|
||||
})
|
||||
// should equal duedate asc
|
||||
t.Run("by due_date", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}}, urlParams)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
|
||||
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
|
||||
})
|
||||
t.Run("by duedate desc", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"desc"}}, urlParams)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":6,"title":"task #6 lower due date`)
|
||||
assert.Contains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":6,"title":"task #6 lower due date`)
|
||||
})
|
||||
// Due date without unix suffix
|
||||
t.Run("by duedate asc without suffix", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"asc"}}, urlParams)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
|
||||
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
|
||||
})
|
||||
t.Run("by due_date without suffix", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}}, urlParams)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
|
||||
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
|
||||
})
|
||||
t.Run("by duedate desc without suffix", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"desc"}}, urlParams)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":6,"title":"task #6 lower due date`)
|
||||
assert.Contains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":6,"title":"task #6 lower due date`)
|
||||
})
|
||||
t.Run("by duedate asc", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"asc"}}, urlParams)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
|
||||
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
|
||||
})
|
||||
t.Run("invalid sort parameter", func(t *testing.T) {
|
||||
_, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"loremipsum"}}, urlParams)
|
||||
|
@ -287,7 +287,7 @@ func TestTaskCollection(t *testing.T) {
|
|||
t.Run("date range", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(
|
||||
nil,
|
||||
map[string]string{"list": "-2"}, // Actually a saved filter - contains the same filter arguments as the start and end date filter from above
|
||||
map[string]string{"project": "-2"}, // Actually a saved filter - contains the same filter arguments as the start and end date filter from above
|
||||
)
|
||||
assert.NoError(t, err)
|
||||
assert.NotContains(t, rec.Body.String(), `task #1`)
|
||||
|
@ -341,7 +341,7 @@ func TestTaskCollection(t *testing.T) {
|
|||
assert.Contains(t, rec.Body.String(), `task #24`) // Shared via namespace user readonly
|
||||
assert.Contains(t, rec.Body.String(), `task #25`) // Shared via namespace user write
|
||||
assert.Contains(t, rec.Body.String(), `task #26`) // Shared via namespace user admin
|
||||
// TODO: Add some cases where the user has access to the list, somhow shared
|
||||
// TODO: Add some cases where the user has access to the project, somhow shared
|
||||
})
|
||||
t.Run("Search", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(url.Values{"s": []string{"task #6"}}, nil)
|
||||
|
@ -366,33 +366,33 @@ func TestTaskCollection(t *testing.T) {
|
|||
t.Run("by priority", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}}, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`)
|
||||
assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`)
|
||||
})
|
||||
t.Run("by priority desc", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"desc"}}, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `[{"id":3,"title":"task #3 high prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":4,"title":"task #4 low prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":1`)
|
||||
assert.Contains(t, rec.Body.String(), `[{"id":3,"title":"task #3 high prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":100,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-3","index":3,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":4,"title":"task #4 low prio","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":1`)
|
||||
})
|
||||
t.Run("by priority asc", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"priority"}, "order_by": []string{"asc"}}, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`)
|
||||
assert.Contains(t, rec.Body.String(), `{"id":33,"title":"task #33 with percent done","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"0001-01-01T00:00:00Z","reminder_dates":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0.5,"identifier":"test1-17","index":17,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":1,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}]`)
|
||||
})
|
||||
// should equal duedate asc
|
||||
t.Run("by due_date", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}}, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
|
||||
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
|
||||
})
|
||||
t.Run("by duedate desc", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"desc"}}, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":6,"title":"task #6 lower due date`)
|
||||
assert.Contains(t, rec.Body.String(), `[{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":6,"title":"task #6 lower due date`)
|
||||
})
|
||||
t.Run("by duedate asc", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(url.Values{"sort_by": []string{"due_date"}, "order_by": []string{"asc"}}, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"list_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
|
||||
assert.Contains(t, rec.Body.String(), `[{"id":6,"title":"task #6 lower due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-11-30T22:25:24Z","reminder_dates":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-6","index":6,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":3,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}},{"id":5,"title":"task #5 higher due date","description":"","done":false,"done_at":"0001-01-01T00:00:00Z","due_date":"2018-12-01T03:58:44Z","reminder_dates":null,"project_id":1,"repeat_after":0,"repeat_mode":0,"priority":0,"start_date":"0001-01-01T00:00:00Z","end_date":"0001-01-01T00:00:00Z","assignees":null,"labels":null,"hex_color":"","percent_done":0,"identifier":"test1-5","index":5,"related_tasks":{},"attachments":null,"cover_image_attachment_id":0,"is_favorite":false,"created":"2018-12-01T01:12:04Z","updated":"2018-12-01T01:12:04Z","bucket_id":2,"position":0,"kanban_position":0,"created_by":{"id":1,"name":"","username":"user1","created":"2018-12-01T15:13:12Z","updated":"2018-12-02T15:13:12Z"}}`)
|
||||
})
|
||||
t.Run("invalid parameter", func(t *testing.T) {
|
||||
// Invalid parameter should not sort at all
|
||||
|
|
|
@ -39,7 +39,7 @@ func TestTaskComments(t *testing.T) {
|
|||
linkShare: &models.LinkSharing{
|
||||
ID: 2,
|
||||
Hash: "test2",
|
||||
ListID: 2,
|
||||
ProjectID: 2,
|
||||
Right: models.RightWrite,
|
||||
SharingType: models.SharingTypeWithoutPassword,
|
||||
SharedByID: 1,
|
||||
|
|
|
@ -39,7 +39,7 @@ func TestTask(t *testing.T) {
|
|||
linkShare: &models.LinkSharing{
|
||||
ID: 2,
|
||||
Hash: "test2",
|
||||
ListID: 2,
|
||||
ProjectID: 2,
|
||||
Right: models.RightWrite,
|
||||
SharingType: models.SharingTypeWithoutPassword,
|
||||
SharedByID: 1,
|
||||
|
@ -54,157 +54,157 @@ func TestTask(t *testing.T) {
|
|||
t.Run("Update", func(t *testing.T) {
|
||||
t.Run("Update task items", func(t *testing.T) {
|
||||
t.Run("Title", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"title":"Lorem Ipsum"}`)
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
|
||||
assert.NotContains(t, rec.Body.String(), `"title":"task #1"`)
|
||||
})
|
||||
t.Run("Description", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"description":"Dolor sit amet"}`)
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"description":"Dolor sit amet"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"description":"Dolor sit amet"`)
|
||||
assert.NotContains(t, rec.Body.String(), `"description":"Lorem Ipsum"`)
|
||||
})
|
||||
t.Run("Description to empty", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"description":""}`)
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"description":""}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"description":""`)
|
||||
assert.NotContains(t, rec.Body.String(), `"description":"Lorem Ipsum"`)
|
||||
})
|
||||
t.Run("Done", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"done":true}`)
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"done":true}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"done":true`)
|
||||
assert.NotContains(t, rec.Body.String(), `"done":false`)
|
||||
})
|
||||
t.Run("Undone", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "2"}, `{"done":false}`)
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "2"}, `{"done":false}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"done":false`)
|
||||
assert.NotContains(t, rec.Body.String(), `"done":true`)
|
||||
})
|
||||
t.Run("Due date", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"due_date": "2020-02-10T10:00:00Z"}`)
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"due_date": "2020-02-10T10:00:00Z"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"due_date":"2020-02-10T10:00:00Z"`)
|
||||
assert.NotContains(t, rec.Body.String(), `"due_date":0`)
|
||||
})
|
||||
t.Run("Due date unset", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "5"}, `{"due_date": null}`)
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "5"}, `{"due_date": null}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"due_date":"0001-01-01T00:00:00Z"`)
|
||||
assert.NotContains(t, rec.Body.String(), `"due_date":"2020-02-10T10:00:00Z"`)
|
||||
})
|
||||
t.Run("Reminders", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"reminder_dates": ["2020-02-10T10:00:00Z","2020-02-11T10:00:00Z"]}`)
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"reminder_dates": ["2020-02-10T10:00:00Z","2020-02-11T10:00:00Z"]}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"reminder_dates":["2020-02-10T10:00:00Z","2020-02-11T10:00:00Z"]`)
|
||||
assert.NotContains(t, rec.Body.String(), `"reminder_dates": null`)
|
||||
})
|
||||
t.Run("Reminders unset to empty array", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "27"}, `{"reminder_dates": []}`)
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "27"}, `{"reminder_dates": []}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"reminder_dates":null`)
|
||||
assert.NotContains(t, rec.Body.String(), `"reminder_dates":[1543626724,1543626824]`)
|
||||
})
|
||||
t.Run("Reminders unset to null", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "27"}, `{"reminder_dates": null}`)
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "27"}, `{"reminder_dates": null}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"reminder_dates":null`)
|
||||
assert.NotContains(t, rec.Body.String(), `"reminder_dates":[1543626724,1543626824]`)
|
||||
})
|
||||
t.Run("Repeat after", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"repeat_after":3600}`)
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"repeat_after":3600}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"repeat_after":3600`)
|
||||
assert.NotContains(t, rec.Body.String(), `"repeat_after":0`)
|
||||
})
|
||||
t.Run("Repeat after unset", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "28"}, `{"repeat_after":0}`)
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "28"}, `{"repeat_after":0}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"repeat_after":0`)
|
||||
assert.NotContains(t, rec.Body.String(), `"repeat_after":3600`)
|
||||
})
|
||||
t.Run("Repeat after update done", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "28"}, `{"done":true}`)
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "28"}, `{"done":true}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"done":false`)
|
||||
assert.NotContains(t, rec.Body.String(), `"done":true`)
|
||||
})
|
||||
t.Run("Assignees", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"assignees":[{"id":1}]}`)
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"assignees":[{"id":1}]}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"assignees":[{"id":1`)
|
||||
assert.NotContains(t, rec.Body.String(), `"assignees":[]`)
|
||||
})
|
||||
t.Run("Removing Assignees empty array", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "30"}, `{"assignees":[]}`)
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "30"}, `{"assignees":[]}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"assignees":null`)
|
||||
assert.NotContains(t, rec.Body.String(), `"assignees":[{"id":1`)
|
||||
})
|
||||
t.Run("Removing Assignees null", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "30"}, `{"assignees":null}`)
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "30"}, `{"assignees":null}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"assignees":null`)
|
||||
assert.NotContains(t, rec.Body.String(), `"assignees":[{"id":1`)
|
||||
})
|
||||
t.Run("Priority", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"priority":100}`)
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"priority":100}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"priority":100`)
|
||||
assert.NotContains(t, rec.Body.String(), `"priority":0`)
|
||||
})
|
||||
t.Run("Priority to 0", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "3"}, `{"priority":0}`)
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "3"}, `{"priority":0}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"priority":0`)
|
||||
assert.NotContains(t, rec.Body.String(), `"priority":100`)
|
||||
})
|
||||
t.Run("Start date", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"start_date":"2020-02-10T10:00:00Z"}`)
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"start_date":"2020-02-10T10:00:00Z"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"start_date":"2020-02-10T10:00:00Z"`)
|
||||
assert.NotContains(t, rec.Body.String(), `"start_date":0`)
|
||||
})
|
||||
t.Run("Start date unset", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "7"}, `{"start_date":"0001-01-01T00:00:00Z"}`)
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "7"}, `{"start_date":"0001-01-01T00:00:00Z"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"start_date":"0001-01-01T00:00:00Z"`)
|
||||
assert.NotContains(t, rec.Body.String(), `"start_date":"2020-02-10T10:00:00Z"`)
|
||||
})
|
||||
t.Run("End date", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"end_date":"2020-02-10T12:00:00Z"}`)
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"end_date":"2020-02-10T12:00:00Z"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"end_date":"2020-02-10T12:00:00Z"`)
|
||||
assert.NotContains(t, rec.Body.String(), `"end_date":""`)
|
||||
})
|
||||
t.Run("End date unset", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "8"}, `{"end_date":"0001-01-01T00:00:00Z"}`)
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "8"}, `{"end_date":"0001-01-01T00:00:00Z"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"end_date":"0001-01-01T00:00:00Z"`)
|
||||
assert.NotContains(t, rec.Body.String(), `"end_date":"2020-02-10T10:00:00Z"`)
|
||||
})
|
||||
t.Run("Color", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"hex_color":"f0f0f0"}`)
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"hex_color":"f0f0f0"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"hex_color":"f0f0f0"`)
|
||||
assert.NotContains(t, rec.Body.String(), `"hex_color":""`)
|
||||
})
|
||||
t.Run("Color unset", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "31"}, `{"hex_color":""}`)
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "31"}, `{"hex_color":""}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"hex_color":""`)
|
||||
assert.NotContains(t, rec.Body.String(), `"hex_color":"f0f0f0"`)
|
||||
})
|
||||
t.Run("Percent Done", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"percent_done":0.1}`)
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"percent_done":0.1}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"percent_done":0.1`)
|
||||
assert.NotContains(t, rec.Body.String(), `"percent_done":0,`)
|
||||
})
|
||||
t.Run("Percent Done unset", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "33"}, `{"percent_done":0}`)
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "33"}, `{"percent_done":0}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"percent_done":0,`)
|
||||
assert.NotContains(t, rec.Body.String(), `"percent_done":0.1`)
|
||||
|
@ -212,112 +212,112 @@ func TestTask(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("Nonexisting", func(t *testing.T) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "99999"}, `{"title":"Lorem Ipsum"}`)
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "99999"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeTaskDoesNotExist)
|
||||
})
|
||||
t.Run("Rights check", func(t *testing.T) {
|
||||
t.Run("Forbidden", func(t *testing.T) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "14"}, `{"title":"Lorem Ipsum"}`)
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "14"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via Team readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "15"}, `{"title":"Lorem Ipsum"}`)
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "15"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via Team write", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "16"}, `{"title":"Lorem Ipsum"}`)
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "16"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
|
||||
})
|
||||
t.Run("Shared Via Team admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "17"}, `{"title":"Lorem Ipsum"}`)
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "17"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
|
||||
})
|
||||
|
||||
t.Run("Shared Via User readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "18"}, `{"title":"Lorem Ipsum"}`)
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "18"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via User write", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "19"}, `{"title":"Lorem Ipsum"}`)
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "19"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
|
||||
})
|
||||
t.Run("Shared Via User admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "20"}, `{"title":"Lorem Ipsum"}`)
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "20"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
|
||||
})
|
||||
|
||||
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "21"}, `{"title":"Lorem Ipsum"}`)
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "21"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "22"}, `{"title":"Lorem Ipsum"}`)
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "22"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "23"}, `{"title":"Lorem Ipsum"}`)
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "23"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
|
||||
})
|
||||
|
||||
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "24"}, `{"title":"Lorem Ipsum"}`)
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "24"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "25"}, `{"title":"Lorem Ipsum"}`)
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "25"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "26"}, `{"title":"Lorem Ipsum"}`)
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "26"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
|
||||
})
|
||||
})
|
||||
t.Run("Move to other list", func(t *testing.T) {
|
||||
t.Run("Move to other project", func(t *testing.T) {
|
||||
t.Run("normal", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"list_id":7}`)
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"project_id":7}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"list_id":7`)
|
||||
assert.NotContains(t, rec.Body.String(), `"list_id":1`)
|
||||
assert.Contains(t, rec.Body.String(), `"project_id":7`)
|
||||
assert.NotContains(t, rec.Body.String(), `"project_id":1`)
|
||||
})
|
||||
t.Run("Forbidden", func(t *testing.T) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"list_id":20}`)
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"project_id":20}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrorCodeGenericForbidden)
|
||||
})
|
||||
t.Run("Read Only", func(t *testing.T) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"list_id":6}`)
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"project_id":6}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrorCodeGenericForbidden)
|
||||
})
|
||||
})
|
||||
t.Run("Bucket", func(t *testing.T) {
|
||||
t.Run("Normal", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"bucket_id":3}`)
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"bucket_id":3}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"bucket_id":3`)
|
||||
assert.NotContains(t, rec.Body.String(), `"bucket_id":1`)
|
||||
})
|
||||
t.Run("Different List", func(t *testing.T) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"bucket_id":4}`)
|
||||
t.Run("Different Project", func(t *testing.T) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"bucket_id":4}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeBucketDoesNotBelongToList)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeBucketDoesNotBelongToProject)
|
||||
})
|
||||
t.Run("Nonexisting Bucket", func(t *testing.T) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"bucket_id":9999}`)
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"projecttask": "1"}, `{"bucket_id":9999}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeBucketDoesNotExist)
|
||||
})
|
||||
|
@ -325,81 +325,81 @@ func TestTask(t *testing.T) {
|
|||
})
|
||||
t.Run("Delete", func(t *testing.T) {
|
||||
t.Run("Normal", func(t *testing.T) {
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "1"})
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "1"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
|
||||
})
|
||||
t.Run("Nonexisting", func(t *testing.T) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "99999"})
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "99999"})
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeTaskDoesNotExist)
|
||||
})
|
||||
t.Run("Rights check", func(t *testing.T) {
|
||||
t.Run("Forbidden", func(t *testing.T) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "14"})
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "14"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via Team readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "15"})
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "15"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via Team write", func(t *testing.T) {
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "16"})
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "16"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
|
||||
})
|
||||
t.Run("Shared Via Team admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "17"})
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "17"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
|
||||
})
|
||||
|
||||
t.Run("Shared Via User readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "18"})
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "18"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via User write", func(t *testing.T) {
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "19"})
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "19"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
|
||||
})
|
||||
t.Run("Shared Via User admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "20"})
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "20"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
|
||||
})
|
||||
|
||||
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "21"})
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "21"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "22"})
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "22"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "23"})
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "23"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
|
||||
})
|
||||
|
||||
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "24"})
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "24"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "25"})
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "25"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "26"})
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"projecttask": "26"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
|
||||
})
|
||||
|
@ -407,110 +407,110 @@ func TestTask(t *testing.T) {
|
|||
})
|
||||
t.Run("Create", func(t *testing.T) {
|
||||
t.Run("Normal", func(t *testing.T) {
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "1"}, `{"title":"Lorem Ipsum"}`)
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "1"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
|
||||
})
|
||||
t.Run("Nonexisting", func(t *testing.T) {
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "9999"}, `{"title":"Lorem Ipsum"}`)
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "9999"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeListDoesNotExist)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeProjectDoesNotExist)
|
||||
})
|
||||
t.Run("Rights check", func(t *testing.T) {
|
||||
t.Run("Forbidden", func(t *testing.T) {
|
||||
// Owned by user13
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "20"}, `{"title":"Lorem Ipsum"}`)
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "20"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via Team readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "6"}, `{"title":"Lorem Ipsum"}`)
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "6"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via Team write", func(t *testing.T) {
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "7"}, `{"title":"Lorem Ipsum"}`)
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "7"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
|
||||
})
|
||||
t.Run("Shared Via Team admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "8"}, `{"title":"Lorem Ipsum"}`)
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "8"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
|
||||
})
|
||||
|
||||
t.Run("Shared Via User readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "9"}, `{"title":"Lorem Ipsum"}`)
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "9"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via User write", func(t *testing.T) {
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "10"}, `{"title":"Lorem Ipsum"}`)
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "10"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
|
||||
})
|
||||
t.Run("Shared Via User admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "11"}, `{"title":"Lorem Ipsum"}`)
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "11"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
|
||||
})
|
||||
|
||||
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "12"}, `{"title":"Lorem Ipsum"}`)
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "12"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "13"}, `{"title":"Lorem Ipsum"}`)
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "13"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "14"}, `{"title":"Lorem Ipsum"}`)
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "14"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
|
||||
})
|
||||
|
||||
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "15"}, `{"title":"Lorem Ipsum"}`)
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "15"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "16"}, `{"title":"Lorem Ipsum"}`)
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "16"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "17"}, `{"title":"Lorem Ipsum"}`)
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "17"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
|
||||
})
|
||||
})
|
||||
t.Run("Bucket", func(t *testing.T) {
|
||||
t.Run("Normal", func(t *testing.T) {
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "1"}, `{"title":"Lorem Ipsum","bucket_id":3}`)
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "1"}, `{"title":"Lorem Ipsum","bucket_id":3}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"bucket_id":3`)
|
||||
assert.NotContains(t, rec.Body.String(), `"bucket_id":1`)
|
||||
})
|
||||
t.Run("Different List", func(t *testing.T) {
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "1"}, `{"title":"Lorem Ipsum","bucket_id":4}`)
|
||||
t.Run("Different Project", func(t *testing.T) {
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "1"}, `{"title":"Lorem Ipsum","bucket_id":4}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeBucketDoesNotBelongToList)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeBucketDoesNotBelongToProject)
|
||||
})
|
||||
t.Run("Nonexisting Bucket", func(t *testing.T) {
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "1"}, `{"title":"Lorem Ipsum","bucket_id":9999}`)
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"project": "1"}, `{"title":"Lorem Ipsum","bucket_id":9999}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeBucketDoesNotExist)
|
||||
})
|
||||
})
|
||||
t.Run("Link Share", func(t *testing.T) {
|
||||
rec, err := testHandlerLinkShareWrite.testCreateWithLinkShare(nil, map[string]string{"list": "2"}, `{"title":"Lorem Ipsum"}`)
|
||||
rec, err := testHandlerLinkShareWrite.testCreateWithLinkShare(nil, map[string]string{"project": "2"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
|
||||
db.AssertExists(t, "tasks", map[string]interface{}{
|
||||
"list_id": 2,
|
||||
"project_id": 2,
|
||||
"title": "Lorem Ipsum",
|
||||
"created_by_id": -2,
|
||||
}, false)
|
||||
|
|
|
@ -24,7 +24,7 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestUserList(t *testing.T) {
|
||||
func TestUserProject(t *testing.T) {
|
||||
t.Run("Normal test", func(t *testing.T) {
|
||||
rec, err := newTestRequestWithUser(t, http.MethodPost, apiv1.UserList, &testuser1, "", nil, nil)
|
||||
assert.NoError(t, err)
|
|
@ -55,6 +55,11 @@ func getClient() (*mail.Client, error) {
|
|||
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.MailerForceSSL.GetBool() {
|
||||
opts = append(opts, mail.WithSSL())
|
||||
}
|
||||
|
||||
if config.MailerUsername.GetString() != "" && config.MailerPassword.GetString() != "" {
|
||||
|
@ -105,14 +110,14 @@ func StartMailDaemon() {
|
|||
if !open {
|
||||
err = c.DialWithContext(context.Background())
|
||||
if err != nil {
|
||||
log.Error("Error during connect to smtp server: %s", err)
|
||||
log.Errorf("Error during connect to smtp server: %s", err)
|
||||
break
|
||||
}
|
||||
open = true
|
||||
}
|
||||
err = c.Send(m)
|
||||
if err != nil {
|
||||
log.Error("Error when sending mail: %s", err)
|
||||
log.Errorf("Error when sending mail: %s", err)
|
||||
break
|
||||
}
|
||||
// Close the connection to the SMTP server if no email was sent in
|
||||
|
@ -122,10 +127,10 @@ func StartMailDaemon() {
|
|||
open = false
|
||||
err = c.Close()
|
||||
if err != nil {
|
||||
log.Error("Error closing the mail server connection: %s\n", err)
|
||||
log.Errorf("Error closing the mail server connection: %s\n", err)
|
||||
break
|
||||
}
|
||||
log.Infof("Closed connection to mail server")
|
||||
log.Info("Closed connection to mail server")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -85,7 +85,7 @@ func getMessage(opts *Opts) *mail.Msg {
|
|||
m.Subject(opts.Subject)
|
||||
|
||||
for _, h := range opts.Headers {
|
||||
m.SetHeader(h.Field, h.Content)
|
||||
m.SetGenHeader(h.Field, h.Content)
|
||||
}
|
||||
|
||||
for name, content := range opts.Embeds {
|
||||
|
|
|
@ -28,7 +28,7 @@ import (
|
|||
)
|
||||
|
||||
// SecondsUntilInactive defines the seconds until a user is considered inactive
|
||||
const SecondsUntilInactive = 60
|
||||
const SecondsUntilInactive = 30
|
||||
|
||||
// ActiveUsersKey is the key used to store active users in redis
|
||||
const ActiveUsersKey = `activeusers`
|
||||
|
@ -55,12 +55,13 @@ func init() {
|
|||
users: make(map[int64]*ActiveUser),
|
||||
mutex: &sync.Mutex{},
|
||||
}
|
||||
}
|
||||
|
||||
promauto.NewGaugeFunc(prometheus.GaugeOpts{
|
||||
func setupActiveUsersMetric() {
|
||||
err := registry.Register(promauto.NewGaugeFunc(prometheus.GaugeOpts{
|
||||
Name: "vikunja_active_users",
|
||||
Help: "The currently active users on this node",
|
||||
Help: "The number of users active within the last 30 seconds on this node",
|
||||
}, func() float64 {
|
||||
|
||||
allActiveUsers, err := getActiveUsers()
|
||||
if err != nil {
|
||||
log.Error(err.Error())
|
||||
|
@ -75,7 +76,10 @@ func init() {
|
|||
}
|
||||
}
|
||||
return float64(activeUsersCount)
|
||||
})
|
||||
}))
|
||||
if err != nil {
|
||||
log.Criticalf("Could not register metrics for currently active users: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// SetUserActive sets a user as active and pushes it to redis
|
||||
|
|
|
@ -28,8 +28,8 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
// ListCountKey is the name of the key in which we save the list count
|
||||
ListCountKey = `listcount`
|
||||
// ProjectCountKey is the name of the key in which we save the project count
|
||||
ProjectCountKey = `projectcount`
|
||||
|
||||
// UserCountKey is the name of the key we use to store total users in redis
|
||||
UserCountKey = `usercount`
|
||||
|
@ -65,16 +65,16 @@ func InitMetrics() {
|
|||
|
||||
GetRegistry()
|
||||
|
||||
// Register total list count metric
|
||||
// Register total project count metric
|
||||
err := registry.Register(promauto.NewGaugeFunc(prometheus.GaugeOpts{
|
||||
Name: "vikunja_list_count",
|
||||
Help: "The number of lists on this instance",
|
||||
Name: "vikunja_project_count",
|
||||
Help: "The number of projects on this instance",
|
||||
}, func() float64 {
|
||||
count, _ := GetCount(ListCountKey)
|
||||
count, _ := GetCount(ProjectCountKey)
|
||||
return float64(count)
|
||||
}))
|
||||
if err != nil {
|
||||
log.Criticalf("Could not register metrics for %s: %s", ListCountKey, err)
|
||||
log.Criticalf("Could not register metrics for %s: %s", ProjectCountKey, err)
|
||||
}
|
||||
|
||||
// Register total user count metric
|
||||
|
@ -113,7 +113,7 @@ func InitMetrics() {
|
|||
log.Criticalf("Could not register metrics for %s: %s", TaskCountKey, err)
|
||||
}
|
||||
|
||||
// Register total user count metric
|
||||
// Register total teams count metric
|
||||
err = registry.Register(promauto.NewGaugeFunc(prometheus.GaugeOpts{
|
||||
Name: "vikunja_team_count",
|
||||
Help: "The total number of teams on this instance",
|
||||
|
@ -124,9 +124,11 @@ func InitMetrics() {
|
|||
if err != nil {
|
||||
log.Criticalf("Could not register metrics for %s: %s", TeamCountKey, err)
|
||||
}
|
||||
|
||||
setupActiveUsersMetric()
|
||||
}
|
||||
|
||||
// GetCount returns the current count from redis
|
||||
// GetCount returns the current count from keyvalue
|
||||
func GetCount(key string) (count int64, err error) {
|
||||
cnt, exists, err := keyvalue.Get(key)
|
||||
if err != nil {
|
||||
|
@ -145,7 +147,7 @@ func GetCount(key string) (count int64, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
// SetCount sets the list count to a given value
|
||||
// SetCount sets the project count to a given value
|
||||
func SetCount(count int64, key string) error {
|
||||
return keyvalue.Put(key, count)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
},
|
||||
})
|
||||
}
|
43
pkg/migration/20221002120521.go
Normal file
43
pkg/migration/20221002120521.go
Normal file
|
@ -0,0 +1,43 @@
|
|||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public Licensee as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public Licensee
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package migration
|
||||
|
||||
import (
|
||||
"src.techknowlogick.com/xormigrate"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
type tasks20221002120521 struct {
|
||||
CoverImageAttachmentID int64 `xorm:"bigint default 0" json:"cover_image_attachment_id"`
|
||||
}
|
||||
|
||||
func (tasks20221002120521) TableName() string {
|
||||
return "tasks"
|
||||
}
|
||||
|
||||
func init() {
|
||||
migrations = append(migrations, &xormigrate.Migration{
|
||||
ID: "20221002120521",
|
||||
Description: "Add cover image attachment id",
|
||||
Migrate: func(tx *xorm.Engine) error {
|
||||
return tx.Sync2(tasks20221002120521{})
|
||||
},
|
||||
Rollback: func(tx *xorm.Engine) error {
|
||||
return nil
|
||||
},
|
||||
})
|
||||
}
|
432
pkg/migration/20221113170740.go
Normal file
432
pkg/migration/20221113170740.go
Normal file
|
@ -0,0 +1,432 @@
|
|||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public Licensee as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public Licensee
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package migration
|
||||
|
||||
import (
|
||||
"src.techknowlogick.com/xormigrate"
|
||||
"xorm.io/xorm"
|
||||
"xorm.io/xorm/schemas"
|
||||
)
|
||||
|
||||
const sqliteRename20221113170740 = `
|
||||
-- buckets
|
||||
create table buckets_dg_tmp
|
||||
(
|
||||
id INTEGER not null
|
||||
primary key autoincrement,
|
||||
title TEXT not null,
|
||||
project_id INTEGER not null,
|
||||
"limit" INTEGER default 0,
|
||||
is_done_bucket INTEGER,
|
||||
position REAL,
|
||||
created DATETIME not null,
|
||||
updated DATETIME not null,
|
||||
created_by_id INTEGER not null
|
||||
);
|
||||
|
||||
insert into buckets_dg_tmp(id, title, project_id, "limit", is_done_bucket, position, created, updated, created_by_id)
|
||||
select id,
|
||||
title,
|
||||
list_id,
|
||||
"limit",
|
||||
is_done_bucket,
|
||||
position,
|
||||
created,
|
||||
updated,
|
||||
created_by_id
|
||||
from buckets;
|
||||
|
||||
drop table buckets;
|
||||
|
||||
alter table buckets_dg_tmp
|
||||
rename to buckets;
|
||||
|
||||
create unique index UQE_buckets_id
|
||||
on buckets (id);
|
||||
|
||||
-- link shares
|
||||
create table link_shares_dg_tmp
|
||||
(
|
||||
id INTEGER not null
|
||||
primary key autoincrement,
|
||||
hash TEXT not null,
|
||||
name TEXT,
|
||||
project_id INTEGER not null,
|
||||
"right" INTEGER default 0 not null,
|
||||
sharing_type INTEGER default 0 not null,
|
||||
password TEXT,
|
||||
shared_by_id INTEGER not null,
|
||||
created DATETIME not null,
|
||||
updated DATETIME not null
|
||||
);
|
||||
|
||||
insert into link_shares_dg_tmp(id, hash, name, project_id, "right", sharing_type, password, shared_by_id, created,
|
||||
updated)
|
||||
select id,
|
||||
hash,
|
||||
name,
|
||||
list_id,
|
||||
"right",
|
||||
sharing_type,
|
||||
password,
|
||||
shared_by_id,
|
||||
created,
|
||||
updated
|
||||
from link_shares;
|
||||
|
||||
drop table link_shares;
|
||||
|
||||
alter table link_shares_dg_tmp
|
||||
rename to link_shares;
|
||||
|
||||
create index IDX_link_shares_right
|
||||
on link_shares ("right");
|
||||
|
||||
create index IDX_link_shares_shared_by_id
|
||||
on link_shares (shared_by_id);
|
||||
|
||||
create index IDX_link_shares_sharing_type
|
||||
on link_shares (sharing_type);
|
||||
|
||||
create unique index UQE_link_shares_hash
|
||||
on link_shares (hash);
|
||||
|
||||
create unique index UQE_link_shares_id
|
||||
on link_shares (id);
|
||||
|
||||
-- tasks
|
||||
create table tasks_dg_tmp
|
||||
(
|
||||
id INTEGER not null
|
||||
primary key autoincrement,
|
||||
title TEXT not null,
|
||||
description TEXT,
|
||||
done INTEGER,
|
||||
done_at DATETIME,
|
||||
due_date DATETIME,
|
||||
project_id INTEGER not null,
|
||||
repeat_after INTEGER,
|
||||
repeat_mode INTEGER default 0 not null,
|
||||
priority INTEGER,
|
||||
start_date DATETIME,
|
||||
end_date DATETIME,
|
||||
hex_color TEXT,
|
||||
percent_done REAL,
|
||||
"index" INTEGER default 0 not null,
|
||||
uid TEXT,
|
||||
cover_image_attachment_id INTEGER default 0,
|
||||
created DATETIME not null,
|
||||
updated DATETIME not null,
|
||||
bucket_id INTEGER,
|
||||
position REAL,
|
||||
kanban_position REAL,
|
||||
created_by_id INTEGER not null
|
||||
);
|
||||
|
||||
insert into tasks_dg_tmp(id, title, description, done, done_at, due_date, project_id, repeat_after, repeat_mode,
|
||||
priority, start_date, end_date, hex_color, percent_done, "index", uid,
|
||||
cover_image_attachment_id, created, updated, bucket_id, position, kanban_position,
|
||||
created_by_id)
|
||||
select id,
|
||||
title,
|
||||
description,
|
||||
done,
|
||||
done_at,
|
||||
due_date,
|
||||
list_id,
|
||||
repeat_after,
|
||||
repeat_mode,
|
||||
priority,
|
||||
start_date,
|
||||
end_date,
|
||||
hex_color,
|
||||
percent_done,
|
||||
"index",
|
||||
uid,
|
||||
cover_image_attachment_id,
|
||||
created,
|
||||
updated,
|
||||
bucket_id,
|
||||
position,
|
||||
kanban_position,
|
||||
created_by_id
|
||||
from tasks;
|
||||
|
||||
drop table tasks;
|
||||
|
||||
alter table tasks_dg_tmp
|
||||
rename to tasks;
|
||||
|
||||
create index IDX_tasks_done
|
||||
on tasks (done);
|
||||
|
||||
create index IDX_tasks_done_at
|
||||
on tasks (done_at);
|
||||
|
||||
create index IDX_tasks_due_date
|
||||
on tasks (due_date);
|
||||
|
||||
create index IDX_tasks_end_date
|
||||
on tasks (end_date);
|
||||
|
||||
create index IDX_tasks_list_id
|
||||
on tasks (project_id);
|
||||
|
||||
create index IDX_tasks_repeat_after
|
||||
on tasks (repeat_after);
|
||||
|
||||
create index IDX_tasks_start_date
|
||||
on tasks (start_date);
|
||||
|
||||
create unique index UQE_tasks_id
|
||||
on tasks (id);
|
||||
|
||||
--- team_lists
|
||||
create table team_lists_dg_tmp
|
||||
(
|
||||
id INTEGER not null
|
||||
primary key autoincrement,
|
||||
team_id INTEGER not null,
|
||||
project_id INTEGER not null,
|
||||
"right" INTEGER default 0 not null,
|
||||
created DATETIME not null,
|
||||
updated DATETIME not null
|
||||
);
|
||||
|
||||
insert into team_lists_dg_tmp(id, team_id, project_id, "right", created, updated)
|
||||
select id, team_id, list_id, "right", created, updated
|
||||
from team_lists;
|
||||
|
||||
drop table team_lists;
|
||||
|
||||
alter table team_lists_dg_tmp
|
||||
rename to team_lists;
|
||||
|
||||
create index IDX_team_lists_list_id
|
||||
on team_lists (project_id);
|
||||
|
||||
create index IDX_team_lists_right
|
||||
on team_lists ("right");
|
||||
|
||||
create index IDX_team_lists_team_id
|
||||
on team_lists (team_id);
|
||||
|
||||
create unique index UQE_team_lists_id
|
||||
on team_lists (id);
|
||||
|
||||
--- users
|
||||
create table users_dg_tmp
|
||||
(
|
||||
id INTEGER not null
|
||||
primary key autoincrement,
|
||||
name TEXT,
|
||||
username TEXT not null,
|
||||
password TEXT,
|
||||
email TEXT,
|
||||
status INTEGER default 0,
|
||||
avatar_provider TEXT,
|
||||
avatar_file_id INTEGER,
|
||||
issuer TEXT,
|
||||
subject TEXT,
|
||||
email_reminders_enabled INTEGER default 1,
|
||||
discoverable_by_name INTEGER default 0,
|
||||
discoverable_by_email INTEGER default 0,
|
||||
overdue_tasks_reminders_enabled INTEGER default 1,
|
||||
overdue_tasks_reminders_time TEXT default '09:00' not null,
|
||||
default_project_id INTEGER,
|
||||
week_start INTEGER,
|
||||
language TEXT,
|
||||
timezone TEXT,
|
||||
deletion_scheduled_at DATETIME,
|
||||
deletion_last_reminder_sent DATETIME,
|
||||
export_file_id INTEGER,
|
||||
created DATETIME not null,
|
||||
updated DATETIME not null
|
||||
);
|
||||
|
||||
insert into users_dg_tmp(id, name, username, password, email, status, avatar_provider, avatar_file_id, issuer, subject,
|
||||
email_reminders_enabled, discoverable_by_name, discoverable_by_email,
|
||||
overdue_tasks_reminders_enabled, overdue_tasks_reminders_time, default_project_id, week_start,
|
||||
language, timezone, deletion_scheduled_at, deletion_last_reminder_sent, export_file_id,
|
||||
created, updated)
|
||||
select id,
|
||||
name,
|
||||
username,
|
||||
password,
|
||||
email,
|
||||
status,
|
||||
avatar_provider,
|
||||
avatar_file_id,
|
||||
issuer,
|
||||
subject,
|
||||
email_reminders_enabled,
|
||||
discoverable_by_name,
|
||||
discoverable_by_email,
|
||||
overdue_tasks_reminders_enabled,
|
||||
overdue_tasks_reminders_time,
|
||||
default_list_id,
|
||||
week_start,
|
||||
language,
|
||||
timezone,
|
||||
deletion_scheduled_at,
|
||||
deletion_last_reminder_sent,
|
||||
export_file_id,
|
||||
created,
|
||||
updated
|
||||
from users;
|
||||
|
||||
drop table users;
|
||||
|
||||
alter table users_dg_tmp
|
||||
rename to users;
|
||||
|
||||
create index IDX_users_default_list_id
|
||||
on users (default_project_id);
|
||||
|
||||
create index IDX_users_discoverable_by_email
|
||||
on users (discoverable_by_email);
|
||||
|
||||
create index IDX_users_discoverable_by_name
|
||||
on users (discoverable_by_name);
|
||||
|
||||
create index IDX_users_overdue_tasks_reminders_enabled
|
||||
on users (overdue_tasks_reminders_enabled);
|
||||
|
||||
create unique index UQE_users_id
|
||||
on users (id);
|
||||
|
||||
create unique index UQE_users_username
|
||||
on users (username);
|
||||
|
||||
--- users_list
|
||||
create table users_lists_dg_tmp
|
||||
(
|
||||
id INTEGER not null
|
||||
primary key autoincrement,
|
||||
user_id INTEGER not null,
|
||||
project_id INTEGER not null,
|
||||
"right" INTEGER default 0 not null,
|
||||
created DATETIME not null,
|
||||
updated DATETIME not null
|
||||
);
|
||||
|
||||
insert into users_lists_dg_tmp(id, user_id, project_id, "right", created, updated)
|
||||
select id, user_id, list_id, "right", created, updated
|
||||
from users_lists;
|
||||
|
||||
drop table users_lists;
|
||||
|
||||
alter table users_lists_dg_tmp
|
||||
rename to users_lists;
|
||||
|
||||
create index IDX_users_lists_list_id
|
||||
on users_lists (project_id);
|
||||
|
||||
create index IDX_users_lists_right
|
||||
on users_lists ("right");
|
||||
|
||||
create index IDX_users_lists_user_id
|
||||
on users_lists (user_id);
|
||||
|
||||
create unique index UQE_users_lists_id
|
||||
on users_lists (id);
|
||||
`
|
||||
|
||||
type colToRename struct {
|
||||
table string
|
||||
oldName string
|
||||
newName string
|
||||
}
|
||||
|
||||
func init() {
|
||||
migrations = append(migrations, &xormigrate.Migration{
|
||||
ID: "20221113170740",
|
||||
Description: "Rename lists to projects",
|
||||
Migrate: func(tx *xorm.Engine) error {
|
||||
// SQLite does not support renaming columns. Instead, we'll need to run manual sql.
|
||||
if tx.Dialect().URI().DBType == schemas.SQLITE {
|
||||
_, err := tx.Exec(sqliteRename20221113170740)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
|
||||
colsToRename := []*colToRename{
|
||||
{
|
||||
table: "buckets",
|
||||
oldName: "list_id",
|
||||
newName: "project_id",
|
||||
},
|
||||
{
|
||||
table: "link_shares",
|
||||
oldName: "list_id",
|
||||
newName: "project_id",
|
||||
},
|
||||
{
|
||||
table: "tasks",
|
||||
oldName: "list_id",
|
||||
newName: "project_id",
|
||||
},
|
||||
{
|
||||
table: "team_lists",
|
||||
oldName: "list_id",
|
||||
newName: "project_id",
|
||||
},
|
||||
{
|
||||
table: "users",
|
||||
oldName: "default_list_id",
|
||||
newName: "default_project_id",
|
||||
},
|
||||
{
|
||||
table: "users_lists",
|
||||
oldName: "list_id",
|
||||
newName: "project_id",
|
||||
},
|
||||
}
|
||||
|
||||
for _, col := range colsToRename {
|
||||
if tx.Dialect().URI().DBType == schemas.POSTGRES || tx.Dialect().URI().DBType == schemas.MYSQL {
|
||||
_, err := tx.Exec("ALTER TABLE `" + col.table + "` RENAME COLUMN `" + col.oldName + "` TO `" + col.newName + "`")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err := renameTable(tx, "lists", "projects")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = renameTable(tx, "team_lists", "team_projects")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = renameTable(tx, "users_lists", "users_projects")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
Rollback: func(tx *xorm.Engine) error {
|
||||
return nil
|
||||
},
|
||||
})
|
||||
}
|
|
@ -24,13 +24,13 @@ import (
|
|||
|
||||
// BulkTask is the definition of a bulk update task
|
||||
type BulkTask struct {
|
||||
// A list of task ids to update
|
||||
// A project of task ids to update
|
||||
IDs []int64 `json:"task_ids"`
|
||||
Tasks []*Task `json:"-"`
|
||||
Task
|
||||
}
|
||||
|
||||
func (bt *BulkTask) checkIfTasksAreOnTheSameList(s *xorm.Session) (err error) {
|
||||
func (bt *BulkTask) checkIfTasksAreOnTheSameProject(s *xorm.Session) (err error) {
|
||||
// Get the tasks
|
||||
err = bt.GetTasksByIDs(s)
|
||||
if err != nil {
|
||||
|
@ -41,11 +41,11 @@ func (bt *BulkTask) checkIfTasksAreOnTheSameList(s *xorm.Session) (err error) {
|
|||
return ErrBulkTasksNeedAtLeastOne{}
|
||||
}
|
||||
|
||||
// Check if all tasks are in the same list
|
||||
var firstListID = bt.Tasks[0].ListID
|
||||
// Check if all tasks are in the same project
|
||||
var firstProjectID = bt.Tasks[0].ProjectID
|
||||
for _, t := range bt.Tasks {
|
||||
if t.ListID != firstListID {
|
||||
return ErrBulkTasksMustBeInSameList{firstListID, t.ListID}
|
||||
if t.ProjectID != firstProjectID {
|
||||
return ErrBulkTasksMustBeInSameProject{firstProjectID, t.ProjectID}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,13 +55,13 @@ func (bt *BulkTask) checkIfTasksAreOnTheSameList(s *xorm.Session) (err error) {
|
|||
// CanUpdate checks if a user is allowed to update a task
|
||||
func (bt *BulkTask) CanUpdate(s *xorm.Session, a web.Auth) (bool, error) {
|
||||
|
||||
err := bt.checkIfTasksAreOnTheSameList(s)
|
||||
err := bt.checkIfTasksAreOnTheSameProject(s)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// A user can update an task if he has write acces to its list
|
||||
l := &List{ID: bt.Tasks[0].ListID}
|
||||
// A user can update an task if he has write acces to its project
|
||||
l := &Project{ID: bt.Tasks[0].ProjectID}
|
||||
return l.CanWrite(s, a)
|
||||
}
|
||||
|
||||
|
@ -72,10 +72,10 @@ func (bt *BulkTask) CanUpdate(s *xorm.Session, a web.Auth) (bool, error) {
|
|||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security JWTKeyAuth
|
||||
// @Param task body models.BulkTask true "The task object. Looks like a normal task, the only difference is it uses an array of list_ids to update."
|
||||
// @Param task body models.BulkTask true "The task object. Looks like a normal task, the only difference is it uses an array of project_ids to update."
|
||||
// @Success 200 {object} models.Task "The updated task object."
|
||||
// @Failure 400 {object} web.HTTPError "Invalid task object provided."
|
||||
// @Failure 403 {object} web.HTTPError "The user does not have access to the task (aka its list)"
|
||||
// @Failure 403 {object} web.HTTPError "The user does not have access to the task (aka its project)"
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /tasks/bulk [post]
|
||||
func (bt *BulkTask) Update(s *xorm.Session, a web.Auth) (err error) {
|
||||
|
|
|
@ -47,7 +47,7 @@ func TestBulkTask_Update(t *testing.T) {
|
|||
},
|
||||
},
|
||||
{
|
||||
name: "Test with one task on different list",
|
||||
name: "Test with one task on different project",
|
||||
fields: fields{
|
||||
IDs: []int64{10, 11, 12, 13},
|
||||
Task: Task{
|
||||
|
|
|
@ -110,166 +110,222 @@ func (err ValidationHTTPError) Error() string {
|
|||
}
|
||||
|
||||
// ===========
|
||||
// List errors
|
||||
// Project errors
|
||||
// ===========
|
||||
|
||||
// ErrListDoesNotExist represents a "ErrListDoesNotExist" kind of error. Used if the list does not exist.
|
||||
type ErrListDoesNotExist struct {
|
||||
// ErrProjectDoesNotExist represents a "ErrProjectDoesNotExist" kind of error. Used if the project does not exist.
|
||||
type ErrProjectDoesNotExist struct {
|
||||
ID int64
|
||||
}
|
||||
|
||||
// IsErrListDoesNotExist checks if an error is a ErrListDoesNotExist.
|
||||
func IsErrListDoesNotExist(err error) bool {
|
||||
_, ok := err.(ErrListDoesNotExist)
|
||||
// IsErrProjectDoesNotExist checks if an error is a ErrProjectDoesNotExist.
|
||||
func IsErrProjectDoesNotExist(err error) bool {
|
||||
_, ok := err.(ErrProjectDoesNotExist)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrListDoesNotExist) Error() string {
|
||||
return fmt.Sprintf("List does not exist [ID: %d]", err.ID)
|
||||
func (err ErrProjectDoesNotExist) Error() string {
|
||||
return fmt.Sprintf("Project does not exist [ID: %d]", err.ID)
|
||||
}
|
||||
|
||||
// ErrCodeListDoesNotExist holds the unique world-error code of this error
|
||||
const ErrCodeListDoesNotExist = 3001
|
||||
// ErrCodeProjectDoesNotExist holds the unique world-error code of this error
|
||||
const ErrCodeProjectDoesNotExist = 3001
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrListDoesNotExist) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusNotFound, Code: ErrCodeListDoesNotExist, Message: "This list does not exist."}
|
||||
func (err ErrProjectDoesNotExist) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusNotFound, Code: ErrCodeProjectDoesNotExist, Message: "This project does not exist."}
|
||||
}
|
||||
|
||||
// ErrNeedToHaveListReadAccess represents an error, where the user dont has read access to that List
|
||||
type ErrNeedToHaveListReadAccess struct {
|
||||
ListID int64
|
||||
// ErrNeedToHaveProjectReadAccess represents an error, where the user dont has read access to that Project
|
||||
type ErrNeedToHaveProjectReadAccess struct {
|
||||
ProjectID int64
|
||||
UserID int64
|
||||
}
|
||||
|
||||
// IsErrNeedToHaveListReadAccess checks if an error is a ErrListDoesNotExist.
|
||||
func IsErrNeedToHaveListReadAccess(err error) bool {
|
||||
_, ok := err.(ErrNeedToHaveListReadAccess)
|
||||
// IsErrNeedToHaveProjectReadAccess checks if an error is a ErrProjectDoesNotExist.
|
||||
func IsErrNeedToHaveProjectReadAccess(err error) bool {
|
||||
_, ok := err.(ErrNeedToHaveProjectReadAccess)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrNeedToHaveListReadAccess) Error() string {
|
||||
return fmt.Sprintf("User needs to have read access to that list [ListID: %d, UserID: %d]", err.ListID, err.UserID)
|
||||
func (err ErrNeedToHaveProjectReadAccess) Error() string {
|
||||
return fmt.Sprintf("User needs to have read access to that project [ProjectID: %d, UserID: %d]", err.ProjectID, err.UserID)
|
||||
}
|
||||
|
||||
// ErrCodeNeedToHaveListReadAccess holds the unique world-error code of this error
|
||||
const ErrCodeNeedToHaveListReadAccess = 3004
|
||||
// ErrCodeNeedToHaveProjectReadAccess holds the unique world-error code of this error
|
||||
const ErrCodeNeedToHaveProjectReadAccess = 3004
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrNeedToHaveListReadAccess) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusForbidden, Code: ErrCodeNeedToHaveListReadAccess, Message: "You need to have read access to this list."}
|
||||
func (err ErrNeedToHaveProjectReadAccess) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusForbidden, Code: ErrCodeNeedToHaveProjectReadAccess, Message: "You need to have read access to this project."}
|
||||
}
|
||||
|
||||
// ErrListTitleCannotBeEmpty represents a "ErrListTitleCannotBeEmpty" kind of error. Used if the list does not exist.
|
||||
type ErrListTitleCannotBeEmpty struct{}
|
||||
// ErrProjectTitleCannotBeEmpty represents a "ErrProjectTitleCannotBeEmpty" kind of error. Used if the project does not exist.
|
||||
type ErrProjectTitleCannotBeEmpty struct{}
|
||||
|
||||
// IsErrListTitleCannotBeEmpty checks if an error is a ErrListTitleCannotBeEmpty.
|
||||
func IsErrListTitleCannotBeEmpty(err error) bool {
|
||||
_, ok := err.(ErrListTitleCannotBeEmpty)
|
||||
// IsErrProjectTitleCannotBeEmpty checks if an error is a ErrProjectTitleCannotBeEmpty.
|
||||
func IsErrProjectTitleCannotBeEmpty(err error) bool {
|
||||
_, ok := err.(ErrProjectTitleCannotBeEmpty)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrListTitleCannotBeEmpty) Error() string {
|
||||
return "List title cannot be empty."
|
||||
func (err ErrProjectTitleCannotBeEmpty) Error() string {
|
||||
return "Project title cannot be empty."
|
||||
}
|
||||
|
||||
// ErrCodeListTitleCannotBeEmpty holds the unique world-error code of this error
|
||||
const ErrCodeListTitleCannotBeEmpty = 3005
|
||||
// ErrCodeProjectTitleCannotBeEmpty holds the unique world-error code of this error
|
||||
const ErrCodeProjectTitleCannotBeEmpty = 3005
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrListTitleCannotBeEmpty) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeListTitleCannotBeEmpty, Message: "You must provide at least a list title."}
|
||||
func (err ErrProjectTitleCannotBeEmpty) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeProjectTitleCannotBeEmpty, Message: "You must provide at least a project title."}
|
||||
}
|
||||
|
||||
// ErrListShareDoesNotExist represents a "ErrListShareDoesNotExist" kind of error. Used if the list share does not exist.
|
||||
type ErrListShareDoesNotExist struct {
|
||||
// ErrProjectShareDoesNotExist represents a "ErrProjectShareDoesNotExist" kind of error. Used if the project share does not exist.
|
||||
type ErrProjectShareDoesNotExist struct {
|
||||
ID int64
|
||||
Hash string
|
||||
}
|
||||
|
||||
// IsErrListShareDoesNotExist checks if an error is a ErrListShareDoesNotExist.
|
||||
func IsErrListShareDoesNotExist(err error) bool {
|
||||
_, ok := err.(ErrListShareDoesNotExist)
|
||||
// IsErrProjectShareDoesNotExist checks if an error is a ErrProjectShareDoesNotExist.
|
||||
func IsErrProjectShareDoesNotExist(err error) bool {
|
||||
_, ok := err.(ErrProjectShareDoesNotExist)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrListShareDoesNotExist) Error() string {
|
||||
return "List share does not exist."
|
||||
func (err ErrProjectShareDoesNotExist) Error() string {
|
||||
return "Project share does not exist."
|
||||
}
|
||||
|
||||
// ErrCodeListShareDoesNotExist holds the unique world-error code of this error
|
||||
const ErrCodeListShareDoesNotExist = 3006
|
||||
// ErrCodeProjectShareDoesNotExist holds the unique world-error code of this error
|
||||
const ErrCodeProjectShareDoesNotExist = 3006
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrListShareDoesNotExist) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusNotFound, Code: ErrCodeListShareDoesNotExist, Message: "The list share does not exist."}
|
||||
func (err ErrProjectShareDoesNotExist) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusNotFound, Code: ErrCodeProjectShareDoesNotExist, Message: "The project share does not exist."}
|
||||
}
|
||||
|
||||
// ErrListIdentifierIsNotUnique represents a "ErrListIdentifierIsNotUnique" kind of error. Used if the provided list identifier is not unique.
|
||||
type ErrListIdentifierIsNotUnique struct {
|
||||
// ErrProjectIdentifierIsNotUnique represents a "ErrProjectIdentifierIsNotUnique" kind of error. Used if the provided project identifier is not unique.
|
||||
type ErrProjectIdentifierIsNotUnique struct {
|
||||
Identifier string
|
||||
}
|
||||
|
||||
// IsErrListIdentifierIsNotUnique checks if an error is a ErrListIdentifierIsNotUnique.
|
||||
func IsErrListIdentifierIsNotUnique(err error) bool {
|
||||
_, ok := err.(ErrListIdentifierIsNotUnique)
|
||||
// IsErrProjectIdentifierIsNotUnique checks if an error is a ErrProjectIdentifierIsNotUnique.
|
||||
func IsErrProjectIdentifierIsNotUnique(err error) bool {
|
||||
_, ok := err.(ErrProjectIdentifierIsNotUnique)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrListIdentifierIsNotUnique) Error() string {
|
||||
return "List identifier is not unique."
|
||||
func (err ErrProjectIdentifierIsNotUnique) Error() string {
|
||||
return "Project identifier is not unique."
|
||||
}
|
||||
|
||||
// ErrCodeListIdentifierIsNotUnique holds the unique world-error code of this error
|
||||
const ErrCodeListIdentifierIsNotUnique = 3007
|
||||
// ErrCodeProjectIdentifierIsNotUnique holds the unique world-error code of this error
|
||||
const ErrCodeProjectIdentifierIsNotUnique = 3007
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrListIdentifierIsNotUnique) HTTPError() web.HTTPError {
|
||||
func (err ErrProjectIdentifierIsNotUnique) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{
|
||||
HTTPCode: http.StatusBadRequest,
|
||||
Code: ErrCodeListIdentifierIsNotUnique,
|
||||
Message: "A list with this identifier already exists.",
|
||||
Code: ErrCodeProjectIdentifierIsNotUnique,
|
||||
Message: "A project with this identifier already exists.",
|
||||
}
|
||||
}
|
||||
|
||||
// ErrListIsArchived represents an error, where a list is archived
|
||||
type ErrListIsArchived struct {
|
||||
ListID int64
|
||||
// ErrProjectIsArchived represents an error, where a project is archived
|
||||
type ErrProjectIsArchived struct {
|
||||
ProjectID int64
|
||||
}
|
||||
|
||||
// IsErrListIsArchived checks if an error is a .
|
||||
func IsErrListIsArchived(err error) bool {
|
||||
_, ok := err.(ErrListIsArchived)
|
||||
// IsErrProjectIsArchived checks if an error is a project is archived error.
|
||||
func IsErrProjectIsArchived(err error) bool {
|
||||
_, ok := err.(ErrProjectIsArchived)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrListIsArchived) Error() string {
|
||||
return fmt.Sprintf("List is archived [ListID: %d]", err.ListID)
|
||||
func (err ErrProjectIsArchived) Error() string {
|
||||
return fmt.Sprintf("Project is archived [ProjectID: %d]", err.ProjectID)
|
||||
}
|
||||
|
||||
// ErrCodeListIsArchived holds the unique world-error code of this error
|
||||
const ErrCodeListIsArchived = 3008
|
||||
// ErrCodeProjectIsArchived holds the unique world-error code of this error
|
||||
const ErrCodeProjectIsArchived = 3008
|
||||
|
||||
// HTTPError holds the http error description
|
||||
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."}
|
||||
func (err ErrProjectIsArchived) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusPreconditionFailed, Code: ErrCodeProjectIsArchived, Message: "This project is archived. Editing or creating new tasks is not possible."}
|
||||
}
|
||||
|
||||
// ErrProjectCannotBelongToAPseudoNamespace represents an error where a project cannot belong to a pseudo namespace
|
||||
type ErrProjectCannotBelongToAPseudoNamespace struct {
|
||||
ProjectID int64
|
||||
NamespaceID int64
|
||||
}
|
||||
|
||||
// IsErrProjectCannotBelongToAPseudoNamespace checks if an error is a project is archived error.
|
||||
func IsErrProjectCannotBelongToAPseudoNamespace(err error) bool {
|
||||
_, ok := err.(*ErrProjectCannotBelongToAPseudoNamespace)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err *ErrProjectCannotBelongToAPseudoNamespace) Error() string {
|
||||
return fmt.Sprintf("Project cannot belong to a pseudo namespace [ProjectID: %d, NamespaceID: %d]", err.ProjectID, err.NamespaceID)
|
||||
}
|
||||
|
||||
// ErrCodeProjectCannotBelongToAPseudoNamespace holds the unique world-error code of this error
|
||||
const ErrCodeProjectCannotBelongToAPseudoNamespace = 3009
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err *ErrProjectCannotBelongToAPseudoNamespace) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{
|
||||
HTTPCode: http.StatusPreconditionFailed,
|
||||
Code: ErrCodeProjectCannotBelongToAPseudoNamespace,
|
||||
Message: "This project cannot belong a dynamically generated namespace.",
|
||||
}
|
||||
}
|
||||
|
||||
// ErrProjectMustBelongToANamespace represents an error where a project must belong to a namespace
|
||||
type ErrProjectMustBelongToANamespace struct {
|
||||
ProjectID int64
|
||||
NamespaceID int64
|
||||
}
|
||||
|
||||
// IsErrProjectMustBelongToANamespace checks if an error is a project must belong to a namespace error.
|
||||
func IsErrProjectMustBelongToANamespace(err error) bool {
|
||||
_, ok := err.(*ErrProjectMustBelongToANamespace)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err *ErrProjectMustBelongToANamespace) Error() string {
|
||||
return fmt.Sprintf("Project must belong to a namespace [ProjectID: %d, NamespaceID: %d]", err.ProjectID, err.NamespaceID)
|
||||
}
|
||||
|
||||
// ErrCodeProjectMustBelongToANamespace holds the unique world-error code of this error
|
||||
const ErrCodeProjectMustBelongToANamespace = 3010
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err *ErrProjectMustBelongToANamespace) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{
|
||||
HTTPCode: http.StatusPreconditionFailed,
|
||||
Code: ErrCodeProjectMustBelongToANamespace,
|
||||
Message: "This project must belong to a namespace.",
|
||||
}
|
||||
}
|
||||
|
||||
// ================
|
||||
// List task errors
|
||||
// Project task errors
|
||||
// ================
|
||||
|
||||
// ErrTaskCannotBeEmpty represents a "ErrListDoesNotExist" kind of error. Used if the list does not exist.
|
||||
// ErrTaskCannotBeEmpty represents a "ErrProjectDoesNotExist" kind of error. Used if the project does not exist.
|
||||
type ErrTaskCannotBeEmpty struct{}
|
||||
|
||||
// IsErrTaskCannotBeEmpty checks if an error is a ErrListDoesNotExist.
|
||||
// IsErrTaskCannotBeEmpty checks if an error is a ErrProjectDoesNotExist.
|
||||
func IsErrTaskCannotBeEmpty(err error) bool {
|
||||
_, ok := err.(ErrTaskCannotBeEmpty)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrTaskCannotBeEmpty) Error() string {
|
||||
return "List task title cannot be empty."
|
||||
return "Project task title cannot be empty."
|
||||
}
|
||||
|
||||
// ErrCodeTaskCannotBeEmpty holds the unique world-error code of this error
|
||||
|
@ -277,22 +333,22 @@ const ErrCodeTaskCannotBeEmpty = 4001
|
|||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrTaskCannotBeEmpty) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeTaskCannotBeEmpty, Message: "You must provide at least a list task title."}
|
||||
return web.HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeTaskCannotBeEmpty, Message: "You must provide at least a project task title."}
|
||||
}
|
||||
|
||||
// ErrTaskDoesNotExist represents a "ErrListDoesNotExist" kind of error. Used if the list does not exist.
|
||||
// ErrTaskDoesNotExist represents a "ErrProjectDoesNotExist" kind of error. Used if the project does not exist.
|
||||
type ErrTaskDoesNotExist struct {
|
||||
ID int64
|
||||
}
|
||||
|
||||
// IsErrTaskDoesNotExist checks if an error is a ErrListDoesNotExist.
|
||||
// IsErrTaskDoesNotExist checks if an error is a ErrProjectDoesNotExist.
|
||||
func IsErrTaskDoesNotExist(err error) bool {
|
||||
_, ok := err.(ErrTaskDoesNotExist)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrTaskDoesNotExist) Error() string {
|
||||
return fmt.Sprintf("List task does not exist. [ID: %d]", err.ID)
|
||||
return fmt.Sprintf("Project task does not exist. [ID: %d]", err.ID)
|
||||
}
|
||||
|
||||
// ErrCodeTaskDoesNotExist holds the unique world-error code of this error
|
||||
|
@ -303,28 +359,28 @@ func (err ErrTaskDoesNotExist) HTTPError() web.HTTPError {
|
|||
return web.HTTPError{HTTPCode: http.StatusNotFound, Code: ErrCodeTaskDoesNotExist, Message: "This task does not exist"}
|
||||
}
|
||||
|
||||
// ErrBulkTasksMustBeInSameList represents a "ErrBulkTasksMustBeInSameList" kind of error.
|
||||
type ErrBulkTasksMustBeInSameList struct {
|
||||
// ErrBulkTasksMustBeInSameProject represents a "ErrBulkTasksMustBeInSameProject" kind of error.
|
||||
type ErrBulkTasksMustBeInSameProject struct {
|
||||
ShouldBeID int64
|
||||
IsID int64
|
||||
}
|
||||
|
||||
// IsErrBulkTasksMustBeInSameList checks if an error is a ErrBulkTasksMustBeInSameList.
|
||||
func IsErrBulkTasksMustBeInSameList(err error) bool {
|
||||
_, ok := err.(ErrBulkTasksMustBeInSameList)
|
||||
// IsErrBulkTasksMustBeInSameProject checks if an error is a ErrBulkTasksMustBeInSameProject.
|
||||
func IsErrBulkTasksMustBeInSameProject(err error) bool {
|
||||
_, ok := err.(ErrBulkTasksMustBeInSameProject)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrBulkTasksMustBeInSameList) Error() string {
|
||||
return fmt.Sprintf("All bulk editing tasks must be in the same list. [Should be: %d, is: %d]", err.ShouldBeID, err.IsID)
|
||||
func (err ErrBulkTasksMustBeInSameProject) Error() string {
|
||||
return fmt.Sprintf("All bulk editing tasks must be in the same project. [Should be: %d, is: %d]", err.ShouldBeID, err.IsID)
|
||||
}
|
||||
|
||||
// ErrCodeBulkTasksMustBeInSameList holds the unique world-error code of this error
|
||||
const ErrCodeBulkTasksMustBeInSameList = 4003
|
||||
// ErrCodeBulkTasksMustBeInSameProject holds the unique world-error code of this error
|
||||
const ErrCodeBulkTasksMustBeInSameProject = 4003
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrBulkTasksMustBeInSameList) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeBulkTasksMustBeInSameList, Message: "All tasks must be in the same list."}
|
||||
func (err ErrBulkTasksMustBeInSameProject) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeBulkTasksMustBeInSameProject, Message: "All tasks must be in the same project."}
|
||||
}
|
||||
|
||||
// ErrBulkTasksNeedAtLeastOne represents a "ErrBulkTasksNeedAtLeastOne" kind of error.
|
||||
|
@ -763,6 +819,62 @@ func (err ErrInvalidTaskFilterValue) HTTPError() web.HTTPError {
|
|||
}
|
||||
}
|
||||
|
||||
// ErrAttachmentDoesNotBelongToTask represents an error where the provided task cover attachment does not belong to the same task
|
||||
type ErrAttachmentDoesNotBelongToTask struct {
|
||||
TaskID int64
|
||||
AttachmentID int64
|
||||
}
|
||||
|
||||
// IsErrAttachmentAndCoverMustBelongToTheSameTask checks if an error is ErrAttachmentDoesNotBelongToTask.
|
||||
func IsErrAttachmentAndCoverMustBelongToTheSameTask(err error) bool {
|
||||
_, ok := err.(ErrAttachmentDoesNotBelongToTask)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrAttachmentDoesNotBelongToTask) Error() string {
|
||||
return fmt.Sprintf("Task attachment and cover image do not belong to the same task [TaskID: %d, AttachmentID: %d]", err.TaskID, err.AttachmentID)
|
||||
}
|
||||
|
||||
// ErrCodeAttachmentDoesNotBelongToTask holds the unique world-error code of this error
|
||||
const ErrCodeAttachmentDoesNotBelongToTask = 4020
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrAttachmentDoesNotBelongToTask) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{
|
||||
HTTPCode: http.StatusBadRequest,
|
||||
Code: ErrCodeAttachmentDoesNotBelongToTask,
|
||||
Message: "This attachment does not belong to that task.",
|
||||
}
|
||||
}
|
||||
|
||||
// ErrUserAlreadyAssigned represents an error where the user is already assigned to this task
|
||||
type ErrUserAlreadyAssigned struct {
|
||||
TaskID int64
|
||||
UserID int64
|
||||
}
|
||||
|
||||
// IsErrUserAlreadyAssigned checks if an error is ErrUserAlreadyAssigned.
|
||||
func IsErrUserAlreadyAssigned(err error) bool {
|
||||
_, ok := err.(ErrUserAlreadyAssigned)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrUserAlreadyAssigned) Error() string {
|
||||
return fmt.Sprintf("User is already assigned to task [TaskID: %d, UserID: %d]", err.TaskID, err.UserID)
|
||||
}
|
||||
|
||||
// ErrCodeUserAlreadyAssigned holds the unique world-error code of this error
|
||||
const ErrCodeUserAlreadyAssigned = 4021
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrUserAlreadyAssigned) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{
|
||||
HTTPCode: http.StatusBadRequest,
|
||||
Code: ErrCodeUserAlreadyAssigned,
|
||||
Message: "This user is already assigned to that task.",
|
||||
}
|
||||
}
|
||||
|
||||
// =================
|
||||
// Namespace errors
|
||||
// =================
|
||||
|
@ -930,7 +1042,7 @@ const ErrCodeNamespaceIsArchived = 5012
|
|||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrNamespaceIsArchived) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusPreconditionFailed, Code: ErrCodeNamespaceIsArchived, Message: "This namespaces is archived. Editing or creating new lists is not possible."}
|
||||
return web.HTTPError{HTTPCode: http.StatusPreconditionFailed, Code: ErrCodeNamespaceIsArchived, Message: "This namespaces is archived. Editing or creating new projects is not possible."}
|
||||
}
|
||||
|
||||
// ============
|
||||
|
@ -983,7 +1095,7 @@ func (err ErrTeamDoesNotExist) HTTPError() web.HTTPError {
|
|||
return web.HTTPError{HTTPCode: http.StatusNotFound, Code: ErrCodeTeamDoesNotExist, Message: "This team does not exist."}
|
||||
}
|
||||
|
||||
// ErrTeamAlreadyHasAccess represents an error where a team already has access to a list/namespace
|
||||
// ErrTeamAlreadyHasAccess represents an error where a team already has access to a project/namespace
|
||||
type ErrTeamAlreadyHasAccess struct {
|
||||
TeamID int64
|
||||
ID int64
|
||||
|
@ -1055,38 +1167,38 @@ func (err ErrCannotDeleteLastTeamMember) HTTPError() web.HTTPError {
|
|||
return web.HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeCannotDeleteLastTeamMember, Message: "You cannot delete the last member of a team."}
|
||||
}
|
||||
|
||||
// ErrTeamDoesNotHaveAccessToList represents an error, where the Team is not the owner of that List (used i.e. when deleting a List)
|
||||
type ErrTeamDoesNotHaveAccessToList struct {
|
||||
ListID int64
|
||||
// ErrTeamDoesNotHaveAccessToProject represents an error, where the Team is not the owner of that Project (used i.e. when deleting a Project)
|
||||
type ErrTeamDoesNotHaveAccessToProject struct {
|
||||
ProjectID int64
|
||||
TeamID int64
|
||||
}
|
||||
|
||||
// IsErrTeamDoesNotHaveAccessToList checks if an error is a ErrListDoesNotExist.
|
||||
func IsErrTeamDoesNotHaveAccessToList(err error) bool {
|
||||
_, ok := err.(ErrTeamDoesNotHaveAccessToList)
|
||||
// IsErrTeamDoesNotHaveAccessToProject checks if an error is a ErrProjectDoesNotExist.
|
||||
func IsErrTeamDoesNotHaveAccessToProject(err error) bool {
|
||||
_, ok := err.(ErrTeamDoesNotHaveAccessToProject)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrTeamDoesNotHaveAccessToList) Error() string {
|
||||
return fmt.Sprintf("Team does not have access to the list [ListID: %d, TeamID: %d]", err.ListID, err.TeamID)
|
||||
func (err ErrTeamDoesNotHaveAccessToProject) Error() string {
|
||||
return fmt.Sprintf("Team does not have access to the project [ProjectID: %d, TeamID: %d]", err.ProjectID, err.TeamID)
|
||||
}
|
||||
|
||||
// ErrCodeTeamDoesNotHaveAccessToList holds the unique world-error code of this error
|
||||
const ErrCodeTeamDoesNotHaveAccessToList = 6007
|
||||
// ErrCodeTeamDoesNotHaveAccessToProject holds the unique world-error code of this error
|
||||
const ErrCodeTeamDoesNotHaveAccessToProject = 6007
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrTeamDoesNotHaveAccessToList) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusForbidden, Code: ErrCodeTeamDoesNotHaveAccessToList, Message: "This team does not have access to the list."}
|
||||
func (err ErrTeamDoesNotHaveAccessToProject) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusForbidden, Code: ErrCodeTeamDoesNotHaveAccessToProject, Message: "This team does not have access to the project."}
|
||||
}
|
||||
|
||||
// ====================
|
||||
// User <-> List errors
|
||||
// User <-> Project errors
|
||||
// ====================
|
||||
|
||||
// ErrUserAlreadyHasAccess represents an error where a user already has access to a list/namespace
|
||||
// ErrUserAlreadyHasAccess represents an error where a user already has access to a project/namespace
|
||||
type ErrUserAlreadyHasAccess struct {
|
||||
UserID int64
|
||||
ListID int64
|
||||
ProjectID int64
|
||||
}
|
||||
|
||||
// IsErrUserAlreadyHasAccess checks if an error is ErrUserAlreadyHasAccess.
|
||||
|
@ -1096,7 +1208,7 @@ func IsErrUserAlreadyHasAccess(err error) bool {
|
|||
}
|
||||
|
||||
func (err ErrUserAlreadyHasAccess) Error() string {
|
||||
return fmt.Sprintf("User already has access to that list. [User ID: %d, List ID: %d]", err.UserID, err.ListID)
|
||||
return fmt.Sprintf("User already has access to that project. [User ID: %d, Project ID: %d]", err.UserID, err.ProjectID)
|
||||
}
|
||||
|
||||
// ErrCodeUserAlreadyHasAccess holds the unique world-error code of this error
|
||||
|
@ -1104,31 +1216,31 @@ const ErrCodeUserAlreadyHasAccess = 7002
|
|||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrUserAlreadyHasAccess) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusConflict, Code: ErrCodeUserAlreadyHasAccess, Message: "This user already has access to this list."}
|
||||
return web.HTTPError{HTTPCode: http.StatusConflict, Code: ErrCodeUserAlreadyHasAccess, Message: "This user already has access to this project."}
|
||||
}
|
||||
|
||||
// ErrUserDoesNotHaveAccessToList represents an error, where the user is not the owner of that List (used i.e. when deleting a List)
|
||||
type ErrUserDoesNotHaveAccessToList struct {
|
||||
ListID int64
|
||||
// ErrUserDoesNotHaveAccessToProject represents an error, where the user is not the owner of that Project (used i.e. when deleting a Project)
|
||||
type ErrUserDoesNotHaveAccessToProject struct {
|
||||
ProjectID int64
|
||||
UserID int64
|
||||
}
|
||||
|
||||
// IsErrUserDoesNotHaveAccessToList checks if an error is a ErrListDoesNotExist.
|
||||
func IsErrUserDoesNotHaveAccessToList(err error) bool {
|
||||
_, ok := err.(ErrUserDoesNotHaveAccessToList)
|
||||
// IsErrUserDoesNotHaveAccessToProject checks if an error is a ErrProjectDoesNotExist.
|
||||
func IsErrUserDoesNotHaveAccessToProject(err error) bool {
|
||||
_, ok := err.(ErrUserDoesNotHaveAccessToProject)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrUserDoesNotHaveAccessToList) Error() string {
|
||||
return fmt.Sprintf("User does not have access to the list [ListID: %d, UserID: %d]", err.ListID, err.UserID)
|
||||
func (err ErrUserDoesNotHaveAccessToProject) Error() string {
|
||||
return fmt.Sprintf("User does not have access to the project [ProjectID: %d, UserID: %d]", err.ProjectID, err.UserID)
|
||||
}
|
||||
|
||||
// ErrCodeUserDoesNotHaveAccessToList holds the unique world-error code of this error
|
||||
const ErrCodeUserDoesNotHaveAccessToList = 7003
|
||||
// ErrCodeUserDoesNotHaveAccessToProject holds the unique world-error code of this error
|
||||
const ErrCodeUserDoesNotHaveAccessToProject = 7003
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrUserDoesNotHaveAccessToList) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusForbidden, Code: ErrCodeUserDoesNotHaveAccessToList, Message: "This user does not have access to the list."}
|
||||
func (err ErrUserDoesNotHaveAccessToProject) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusForbidden, Code: ErrCodeUserDoesNotHaveAccessToProject, Message: "This user does not have access to the project."}
|
||||
}
|
||||
|
||||
// =============
|
||||
|
@ -1280,38 +1392,38 @@ func (err ErrBucketDoesNotExist) HTTPError() web.HTTPError {
|
|||
}
|
||||
}
|
||||
|
||||
// ErrBucketDoesNotBelongToList represents an error where a kanban bucket does not belong to a list
|
||||
type ErrBucketDoesNotBelongToList struct {
|
||||
// ErrBucketDoesNotBelongToProject represents an error where a kanban bucket does not belong to a project
|
||||
type ErrBucketDoesNotBelongToProject struct {
|
||||
BucketID int64
|
||||
ListID int64
|
||||
ProjectID int64
|
||||
}
|
||||
|
||||
// IsErrBucketDoesNotBelongToList checks if an error is ErrBucketDoesNotBelongToList.
|
||||
func IsErrBucketDoesNotBelongToList(err error) bool {
|
||||
_, ok := err.(ErrBucketDoesNotBelongToList)
|
||||
// IsErrBucketDoesNotBelongToProject checks if an error is ErrBucketDoesNotBelongToProject.
|
||||
func IsErrBucketDoesNotBelongToProject(err error) bool {
|
||||
_, ok := err.(ErrBucketDoesNotBelongToProject)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrBucketDoesNotBelongToList) Error() string {
|
||||
return fmt.Sprintf("Bucket does not not belong to list [BucketID: %d, ListID: %d]", err.BucketID, err.ListID)
|
||||
func (err ErrBucketDoesNotBelongToProject) Error() string {
|
||||
return fmt.Sprintf("Bucket does not not belong to project [BucketID: %d, ProjectID: %d]", err.BucketID, err.ProjectID)
|
||||
}
|
||||
|
||||
// ErrCodeBucketDoesNotBelongToList holds the unique world-error code of this error
|
||||
const ErrCodeBucketDoesNotBelongToList = 10002
|
||||
// ErrCodeBucketDoesNotBelongToProject holds the unique world-error code of this error
|
||||
const ErrCodeBucketDoesNotBelongToProject = 10002
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrBucketDoesNotBelongToList) HTTPError() web.HTTPError {
|
||||
func (err ErrBucketDoesNotBelongToProject) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{
|
||||
HTTPCode: http.StatusBadRequest,
|
||||
Code: ErrCodeBucketDoesNotBelongToList,
|
||||
Message: "This bucket does not belong to that list.",
|
||||
Code: ErrCodeBucketDoesNotBelongToProject,
|
||||
Message: "This bucket does not belong to that project.",
|
||||
}
|
||||
}
|
||||
|
||||
// ErrCannotRemoveLastBucket represents an error where a kanban bucket is the last on a list and thus cannot be removed.
|
||||
// ErrCannotRemoveLastBucket represents an error where a kanban bucket is the last on a project and thus cannot be removed.
|
||||
type ErrCannotRemoveLastBucket struct {
|
||||
BucketID int64
|
||||
ListID int64
|
||||
ProjectID int64
|
||||
}
|
||||
|
||||
// IsErrCannotRemoveLastBucket checks if an error is ErrCannotRemoveLastBucket.
|
||||
|
@ -1321,7 +1433,7 @@ func IsErrCannotRemoveLastBucket(err error) bool {
|
|||
}
|
||||
|
||||
func (err ErrCannotRemoveLastBucket) Error() string {
|
||||
return fmt.Sprintf("Cannot remove last bucket of list [BucketID: %d, ListID: %d]", err.BucketID, err.ListID)
|
||||
return fmt.Sprintf("Cannot remove last bucket of project [BucketID: %d, ProjectID: %d]", err.BucketID, err.ProjectID)
|
||||
}
|
||||
|
||||
// ErrCodeCannotRemoveLastBucket holds the unique world-error code of this error
|
||||
|
@ -1332,7 +1444,7 @@ func (err ErrCannotRemoveLastBucket) HTTPError() web.HTTPError {
|
|||
return web.HTTPError{
|
||||
HTTPCode: http.StatusPreconditionFailed,
|
||||
Code: ErrCodeCannotRemoveLastBucket,
|
||||
Message: "You cannot remove the last bucket on this list.",
|
||||
Message: "You cannot remove the last bucket on this project.",
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1365,32 +1477,32 @@ func (err ErrBucketLimitExceeded) HTTPError() web.HTTPError {
|
|||
}
|
||||
}
|
||||
|
||||
// ErrOnlyOneDoneBucketPerList represents an error where a bucket is set to the done bucket but one already exists for its list.
|
||||
type ErrOnlyOneDoneBucketPerList struct {
|
||||
// ErrOnlyOneDoneBucketPerProject represents an error where a bucket is set to the done bucket but one already exists for its project.
|
||||
type ErrOnlyOneDoneBucketPerProject struct {
|
||||
BucketID int64
|
||||
ListID int64
|
||||
ProjectID int64
|
||||
DoneBucketID int64
|
||||
}
|
||||
|
||||
// IsErrOnlyOneDoneBucketPerList checks if an error is ErrBucketLimitExceeded.
|
||||
func IsErrOnlyOneDoneBucketPerList(err error) bool {
|
||||
_, ok := err.(*ErrOnlyOneDoneBucketPerList)
|
||||
// IsErrOnlyOneDoneBucketPerProject checks if an error is ErrBucketLimitExceeded.
|
||||
func IsErrOnlyOneDoneBucketPerProject(err error) bool {
|
||||
_, ok := err.(*ErrOnlyOneDoneBucketPerProject)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err *ErrOnlyOneDoneBucketPerList) Error() string {
|
||||
return fmt.Sprintf("There can be only one done bucket per list [BucketID: %d, ListID: %d]", err.BucketID, err.ListID)
|
||||
func (err *ErrOnlyOneDoneBucketPerProject) Error() string {
|
||||
return fmt.Sprintf("There can be only one done bucket per project [BucketID: %d, ProjectID: %d]", err.BucketID, err.ProjectID)
|
||||
}
|
||||
|
||||
// ErrCodeOnlyOneDoneBucketPerList holds the unique world-error code of this error
|
||||
const ErrCodeOnlyOneDoneBucketPerList = 10005
|
||||
// ErrCodeOnlyOneDoneBucketPerProject holds the unique world-error code of this error
|
||||
const ErrCodeOnlyOneDoneBucketPerProject = 10005
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err *ErrOnlyOneDoneBucketPerList) HTTPError() web.HTTPError {
|
||||
func (err *ErrOnlyOneDoneBucketPerProject) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{
|
||||
HTTPCode: http.StatusPreconditionFailed,
|
||||
Code: ErrCodeOnlyOneDoneBucketPerList,
|
||||
Message: "There can be only one done bucket per list.",
|
||||
Code: ErrCodeOnlyOneDoneBucketPerProject,
|
||||
Message: "There can be only one done bucket per project.",
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -80,6 +80,18 @@ func (t *TaskAssigneeCreatedEvent) Name() string {
|
|||
return "task.assignee.created"
|
||||
}
|
||||
|
||||
// TaskAssigneeDeletedEvent represents a TaskAssigneeDeletedEvent event
|
||||
type TaskAssigneeDeletedEvent struct {
|
||||
Task *Task
|
||||
Assignee *user.User
|
||||
Doer *user.User
|
||||
}
|
||||
|
||||
// Name defines the name for TaskAssigneeDeletedEvent
|
||||
func (t *TaskAssigneeDeletedEvent) Name() string {
|
||||
return "task.assignee.deleted"
|
||||
}
|
||||
|
||||
// TaskCommentCreatedEvent represents an event where a task comment has been created
|
||||
type TaskCommentCreatedEvent struct {
|
||||
Task *Task
|
||||
|
@ -104,6 +116,66 @@ func (t *TaskCommentUpdatedEvent) Name() string {
|
|||
return "task.comment.edited"
|
||||
}
|
||||
|
||||
// TaskCommentDeletedEvent represents a TaskCommentDeletedEvent event
|
||||
type TaskCommentDeletedEvent struct {
|
||||
Task *Task
|
||||
Comment *TaskComment
|
||||
Doer *user.User
|
||||
}
|
||||
|
||||
// Name defines the name for TaskCommentDeletedEvent
|
||||
func (t *TaskCommentDeletedEvent) Name() string {
|
||||
return "task.comment.deleted"
|
||||
}
|
||||
|
||||
// TaskAttachmentCreatedEvent represents a TaskAttachmentCreatedEvent event
|
||||
type TaskAttachmentCreatedEvent struct {
|
||||
Task *Task
|
||||
Attachment *TaskAttachment
|
||||
Doer *user.User
|
||||
}
|
||||
|
||||
// Name defines the name for TaskAttachmentCreatedEvent
|
||||
func (t *TaskAttachmentCreatedEvent) Name() string {
|
||||
return "task.attachment.created"
|
||||
}
|
||||
|
||||
// TaskAttachmentDeletedEvent represents a TaskAttachmentDeletedEvent event
|
||||
type TaskAttachmentDeletedEvent struct {
|
||||
Task *Task
|
||||
Attachment *TaskAttachment
|
||||
Doer *user.User
|
||||
}
|
||||
|
||||
// Name defines the name for TaskAttachmentDeletedEvent
|
||||
func (t *TaskAttachmentDeletedEvent) Name() string {
|
||||
return "task.attachment.deleted"
|
||||
}
|
||||
|
||||
// TaskRelationCreatedEvent represents a TaskRelationCreatedEvent event
|
||||
type TaskRelationCreatedEvent struct {
|
||||
Task *Task
|
||||
Relation *TaskRelation
|
||||
Doer *user.User
|
||||
}
|
||||
|
||||
// Name defines the name for TaskRelationCreatedEvent
|
||||
func (t *TaskRelationCreatedEvent) Name() string {
|
||||
return "task.relation.created"
|
||||
}
|
||||
|
||||
// TaskRelationDeletedEvent represents a TaskRelationDeletedEvent event
|
||||
type TaskRelationDeletedEvent struct {
|
||||
Task *Task
|
||||
Relation *TaskRelation
|
||||
Doer *user.User
|
||||
}
|
||||
|
||||
// Name defines the name for TaskRelationDeletedEvent
|
||||
func (t *TaskRelationDeletedEvent) Name() string {
|
||||
return "task.relation.deleted"
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
// Namespace Events //
|
||||
//////////////////////
|
||||
|
@ -142,68 +214,68 @@ func (t *NamespaceDeletedEvent) Name() string {
|
|||
}
|
||||
|
||||
/////////////////
|
||||
// List Events //
|
||||
// Project Events //
|
||||
/////////////////
|
||||
|
||||
// ListCreatedEvent represents an event where a list has been created
|
||||
type ListCreatedEvent struct {
|
||||
List *List
|
||||
// ProjectCreatedEvent represents an event where a project has been created
|
||||
type ProjectCreatedEvent struct {
|
||||
Project *Project
|
||||
Doer *user.User
|
||||
}
|
||||
|
||||
// Name defines the name for ListCreatedEvent
|
||||
func (l *ListCreatedEvent) Name() string {
|
||||
return "list.created"
|
||||
// Name defines the name for ProjectCreatedEvent
|
||||
func (l *ProjectCreatedEvent) Name() string {
|
||||
return "project.created"
|
||||
}
|
||||
|
||||
// ListUpdatedEvent represents an event where a list has been updated
|
||||
type ListUpdatedEvent struct {
|
||||
List *List
|
||||
// ProjectUpdatedEvent represents an event where a project has been updated
|
||||
type ProjectUpdatedEvent struct {
|
||||
Project *Project
|
||||
Doer web.Auth
|
||||
}
|
||||
|
||||
// Name defines the name for ListUpdatedEvent
|
||||
func (l *ListUpdatedEvent) Name() string {
|
||||
return "list.updated"
|
||||
// Name defines the name for ProjectUpdatedEvent
|
||||
func (l *ProjectUpdatedEvent) Name() string {
|
||||
return "project.updated"
|
||||
}
|
||||
|
||||
// ListDeletedEvent represents an event where a list has been deleted
|
||||
type ListDeletedEvent struct {
|
||||
List *List
|
||||
// ProjectDeletedEvent represents an event where a project has been deleted
|
||||
type ProjectDeletedEvent struct {
|
||||
Project *Project
|
||||
Doer web.Auth
|
||||
}
|
||||
|
||||
// Name defines the name for ListDeletedEvent
|
||||
func (t *ListDeletedEvent) Name() string {
|
||||
return "list.deleted"
|
||||
// Name defines the name for ProjectDeletedEvent
|
||||
func (t *ProjectDeletedEvent) Name() string {
|
||||
return "project.deleted"
|
||||
}
|
||||
|
||||
////////////////////
|
||||
// Sharing Events //
|
||||
////////////////////
|
||||
|
||||
// ListSharedWithUserEvent represents an event where a list has been shared with a user
|
||||
type ListSharedWithUserEvent struct {
|
||||
List *List
|
||||
// ProjectSharedWithUserEvent represents an event where a project has been shared with a user
|
||||
type ProjectSharedWithUserEvent struct {
|
||||
Project *Project
|
||||
User *user.User
|
||||
Doer web.Auth
|
||||
}
|
||||
|
||||
// Name defines the name for ListSharedWithUserEvent
|
||||
func (l *ListSharedWithUserEvent) Name() string {
|
||||
return "list.shared.user"
|
||||
// Name defines the name for ProjectSharedWithUserEvent
|
||||
func (l *ProjectSharedWithUserEvent) Name() string {
|
||||
return "project.shared.user"
|
||||
}
|
||||
|
||||
// ListSharedWithTeamEvent represents an event where a list has been shared with a team
|
||||
type ListSharedWithTeamEvent struct {
|
||||
List *List
|
||||
// ProjectSharedWithTeamEvent represents an event where a project has been shared with a team
|
||||
type ProjectSharedWithTeamEvent struct {
|
||||
Project *Project
|
||||
Team *Team
|
||||
Doer web.Auth
|
||||
}
|
||||
|
||||
// Name defines the name for ListSharedWithTeamEvent
|
||||
func (l *ListSharedWithTeamEvent) Name() string {
|
||||
return "list.shared.team"
|
||||
// Name defines the name for ProjectSharedWithTeamEvent
|
||||
func (l *ProjectSharedWithTeamEvent) Name() string {
|
||||
return "project.shared.team"
|
||||
}
|
||||
|
||||
// NamespaceSharedWithUserEvent represents an event where a namespace has been shared with a user
|
||||
|
|
|
@ -57,7 +57,7 @@ func ExportUserData(s *xorm.Session, u *user.User) (err error) {
|
|||
defer dumpWriter.Close()
|
||||
|
||||
// Get the data
|
||||
err = exportListsAndTasks(s, u, dumpWriter)
|
||||
err = exportProjectsAndTasks(s, u, dumpWriter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -72,7 +72,7 @@ func ExportUserData(s *xorm.Session, u *user.User) (err error) {
|
|||
return err
|
||||
}
|
||||
// Background files
|
||||
err = exportListBackgrounds(s, u, dumpWriter)
|
||||
err = exportProjectBackgrounds(s, u, dumpWriter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -97,7 +97,7 @@ func ExportUserData(s *xorm.Session, u *user.User) (err error) {
|
|||
return err
|
||||
}
|
||||
|
||||
exportFile, err := files.CreateWithMimeAndSession(s, exported, tmpFilename, uint64(stat.Size()), u, "application/zip")
|
||||
exportFile, err := files.CreateWithMimeAndSession(s, exported, tmpFilename, uint64(stat.Size()), u, "application/zip", false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -121,7 +121,7 @@ func ExportUserData(s *xorm.Session, u *user.User) (err error) {
|
|||
})
|
||||
}
|
||||
|
||||
func exportListsAndTasks(s *xorm.Session, u *user.User, wr *zip.Writer) (err error) {
|
||||
func exportProjectsAndTasks(s *xorm.Session, u *user.User, wr *zip.Writer) (err error) {
|
||||
|
||||
namspaces, _, _, err := (&Namespace{IsArchived: true}).ReadAll(s, u, "", -1, 0)
|
||||
if err != nil {
|
||||
|
@ -129,29 +129,29 @@ func exportListsAndTasks(s *xorm.Session, u *user.User, wr *zip.Writer) (err err
|
|||
}
|
||||
|
||||
namespaceIDs := []int64{}
|
||||
namespaces := []*NamespaceWithListsAndTasks{}
|
||||
listMap := make(map[int64]*ListWithTasksAndBuckets)
|
||||
listIDs := []int64{}
|
||||
for _, n := range namspaces.([]*NamespaceWithLists) {
|
||||
namespaces := []*NamespaceWithProjectsAndTasks{}
|
||||
projectMap := make(map[int64]*ProjectWithTasksAndBuckets)
|
||||
projectIDs := []int64{}
|
||||
for _, n := range namspaces.([]*NamespaceWithProjects) {
|
||||
if n.ID < 1 {
|
||||
// Don't include filters
|
||||
continue
|
||||
}
|
||||
|
||||
nn := &NamespaceWithListsAndTasks{
|
||||
nn := &NamespaceWithProjectsAndTasks{
|
||||
Namespace: n.Namespace,
|
||||
Lists: []*ListWithTasksAndBuckets{},
|
||||
Projects: []*ProjectWithTasksAndBuckets{},
|
||||
}
|
||||
|
||||
for _, l := range n.Lists {
|
||||
ll := &ListWithTasksAndBuckets{
|
||||
List: *l,
|
||||
for _, l := range n.Projects {
|
||||
ll := &ProjectWithTasksAndBuckets{
|
||||
Project: *l,
|
||||
BackgroundFileID: l.BackgroundFileID,
|
||||
Tasks: []*TaskWithComments{},
|
||||
}
|
||||
nn.Lists = append(nn.Lists, ll)
|
||||
listMap[l.ID] = ll
|
||||
listIDs = append(listIDs, l.ID)
|
||||
nn.Projects = append(nn.Projects, ll)
|
||||
projectMap[l.ID] = ll
|
||||
projectIDs = append(projectIDs, l.ID)
|
||||
}
|
||||
|
||||
namespaceIDs = append(namespaceIDs, n.ID)
|
||||
|
@ -162,13 +162,13 @@ func exportListsAndTasks(s *xorm.Session, u *user.User, wr *zip.Writer) (err err
|
|||
return nil
|
||||
}
|
||||
|
||||
// Get all lists
|
||||
lists, err := getListsForNamespaces(s, namespaceIDs, true)
|
||||
// Get all projects
|
||||
projects, err := getProjectsForNamespaces(s, namespaceIDs, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tasks, _, _, err := getTasksForLists(s, lists, u, &taskOptions{
|
||||
tasks, _, _, err := getTasksForProjects(s, projects, u, &taskOptions{
|
||||
page: 0,
|
||||
perPage: -1,
|
||||
})
|
||||
|
@ -181,17 +181,17 @@ func exportListsAndTasks(s *xorm.Session, u *user.User, wr *zip.Writer) (err err
|
|||
taskMap[t.ID] = &TaskWithComments{
|
||||
Task: *t,
|
||||
}
|
||||
if _, exists := listMap[t.ListID]; !exists {
|
||||
log.Debugf("[User Data Export] List %d does not exist for task %d, omitting", t.ListID, t.ID)
|
||||
if _, exists := projectMap[t.ProjectID]; !exists {
|
||||
log.Debugf("[User Data Export] Project %d does not exist for task %d, omitting", t.ProjectID, t.ID)
|
||||
continue
|
||||
}
|
||||
listMap[t.ListID].Tasks = append(listMap[t.ListID].Tasks, taskMap[t.ID])
|
||||
projectMap[t.ProjectID].Tasks = append(projectMap[t.ProjectID].Tasks, taskMap[t.ID])
|
||||
}
|
||||
|
||||
comments := []*TaskComment{}
|
||||
err = s.
|
||||
Join("LEFT", "tasks", "tasks.id = task_comments.task_id").
|
||||
In("tasks.list_id", listIDs).
|
||||
In("tasks.project_id", projectIDs).
|
||||
Find(&comments)
|
||||
if err != nil {
|
||||
return
|
||||
|
@ -206,17 +206,17 @@ func exportListsAndTasks(s *xorm.Session, u *user.User, wr *zip.Writer) (err err
|
|||
}
|
||||
|
||||
buckets := []*Bucket{}
|
||||
err = s.In("list_id", listIDs).Find(&buckets)
|
||||
err = s.In("project_id", projectIDs).Find(&buckets)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, b := range buckets {
|
||||
if _, exists := listMap[b.ListID]; !exists {
|
||||
log.Debugf("[User Data Export] List %d does not exist for bucket %d, omitting", b.ListID, b.ID)
|
||||
if _, exists := projectMap[b.ProjectID]; !exists {
|
||||
log.Debugf("[User Data Export] Project %d does not exist for bucket %d, omitting", b.ProjectID, b.ID)
|
||||
continue
|
||||
}
|
||||
listMap[b.ListID].Buckets = append(listMap[b.ListID].Buckets, b)
|
||||
projectMap[b.ProjectID].Buckets = append(projectMap[b.ProjectID].Buckets, b)
|
||||
}
|
||||
|
||||
data, err := json.Marshal(namespaces)
|
||||
|
@ -228,9 +228,9 @@ func exportListsAndTasks(s *xorm.Session, u *user.User, wr *zip.Writer) (err err
|
|||
}
|
||||
|
||||
func exportTaskAttachments(s *xorm.Session, u *user.User, wr *zip.Writer) (err error) {
|
||||
lists, _, _, err := getRawListsForUser(
|
||||
projects, _, _, err := getRawProjectsForUser(
|
||||
s,
|
||||
&listOptions{
|
||||
&projectOptions{
|
||||
user: u,
|
||||
page: -1,
|
||||
},
|
||||
|
@ -239,7 +239,7 @@ func exportTaskAttachments(s *xorm.Session, u *user.User, wr *zip.Writer) (err e
|
|||
return err
|
||||
}
|
||||
|
||||
tasks, _, _, err := getRawTasksForLists(s, lists, u, &taskOptions{page: -1})
|
||||
tasks, _, _, err := getRawTasksForProjects(s, projects, u, &taskOptions{page: -1})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -279,10 +279,10 @@ func exportSavedFilters(s *xorm.Session, u *user.User, wr *zip.Writer) (err erro
|
|||
return utils.WriteBytesToZip("filters.json", data, wr)
|
||||
}
|
||||
|
||||
func exportListBackgrounds(s *xorm.Session, u *user.User, wr *zip.Writer) (err error) {
|
||||
lists, _, _, err := getRawListsForUser(
|
||||
func exportProjectBackgrounds(s *xorm.Session, u *user.User, wr *zip.Writer) (err error) {
|
||||
projects, _, _, err := getRawProjectsForUser(
|
||||
s,
|
||||
&listOptions{
|
||||
&projectOptions{
|
||||
user: u,
|
||||
page: -1,
|
||||
},
|
||||
|
@ -292,7 +292,7 @@ func exportListBackgrounds(s *xorm.Session, u *user.User, wr *zip.Writer) (err e
|
|||
}
|
||||
|
||||
fs := make(map[int64]io.ReadCloser)
|
||||
for _, l := range lists {
|
||||
for _, l := range projects {
|
||||
if l.BackgroundFileID == 0 {
|
||||
continue
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ type FavoriteKind int
|
|||
const (
|
||||
FavoriteKindUnknown FavoriteKind = iota
|
||||
FavoriteKindTask
|
||||
FavoriteKindList
|
||||
FavoriteKindProject
|
||||
)
|
||||
|
||||
// Favorite represents an entity which is a favorite to someone
|
||||
|
|
|
@ -31,8 +31,8 @@ type Bucket struct {
|
|||
ID int64 `xorm:"bigint autoincr not null unique pk" json:"id" param:"bucket"`
|
||||
// The title of this bucket.
|
||||
Title string `xorm:"text not null" valid:"required" minLength:"1" json:"title"`
|
||||
// The list this bucket belongs to.
|
||||
ListID int64 `xorm:"bigint not null" json:"list_id" param:"list"`
|
||||
// The project this bucket belongs to.
|
||||
ProjectID int64 `xorm:"bigint not null" json:"project_id" param:"project"`
|
||||
// All tasks which belong to this bucket.
|
||||
Tasks []*Task `xorm:"-" json:"tasks"`
|
||||
|
||||
|
@ -77,19 +77,19 @@ func getBucketByID(s *xorm.Session, id int64) (b *Bucket, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
func getDefaultBucket(s *xorm.Session, listID int64) (bucket *Bucket, err error) {
|
||||
func getDefaultBucket(s *xorm.Session, projectID int64) (bucket *Bucket, err error) {
|
||||
bucket = &Bucket{}
|
||||
_, err = s.
|
||||
Where("list_id = ?", listID).
|
||||
OrderBy("id asc").
|
||||
Where("project_id = ?", projectID).
|
||||
OrderBy("position asc").
|
||||
Get(bucket)
|
||||
return
|
||||
}
|
||||
|
||||
func getDoneBucketForList(s *xorm.Session, listID int64) (bucket *Bucket, err error) {
|
||||
func getDoneBucketForProject(s *xorm.Session, projectID int64) (bucket *Bucket, err error) {
|
||||
bucket = &Bucket{}
|
||||
exists, err := s.
|
||||
Where("list_id = ? and is_done_bucket = ?", listID, true).
|
||||
Where("project_id = ? and is_done_bucket = ?", projectID, true).
|
||||
Get(bucket)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -101,14 +101,14 @@ func getDoneBucketForList(s *xorm.Session, listID int64) (bucket *Bucket, err er
|
|||
return
|
||||
}
|
||||
|
||||
// ReadAll returns all buckets with their tasks for a certain list
|
||||
// @Summary Get all kanban buckets of a list
|
||||
// @Description Returns all kanban buckets with belong to a list including their tasks.
|
||||
// ReadAll returns all buckets with their tasks for a certain project
|
||||
// @Summary Get all kanban buckets of a project
|
||||
// @Description Returns all kanban buckets with belong to a project including their tasks.
|
||||
// @tags task
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security JWTKeyAuth
|
||||
// @Param id path int true "List Id"
|
||||
// @Param id path int true "Project Id"
|
||||
// @Param page query int false "The page number for tasks. Used for pagination. If not provided, the first page of results is returned."
|
||||
// @Param per_page query int false "The maximum number of tasks per bucket per page. This parameter is limited by the configured maximum of items per page."
|
||||
// @Param s query string false "Search tasks by task text."
|
||||
|
@ -119,15 +119,15 @@ func getDoneBucketForList(s *xorm.Session, listID int64) (bucket *Bucket, err er
|
|||
// @Param filter_include_nulls query string false "If set to true the result will include filtered fields whose value is set to `null`. Available values are `true` or `false`. Defaults to `false`."
|
||||
// @Success 200 {array} models.Bucket "The buckets with their tasks"
|
||||
// @Failure 500 {object} models.Message "Internal server error"
|
||||
// @Router /lists/{id}/buckets [get]
|
||||
// @Router /projects/{id}/buckets [get]
|
||||
func (b *Bucket) ReadAll(s *xorm.Session, auth web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, numberOfTotalItems int64, err error) {
|
||||
|
||||
list, err := GetListSimpleByID(s, b.ListID)
|
||||
project, err := GetProjectSimpleByID(s, b.ProjectID)
|
||||
if err != nil {
|
||||
return nil, 0, 0, err
|
||||
}
|
||||
|
||||
can, _, err := list.CanRead(s, auth)
|
||||
can, _, err := project.CanRead(s, auth)
|
||||
if err != nil {
|
||||
return nil, 0, 0, err
|
||||
}
|
||||
|
@ -135,10 +135,10 @@ func (b *Bucket) ReadAll(s *xorm.Session, auth web.Auth, search string, page int
|
|||
return nil, 0, 0, ErrGenericForbidden{}
|
||||
}
|
||||
|
||||
// Get all buckets for this list
|
||||
// Get all buckets for this project
|
||||
buckets := []*Bucket{}
|
||||
err = s.
|
||||
Where("list_id = ?", b.ListID).
|
||||
Where("project_id = ?", b.ProjectID).
|
||||
OrderBy("position").
|
||||
Find(&buckets)
|
||||
if err != nil {
|
||||
|
@ -202,7 +202,7 @@ func (b *Bucket) ReadAll(s *xorm.Session, auth web.Auth, search string, page int
|
|||
|
||||
opts.filters[bucketFilterIndex].value = id
|
||||
|
||||
ts, _, _, err := getRawTasksForLists(s, []*List{{ID: bucket.ListID}}, auth, opts)
|
||||
ts, _, _, err := getRawTasksForProjects(s, []*Project{{ID: bucket.ProjectID}}, auth, opts)
|
||||
if err != nil {
|
||||
return nil, 0, 0, err
|
||||
}
|
||||
|
@ -226,7 +226,7 @@ func (b *Bucket) ReadAll(s *xorm.Session, auth web.Auth, search string, page int
|
|||
for _, task := range tasks {
|
||||
// Check if the bucket exists in the map to prevent nil pointer panics
|
||||
if _, exists := bucketMap[task.BucketID]; !exists {
|
||||
log.Debugf("Tried to put task %d into bucket %d which does not exist in list %d", task.ID, task.BucketID, b.ListID)
|
||||
log.Debugf("Tried to put task %d into bucket %d which does not exist in project %d", task.ID, task.BucketID, b.ProjectID)
|
||||
continue
|
||||
}
|
||||
bucketMap[task.BucketID].Tasks = append(bucketMap[task.BucketID].Tasks, task)
|
||||
|
@ -237,18 +237,18 @@ func (b *Bucket) ReadAll(s *xorm.Session, auth web.Auth, search string, page int
|
|||
|
||||
// Create creates a new bucket
|
||||
// @Summary Create a new bucket
|
||||
// @Description Creates a new kanban bucket on a list.
|
||||
// @Description Creates a new kanban bucket on a project.
|
||||
// @tags task
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security JWTKeyAuth
|
||||
// @Param id path int true "List Id"
|
||||
// @Param id path int true "Project Id"
|
||||
// @Param bucket body models.Bucket true "The bucket object"
|
||||
// @Success 200 {object} models.Bucket "The created bucket object."
|
||||
// @Failure 400 {object} web.HTTPError "Invalid bucket object provided."
|
||||
// @Failure 404 {object} web.HTTPError "The list does not exist."
|
||||
// @Failure 404 {object} web.HTTPError "The project does not exist."
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /lists/{id}/buckets [put]
|
||||
// @Router /projects/{id}/buckets [put]
|
||||
func (b *Bucket) Create(s *xorm.Session, a web.Auth) (err error) {
|
||||
b.CreatedBy, err = GetUserOrLinkShareUser(s, a)
|
||||
if err != nil {
|
||||
|
@ -273,24 +273,24 @@ func (b *Bucket) Create(s *xorm.Session, a web.Auth) (err error) {
|
|||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security JWTKeyAuth
|
||||
// @Param listID path int true "List Id"
|
||||
// @Param projectID path int true "Project Id"
|
||||
// @Param bucketID path int true "Bucket Id"
|
||||
// @Param bucket body models.Bucket true "The bucket object"
|
||||
// @Success 200 {object} models.Bucket "The created bucket object."
|
||||
// @Failure 400 {object} web.HTTPError "Invalid bucket object provided."
|
||||
// @Failure 404 {object} web.HTTPError "The bucket does not exist."
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /lists/{listID}/buckets/{bucketID} [post]
|
||||
// @Router /projects/{projectID}/buckets/{bucketID} [post]
|
||||
func (b *Bucket) Update(s *xorm.Session, a web.Auth) (err error) {
|
||||
doneBucket, err := getDoneBucketForList(s, b.ListID)
|
||||
doneBucket, err := getDoneBucketForProject(s, b.ProjectID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if doneBucket != nil && doneBucket.IsDoneBucket && b.IsDoneBucket && doneBucket.ID != b.ID {
|
||||
return &ErrOnlyOneDoneBucketPerList{
|
||||
return &ErrOnlyOneDoneBucketPerProject{
|
||||
BucketID: b.ID,
|
||||
ListID: b.ListID,
|
||||
ProjectID: b.ProjectID,
|
||||
DoneBucketID: doneBucket.ID,
|
||||
}
|
||||
}
|
||||
|
@ -309,28 +309,28 @@ func (b *Bucket) Update(s *xorm.Session, a web.Auth) (err error) {
|
|||
|
||||
// Delete removes a bucket, but no tasks
|
||||
// @Summary Deletes an existing bucket
|
||||
// @Description Deletes an existing kanban bucket and dissociates all of its task. It does not delete any tasks. You cannot delete the last bucket on a list.
|
||||
// @Description Deletes an existing kanban bucket and dissociates all of its task. It does not delete any tasks. You cannot delete the last bucket on a project.
|
||||
// @tags task
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security JWTKeyAuth
|
||||
// @Param listID path int true "List Id"
|
||||
// @Param projectID path int true "Project Id"
|
||||
// @Param bucketID path int true "Bucket Id"
|
||||
// @Success 200 {object} models.Message "Successfully deleted."
|
||||
// @Failure 404 {object} web.HTTPError "The bucket does not exist."
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /lists/{listID}/buckets/{bucketID} [delete]
|
||||
// @Router /projects/{projectID}/buckets/{bucketID} [delete]
|
||||
func (b *Bucket) Delete(s *xorm.Session, a web.Auth) (err error) {
|
||||
|
||||
// Prevent removing the last bucket
|
||||
total, err := s.Where("list_id = ?", b.ListID).Count(&Bucket{})
|
||||
total, err := s.Where("project_id = ?", b.ProjectID).Count(&Bucket{})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if total <= 1 {
|
||||
return ErrCannotRemoveLastBucket{
|
||||
BucketID: b.ID,
|
||||
ListID: b.ListID,
|
||||
ProjectID: b.ProjectID,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -341,7 +341,7 @@ func (b *Bucket) Delete(s *xorm.Session, a web.Auth) (err error) {
|
|||
}
|
||||
|
||||
// Get the default bucket
|
||||
defaultBucket, err := getDefaultBucket(s, b.ListID)
|
||||
defaultBucket, err := getDefaultBucket(s, b.ProjectID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ import (
|
|||
|
||||
// CanCreate checks if a user can create a new bucket
|
||||
func (b *Bucket) CanCreate(s *xorm.Session, a web.Auth) (bool, error) {
|
||||
l := &List{ID: b.ListID}
|
||||
l := &Project{ID: b.ProjectID}
|
||||
return l.CanWrite(s, a)
|
||||
}
|
||||
|
||||
|
@ -43,6 +43,6 @@ func (b *Bucket) canDoBucket(s *xorm.Session, a web.Auth) (bool, error) {
|
|||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
l := &List{ID: bb.ListID}
|
||||
l := &Project{ID: bb.ProjectID}
|
||||
return l.CanWrite(s, a)
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ func TestBucket_ReadAll(t *testing.T) {
|
|||
defer s.Close()
|
||||
|
||||
testuser := &user.User{ID: 1}
|
||||
b := &Bucket{ListID: 1}
|
||||
b := &Bucket{ProjectID: 1}
|
||||
bucketsInterface, _, _, err := b.ReadAll(s, testuser, "", 0, 0)
|
||||
assert.NoError(t, err)
|
||||
|
||||
|
@ -49,22 +49,22 @@ func TestBucket_ReadAll(t *testing.T) {
|
|||
assert.Len(t, buckets, 3)
|
||||
|
||||
// Assert all tasks are in the right bucket
|
||||
assert.Len(t, buckets[0].Tasks, 3)
|
||||
assert.Len(t, buckets[1].Tasks, 12)
|
||||
assert.Len(t, buckets[0].Tasks, 12)
|
||||
assert.Len(t, buckets[1].Tasks, 3)
|
||||
assert.Len(t, buckets[2].Tasks, 3)
|
||||
|
||||
// Assert we have bucket 1, 2, 3 but not 4 (that belongs to a different list) and their position
|
||||
assert.Equal(t, int64(2), buckets[0].ID)
|
||||
assert.Equal(t, int64(1), buckets[1].ID)
|
||||
// Assert we have bucket 1, 2, 3 but not 4 (that belongs to a different project) and their position
|
||||
assert.Equal(t, int64(1), buckets[0].ID)
|
||||
assert.Equal(t, int64(2), buckets[1].ID)
|
||||
assert.Equal(t, int64(3), buckets[2].ID)
|
||||
|
||||
// Kinda assert all tasks are in the right buckets
|
||||
assert.Equal(t, int64(1), buckets[1].Tasks[0].BucketID)
|
||||
assert.Equal(t, int64(1), buckets[1].Tasks[1].BucketID)
|
||||
assert.Equal(t, int64(1), buckets[0].Tasks[0].BucketID)
|
||||
assert.Equal(t, int64(1), buckets[0].Tasks[1].BucketID)
|
||||
|
||||
assert.Equal(t, int64(2), buckets[0].Tasks[0].BucketID)
|
||||
assert.Equal(t, int64(2), buckets[0].Tasks[1].BucketID)
|
||||
assert.Equal(t, int64(2), buckets[0].Tasks[2].BucketID)
|
||||
assert.Equal(t, int64(2), buckets[1].Tasks[0].BucketID)
|
||||
assert.Equal(t, int64(2), buckets[1].Tasks[1].BucketID)
|
||||
assert.Equal(t, int64(2), buckets[1].Tasks[2].BucketID)
|
||||
|
||||
assert.Equal(t, int64(3), buckets[2].Tasks[0].BucketID)
|
||||
assert.Equal(t, int64(3), buckets[2].Tasks[1].BucketID)
|
||||
|
@ -77,7 +77,7 @@ func TestBucket_ReadAll(t *testing.T) {
|
|||
|
||||
testuser := &user.User{ID: 1}
|
||||
b := &Bucket{
|
||||
ListID: 1,
|
||||
ProjectID: 1,
|
||||
TaskCollection: TaskCollection{
|
||||
FilterBy: []string{"title"},
|
||||
FilterComparator: []string{"like"},
|
||||
|
@ -89,8 +89,8 @@ func TestBucket_ReadAll(t *testing.T) {
|
|||
|
||||
buckets := bucketsInterface.([]*Bucket)
|
||||
assert.Len(t, buckets, 3)
|
||||
assert.Equal(t, int64(2), buckets[1].Tasks[0].ID)
|
||||
assert.Equal(t, int64(33), buckets[1].Tasks[1].ID)
|
||||
assert.Equal(t, int64(2), buckets[0].Tasks[0].ID)
|
||||
assert.Equal(t, int64(33), buckets[0].Tasks[1].ID)
|
||||
})
|
||||
t.Run("accessed by link share", func(t *testing.T) {
|
||||
db.LoadAndAssertFixtures(t)
|
||||
|
@ -99,10 +99,10 @@ func TestBucket_ReadAll(t *testing.T) {
|
|||
|
||||
linkShare := &LinkSharing{
|
||||
ID: 1,
|
||||
ListID: 1,
|
||||
ProjectID: 1,
|
||||
Right: RightRead,
|
||||
}
|
||||
b := &Bucket{ListID: 1}
|
||||
b := &Bucket{ProjectID: 1}
|
||||
result, _, _, err := b.ReadAll(s, linkShare, "", 0, 0)
|
||||
assert.NoError(t, err)
|
||||
buckets, _ := result.([]*Bucket)
|
||||
|
@ -116,7 +116,7 @@ func TestBucket_ReadAll(t *testing.T) {
|
|||
defer s.Close()
|
||||
|
||||
testuser := &user.User{ID: 12}
|
||||
b := &Bucket{ListID: 23}
|
||||
b := &Bucket{ProjectID: 23}
|
||||
result, _, _, err := b.ReadAll(s, testuser, "", 0, 0)
|
||||
assert.NoError(t, err)
|
||||
buckets, _ := result.([]*Bucket)
|
||||
|
@ -136,7 +136,7 @@ func TestBucket_Delete(t *testing.T) {
|
|||
|
||||
b := &Bucket{
|
||||
ID: 2, // The second bucket only has 3 tasks
|
||||
ListID: 1,
|
||||
ProjectID: 1,
|
||||
}
|
||||
err := b.Delete(s, user)
|
||||
assert.NoError(t, err)
|
||||
|
@ -147,20 +147,20 @@ func TestBucket_Delete(t *testing.T) {
|
|||
tasks := []*Task{}
|
||||
err = s.Where("bucket_id = ?", 1).Find(&tasks)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, tasks, 15)
|
||||
assert.Len(t, tasks, 16)
|
||||
db.AssertMissing(t, "buckets", map[string]interface{}{
|
||||
"id": 2,
|
||||
"list_id": 1,
|
||||
"project_id": 1,
|
||||
})
|
||||
})
|
||||
t.Run("last bucket in list", func(t *testing.T) {
|
||||
t.Run("last bucket in project", func(t *testing.T) {
|
||||
db.LoadAndAssertFixtures(t)
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
b := &Bucket{
|
||||
ID: 34,
|
||||
ListID: 18,
|
||||
ProjectID: 18,
|
||||
}
|
||||
err := b.Delete(s, user)
|
||||
assert.Error(t, err)
|
||||
|
@ -170,7 +170,7 @@ func TestBucket_Delete(t *testing.T) {
|
|||
|
||||
db.AssertExists(t, "buckets", map[string]interface{}{
|
||||
"id": 34,
|
||||
"list_id": 18,
|
||||
"project_id": 18,
|
||||
}, false)
|
||||
})
|
||||
}
|
||||
|
@ -217,19 +217,19 @@ func TestBucket_Update(t *testing.T) {
|
|||
|
||||
testAndAssertBucketUpdate(t, b, s)
|
||||
})
|
||||
t.Run("only one done bucket per list", func(t *testing.T) {
|
||||
t.Run("only one done bucket per project", func(t *testing.T) {
|
||||
db.LoadAndAssertFixtures(t)
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
b := &Bucket{
|
||||
ID: 1,
|
||||
ListID: 1,
|
||||
ProjectID: 1,
|
||||
IsDoneBucket: true,
|
||||
}
|
||||
|
||||
err := b.Update(s, &user.User{ID: 1})
|
||||
assert.Error(t, err)
|
||||
assert.True(t, IsErrOnlyOneDoneBucketPerList(err))
|
||||
assert.True(t, IsErrOnlyOneDoneBucketPerProject(err))
|
||||
})
|
||||
}
|
||||
|
|
|
@ -151,8 +151,8 @@ func (l *Label) ReadAll(s *xorm.Session, a web.Auth, search string, page int, pe
|
|||
return nil, 0, 0, err
|
||||
}
|
||||
|
||||
return getLabelsByTaskIDs(s, &LabelByTaskIDsOptions{
|
||||
Search: search,
|
||||
return GetLabelsByTaskIDs(s, &LabelByTaskIDsOptions{
|
||||
Search: []string{search},
|
||||
User: u,
|
||||
GetForUser: u.ID,
|
||||
Page: page,
|
||||
|
@ -178,13 +178,13 @@ func (l *Label) ReadAll(s *xorm.Session, a web.Auth, search string, page int, pe
|
|||
func (l *Label) ReadOne(s *xorm.Session, a web.Auth) (err error) {
|
||||
label, err := getLabelByIDSimple(s, l.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
return
|
||||
}
|
||||
*l = *label
|
||||
|
||||
u, err := user.GetUserByID(s, l.CreatedByID)
|
||||
if err != nil {
|
||||
return err
|
||||
return
|
||||
}
|
||||
|
||||
l.CreatedBy = u
|
||||
|
@ -192,14 +192,16 @@ func (l *Label) ReadOne(s *xorm.Session, a web.Auth) (err error) {
|
|||
}
|
||||
|
||||
func getLabelByIDSimple(s *xorm.Session, labelID int64) (*Label, error) {
|
||||
label := Label{}
|
||||
exists, err := s.ID(labelID).Get(&label)
|
||||
if err != nil {
|
||||
return &label, err
|
||||
}
|
||||
|
||||
if !exists {
|
||||
return &Label{}, ErrLabelDoesNotExist{labelID}
|
||||
}
|
||||
return &label, err
|
||||
return GetLabelSimple(s, &Label{ID: labelID})
|
||||
}
|
||||
|
||||
func GetLabelSimple(s *xorm.Session, l *Label) (*Label, error) {
|
||||
exists, err := s.Get(l)
|
||||
if err != nil {
|
||||
return l, err
|
||||
}
|
||||
if !exists {
|
||||
return &Label{}, ErrLabelDoesNotExist{l.ID}
|
||||
}
|
||||
return l, err
|
||||
}
|
||||
|
|
|
@ -77,7 +77,7 @@ func (l *Label) hasAccessToLabel(s *xorm.Session, a web.Auth) (has bool, maxRigh
|
|||
builder.
|
||||
Select("id").
|
||||
From("tasks").
|
||||
Where(builder.In("list_id", getUserListsStatement(u.ID).Select("l.id"))),
|
||||
Where(builder.In("project_id", getUserProjectsStatement(u.ID).Select("l.id"))),
|
||||
)
|
||||
|
||||
ll := &LabelTask{}
|
||||
|
|
|
@ -35,7 +35,7 @@ import (
|
|||
type LabelTask struct {
|
||||
// The unique, numeric id of this label.
|
||||
ID int64 `xorm:"bigint autoincr not null unique pk" json:"-"`
|
||||
TaskID int64 `xorm:"bigint INDEX not null" json:"-" param:"listtask"`
|
||||
TaskID int64 `xorm:"bigint INDEX not null" json:"-" param:"projecttask"`
|
||||
// The label id you want to associate with a task.
|
||||
LabelID int64 `xorm:"bigint INDEX not null" json:"label_id" param:"label"`
|
||||
// A timestamp when this task was created. You cannot change this value.
|
||||
|
@ -52,7 +52,7 @@ func (LabelTask) TableName() string {
|
|||
|
||||
// Delete deletes a label on a task
|
||||
// @Summary Remove a label from a task
|
||||
// @Description Remove a label from a task. The user needs to have write-access to the list to be able do this.
|
||||
// @Description Remove a label from a task. The user needs to have write-access to the project to be able do this.
|
||||
// @tags labels
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
|
@ -71,7 +71,7 @@ func (lt *LabelTask) Delete(s *xorm.Session, a web.Auth) (err error) {
|
|||
|
||||
// Create adds a label to a task
|
||||
// @Summary Add a label to a task
|
||||
// @Description Add a label to a task. The user needs to have write-access to the list to be able do this.
|
||||
// @Description Add a label to a task. The user needs to have write-access to the project to be able do this.
|
||||
// @tags labels
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
|
@ -100,7 +100,7 @@ func (lt *LabelTask) Create(s *xorm.Session, a web.Auth) (err error) {
|
|||
return err
|
||||
}
|
||||
|
||||
err = updateListByTaskID(s, lt.TaskID)
|
||||
err = updateProjectByTaskID(s, lt.TaskID)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -129,16 +129,16 @@ func (lt *LabelTask) ReadAll(s *xorm.Session, a web.Auth, search string, page in
|
|||
return nil, 0, 0, ErrNoRightToSeeTask{lt.TaskID, a.GetID()}
|
||||
}
|
||||
|
||||
return getLabelsByTaskIDs(s, &LabelByTaskIDsOptions{
|
||||
return GetLabelsByTaskIDs(s, &LabelByTaskIDsOptions{
|
||||
User: &user.User{ID: a.GetID()},
|
||||
Search: search,
|
||||
Search: []string{search},
|
||||
Page: page,
|
||||
TaskIDs: []int64{lt.TaskID},
|
||||
})
|
||||
}
|
||||
|
||||
// Helper struct, contains the label + its task ID
|
||||
type labelWithTaskID struct {
|
||||
// LabelWithTaskID is a helper struct, contains the label + its task ID
|
||||
type LabelWithTaskID struct {
|
||||
TaskID int64 `json:"-"`
|
||||
Label `xorm:"extends"`
|
||||
}
|
||||
|
@ -146,7 +146,7 @@ type labelWithTaskID struct {
|
|||
// LabelByTaskIDsOptions is a struct to not clutter the function with too many optional parameters.
|
||||
type LabelByTaskIDsOptions struct {
|
||||
User *user.User
|
||||
Search string
|
||||
Search []string
|
||||
Page int
|
||||
PerPage int
|
||||
TaskIDs []int64
|
||||
|
@ -155,9 +155,9 @@ type LabelByTaskIDsOptions struct {
|
|||
GetForUser int64
|
||||
}
|
||||
|
||||
// Helper function to get all labels for a set of tasks
|
||||
// GetLabelsByTaskIDs is a helper function to get all labels for a set of tasks
|
||||
// Used when getting all labels for one task as well when getting all lables
|
||||
func getLabelsByTaskIDs(s *xorm.Session, opts *LabelByTaskIDsOptions) (ls []*labelWithTaskID, resultCount int, totalEntries int64, err error) {
|
||||
func GetLabelsByTaskIDs(s *xorm.Session, opts *LabelByTaskIDsOptions) (ls []*LabelWithTaskID, resultCount int, totalEntries int64, err error) {
|
||||
// We still need the task ID when we want to get all labels for a task, but because of this, we get the same label
|
||||
// multiple times when it is associated to more than one task.
|
||||
// Because of this whole thing, we need this extra switch here to only group by Task IDs if needed.
|
||||
|
@ -170,7 +170,7 @@ func getLabelsByTaskIDs(s *xorm.Session, opts *LabelByTaskIDsOptions) (ls []*lab
|
|||
}
|
||||
|
||||
// Get all labels associated with these tasks
|
||||
var labels []*labelWithTaskID
|
||||
var labels []*LabelWithTaskID
|
||||
cond := builder.And(builder.NotNull{"label_tasks.label_id"})
|
||||
if len(opts.TaskIDs) > 0 && opts.GetForUser == 0 {
|
||||
cond = builder.And(builder.In("label_tasks.task_id", opts.TaskIDs), cond)
|
||||
|
@ -180,7 +180,7 @@ func getLabelsByTaskIDs(s *xorm.Session, opts *LabelByTaskIDsOptions) (ls []*lab
|
|||
builder.
|
||||
Select("id").
|
||||
From("tasks").
|
||||
Where(builder.In("list_id", getUserListsStatement(opts.GetForUser).Select("l.id"))),
|
||||
Where(builder.In("project_id", getUserProjectsStatement(opts.GetForUser).Select("l.id"))),
|
||||
), cond)
|
||||
}
|
||||
if opts.GetUnusedLabels {
|
||||
|
@ -188,8 +188,14 @@ func getLabelsByTaskIDs(s *xorm.Session, opts *LabelByTaskIDsOptions) (ls []*lab
|
|||
}
|
||||
|
||||
ids := []int64{}
|
||||
if opts.Search != "" {
|
||||
vals := strings.Split(opts.Search, ",")
|
||||
|
||||
for _, search := range opts.Search {
|
||||
search = strings.Trim(search, " ")
|
||||
if search == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
vals := strings.Split(search, ",")
|
||||
for _, val := range vals {
|
||||
v, err := strconv.ParseInt(val, 10, 64)
|
||||
if err != nil {
|
||||
|
@ -202,8 +208,19 @@ func getLabelsByTaskIDs(s *xorm.Session, opts *LabelByTaskIDsOptions) (ls []*lab
|
|||
|
||||
if len(ids) > 0 {
|
||||
cond = builder.And(cond, builder.In("labels.id", ids))
|
||||
} else {
|
||||
cond = builder.And(cond, db.ILIKE("labels.title", opts.Search))
|
||||
} else if len(opts.Search) > 0 {
|
||||
|
||||
var searchcond builder.Cond
|
||||
for _, search := range opts.Search {
|
||||
search = strings.Trim(search, " ")
|
||||
if search == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
searchcond = builder.Or(searchcond, db.ILIKE("labels.title", search))
|
||||
}
|
||||
|
||||
cond = builder.And(cond, searchcond)
|
||||
}
|
||||
|
||||
limit, start := getLimitFromPageIndex(opts.Page, opts.PerPage)
|
||||
|
@ -254,7 +271,6 @@ func getLabelsByTaskIDs(s *xorm.Session, opts *LabelByTaskIDsOptions) (ls []*lab
|
|||
Select("count(DISTINCT labels.id)").
|
||||
Join("LEFT", "label_tasks", "label_tasks.label_id = labels.id").
|
||||
Where(cond).
|
||||
And("labels.title LIKE ?", "%"+opts.Search+"%").
|
||||
Count(&Label{})
|
||||
if err != nil {
|
||||
return nil, 0, 0, err
|
||||
|
@ -264,7 +280,7 @@ func getLabelsByTaskIDs(s *xorm.Session, opts *LabelByTaskIDsOptions) (ls []*lab
|
|||
}
|
||||
|
||||
// Create or update a bunch of task labels
|
||||
func (t *Task) updateTaskLabels(s *xorm.Session, creator web.Auth, labels []*Label) (err error) {
|
||||
func (t *Task) UpdateTaskLabels(s *xorm.Session, creator web.Auth, labels []*Label) (err error) {
|
||||
|
||||
// If we don't have any new labels, delete everything right away. Saves us some hassle.
|
||||
if len(labels) == 0 && len(t.Labels) > 0 {
|
||||
|
@ -293,17 +309,17 @@ func (t *Task) updateTaskLabels(s *xorm.Session, creator web.Auth, labels []*Lab
|
|||
for _, oldLabel := range allLabels {
|
||||
found = false
|
||||
if newLabels[oldLabel.ID] != nil {
|
||||
found = true // If a new label is already in the list with old labels
|
||||
found = true // If a new label is already in the project with old labels
|
||||
}
|
||||
|
||||
// Put all labels which are only on the old list to the trash
|
||||
// Put all labels which are only on the old project to the trash
|
||||
if !found {
|
||||
labelsToDelete = append(labelsToDelete, oldLabel.ID)
|
||||
} else {
|
||||
t.Labels = append(t.Labels, oldLabel)
|
||||
}
|
||||
|
||||
// Put it in a list with all old labels, just using the loop here
|
||||
// Put it in a project with all old labels, just using the loop here
|
||||
oldLabels[oldLabel.ID] = oldLabel
|
||||
}
|
||||
|
||||
|
@ -349,7 +365,7 @@ func (t *Task) updateTaskLabels(s *xorm.Session, creator web.Auth, labels []*Lab
|
|||
t.Labels = append(t.Labels, label)
|
||||
}
|
||||
|
||||
err = updateListLastUpdated(s, &List{ID: t.ListID})
|
||||
err = updateProjectLastUpdated(s, &Project{ID: t.ProjectID})
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -357,7 +373,7 @@ func (t *Task) updateTaskLabels(s *xorm.Session, creator web.Auth, labels []*Lab
|
|||
type LabelTaskBulk struct {
|
||||
// All labels you want to update at once.
|
||||
Labels []*Label `json:"labels"`
|
||||
TaskID int64 `json:"-" param:"listtask"`
|
||||
TaskID int64 `json:"-" param:"projecttask"`
|
||||
|
||||
web.CRUDable `json:"-"`
|
||||
web.Rights `json:"-"`
|
||||
|
@ -381,7 +397,7 @@ func (ltb *LabelTaskBulk) Create(s *xorm.Session, a web.Auth) (err error) {
|
|||
if err != nil {
|
||||
return
|
||||
}
|
||||
labels, _, _, err := getLabelsByTaskIDs(s, &LabelByTaskIDsOptions{
|
||||
labels, _, _, err := GetLabelsByTaskIDs(s, &LabelByTaskIDsOptions{
|
||||
TaskIDs: []int64{ltb.TaskID},
|
||||
})
|
||||
if err != nil {
|
||||
|
@ -390,5 +406,5 @@ func (ltb *LabelTaskBulk) Create(s *xorm.Session, a web.Auth) (err error) {
|
|||
for _, l := range labels {
|
||||
task.Labels = append(task.Labels, &l.Label)
|
||||
}
|
||||
return task.updateTaskLabels(s, a, ltb.Labels)
|
||||
return task.UpdateTaskLabels(s, a, ltb.Labels)
|
||||
}
|
||||
|
|
|
@ -78,7 +78,7 @@ func TestLabelTask_ReadAll(t *testing.T) {
|
|||
args: args{
|
||||
a: &user.User{ID: 1},
|
||||
},
|
||||
wantLabels: []*labelWithTaskID{
|
||||
wantLabels: []*LabelWithTaskID{
|
||||
{
|
||||
TaskID: 1,
|
||||
Label: label,
|
||||
|
@ -116,7 +116,7 @@ func TestLabelTask_ReadAll(t *testing.T) {
|
|||
a: &user.User{ID: 1},
|
||||
search: "VISIBLE",
|
||||
},
|
||||
wantLabels: []*labelWithTaskID{
|
||||
wantLabels: []*LabelWithTaskID{
|
||||
{
|
||||
TaskID: 1,
|
||||
Label: label,
|
||||
|
|
|
@ -70,7 +70,7 @@ func TestLabel_ReadAll(t *testing.T) {
|
|||
args: args{
|
||||
a: &user.User{ID: 1},
|
||||
},
|
||||
wantLs: []*labelWithTaskID{
|
||||
wantLs: []*LabelWithTaskID{
|
||||
{
|
||||
Label: Label{
|
||||
ID: 1,
|
||||
|
|
|
@ -42,17 +42,17 @@ const (
|
|||
SharingTypeWithPassword
|
||||
)
|
||||
|
||||
// LinkSharing represents a shared list
|
||||
// LinkSharing represents a shared project
|
||||
type LinkSharing struct {
|
||||
// The ID of the shared thing
|
||||
ID int64 `xorm:"bigint autoincr not null unique pk" json:"id" param:"share"`
|
||||
// The public id to get this shared list
|
||||
// The public id to get this shared project
|
||||
Hash string `xorm:"varchar(40) not null unique" json:"hash" param:"hash"`
|
||||
// The name of this link share. All actions someone takes while being authenticated with that link will appear with that name.
|
||||
Name string `xorm:"text null" json:"name"`
|
||||
// The ID of the shared list
|
||||
ListID int64 `xorm:"bigint not null" json:"-" param:"list"`
|
||||
// The right this list is shared with. 0 = Read only, 1 = Read & Write, 2 = Admin. See the docs for more details.
|
||||
// The ID of the shared project
|
||||
ProjectID int64 `xorm:"bigint not null" json:"-" param:"project"`
|
||||
// The right this project is shared with. 0 = Read only, 1 = Read & Write, 2 = Admin. See the docs for more details.
|
||||
Right Right `xorm:"bigint INDEX not null default 0" json:"right" valid:"length(0|2)" maximum:"2" default:"0"`
|
||||
|
||||
// The kind of this link. 0 = undefined, 1 = without password, 2 = with password.
|
||||
|
@ -61,11 +61,11 @@ type LinkSharing struct {
|
|||
// The password of this link share. You can only set it, not retrieve it after the link share has been created.
|
||||
Password string `xorm:"text null" json:"password"`
|
||||
|
||||
// The user who shared this list
|
||||
// The user who shared this project
|
||||
SharedBy *user.User `xorm:"-" json:"shared_by"`
|
||||
SharedByID int64 `xorm:"bigint INDEX not null" json:"-"`
|
||||
|
||||
// A timestamp when this list was shared. You cannot change this value.
|
||||
// A timestamp when this project was shared. You cannot change this value.
|
||||
Created time.Time `xorm:"created not null" json:"created"`
|
||||
// A timestamp when this share was last updated. You cannot change this value.
|
||||
Updated time.Time `xorm:"updated not null" json:"updated"`
|
||||
|
@ -89,7 +89,7 @@ func GetLinkShareFromClaims(claims jwt.MapClaims) (share *LinkSharing, err error
|
|||
share = &LinkSharing{}
|
||||
share.ID = int64(claims["id"].(float64))
|
||||
share.Hash = claims["hash"].(string)
|
||||
share.ListID = int64(claims["list_id"].(float64))
|
||||
share.ProjectID = int64(claims["project_id"].(float64))
|
||||
share.Right = Right(claims["right"].(float64))
|
||||
share.SharedByID = int64(claims["sharedByID"].(float64))
|
||||
return
|
||||
|
@ -114,21 +114,21 @@ func (share *LinkSharing) toUser() *user.User {
|
|||
}
|
||||
}
|
||||
|
||||
// Create creates a new link share for a given list
|
||||
// @Summary Share a list via link
|
||||
// @Description Share a list via link. The user needs to have write-access to the list to be able do this.
|
||||
// Create creates a new link share for a given project
|
||||
// @Summary Share a project via link
|
||||
// @Description Share a project via link. The user needs to have write-access to the project to be able do this.
|
||||
// @tags sharing
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security JWTKeyAuth
|
||||
// @Param list path int true "List ID"
|
||||
// @Param project path int true "Project ID"
|
||||
// @Param label body models.LinkSharing true "The new link share object"
|
||||
// @Success 201 {object} models.LinkSharing "The created link share object."
|
||||
// @Failure 400 {object} web.HTTPError "Invalid link share object provided."
|
||||
// @Failure 403 {object} web.HTTPError "Not allowed to add the list share."
|
||||
// @Failure 404 {object} web.HTTPError "The list does not exist."
|
||||
// @Failure 403 {object} web.HTTPError "Not allowed to add the project share."
|
||||
// @Failure 404 {object} web.HTTPError "The project does not exist."
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /lists/{list}/shares [put]
|
||||
// @Router /projects/{project}/shares [put]
|
||||
func (share *LinkSharing) Create(s *xorm.Session, a web.Auth) (err error) {
|
||||
|
||||
err = share.Right.isValid()
|
||||
|
@ -156,48 +156,48 @@ func (share *LinkSharing) Create(s *xorm.Session, a web.Auth) (err error) {
|
|||
}
|
||||
|
||||
// ReadOne returns one share
|
||||
// @Summary Get one link shares for a list
|
||||
// @Summary Get one link shares for a project
|
||||
// @Description Returns one link share by its ID.
|
||||
// @tags sharing
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param list path int true "List ID"
|
||||
// @Param project path int true "Project ID"
|
||||
// @Param share path int true "Share ID"
|
||||
// @Security JWTKeyAuth
|
||||
// @Success 200 {object} models.LinkSharing "The share links"
|
||||
// @Failure 403 {object} web.HTTPError "No access to the list"
|
||||
// @Failure 403 {object} web.HTTPError "No access to the project"
|
||||
// @Failure 404 {object} web.HTTPError "Share Link not found."
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /lists/{list}/shares/{share} [get]
|
||||
// @Router /projects/{project}/shares/{share} [get]
|
||||
func (share *LinkSharing) ReadOne(s *xorm.Session, a web.Auth) (err error) {
|
||||
exists, err := s.Where("id = ?", share.ID).Get(share)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !exists {
|
||||
return ErrListShareDoesNotExist{ID: share.ID, Hash: share.Hash}
|
||||
return ErrProjectShareDoesNotExist{ID: share.ID, Hash: share.Hash}
|
||||
}
|
||||
share.Password = ""
|
||||
return
|
||||
}
|
||||
|
||||
// ReadAll returns all shares for a given list
|
||||
// @Summary Get all link shares for a list
|
||||
// @Description Returns all link shares which exist for a given list
|
||||
// ReadAll returns all shares for a given project
|
||||
// @Summary Get all link shares for a project
|
||||
// @Description Returns all link shares which exist for a given project
|
||||
// @tags sharing
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param list path int true "List ID"
|
||||
// @Param project path int true "Project ID"
|
||||
// @Param page query int false "The page number. Used for pagination. If not provided, the first page of results is returned."
|
||||
// @Param per_page query int false "The maximum number of items per page. Note this parameter is limited by the configured maximum of items per page."
|
||||
// @Param s query string false "Search shares by hash."
|
||||
// @Security JWTKeyAuth
|
||||
// @Success 200 {array} models.LinkSharing "The share links"
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /lists/{list}/shares [get]
|
||||
// @Router /projects/{project}/shares [get]
|
||||
func (share *LinkSharing) ReadAll(s *xorm.Session, a web.Auth, search string, page int, perPage int) (result interface{}, resultCount int, totalItems int64, err error) {
|
||||
list := &List{ID: share.ListID}
|
||||
can, _, err := list.CanRead(s, a)
|
||||
project := &Project{ID: share.ProjectID}
|
||||
can, _, err := project.CanRead(s, a)
|
||||
if err != nil {
|
||||
return nil, 0, 0, err
|
||||
}
|
||||
|
@ -210,7 +210,7 @@ func (share *LinkSharing) ReadAll(s *xorm.Session, a web.Auth, search string, pa
|
|||
var shares []*LinkSharing
|
||||
query := s.
|
||||
Where(builder.And(
|
||||
builder.Eq{"list_id": share.ListID},
|
||||
builder.Eq{"project_id": share.ProjectID},
|
||||
builder.Or(
|
||||
db.ILIKE("hash", search),
|
||||
db.ILIKE("name", search),
|
||||
|
@ -246,7 +246,7 @@ func (share *LinkSharing) ReadAll(s *xorm.Session, a web.Auth, search string, pa
|
|||
|
||||
// Total count
|
||||
totalItems, err = s.
|
||||
Where("list_id = ? AND hash LIKE ?", share.ListID, "%"+search+"%").
|
||||
Where("project_id = ? AND hash LIKE ?", share.ProjectID, "%"+search+"%").
|
||||
Count(&LinkSharing{})
|
||||
if err != nil {
|
||||
return nil, 0, 0, err
|
||||
|
@ -257,18 +257,18 @@ func (share *LinkSharing) ReadAll(s *xorm.Session, a web.Auth, search string, pa
|
|||
|
||||
// Delete removes a link share
|
||||
// @Summary Remove a link share
|
||||
// @Description Remove a link share. The user needs to have write-access to the list to be able do this.
|
||||
// @Description Remove a link share. The user needs to have write-access to the project to be able do this.
|
||||
// @tags sharing
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security JWTKeyAuth
|
||||
// @Param list path int true "List ID"
|
||||
// @Param project path int true "Project ID"
|
||||
// @Param share path int true "Share Link ID"
|
||||
// @Success 200 {object} models.Message "The link was successfully removed."
|
||||
// @Failure 403 {object} web.HTTPError "Not allowed to remove the link."
|
||||
// @Failure 404 {object} web.HTTPError "Share Link not found."
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /lists/{list}/shares/{share} [delete]
|
||||
// @Router /projects/{project}/shares/{share} [delete]
|
||||
func (share *LinkSharing) Delete(s *xorm.Session, a web.Auth) (err error) {
|
||||
_, err = s.Where("id = ?", share.ID).Delete(share)
|
||||
return
|
||||
|
@ -282,19 +282,19 @@ func GetLinkShareByHash(s *xorm.Session, hash string) (share *LinkSharing, err e
|
|||
return
|
||||
}
|
||||
if !has {
|
||||
return share, ErrListShareDoesNotExist{Hash: hash}
|
||||
return share, ErrProjectShareDoesNotExist{Hash: hash}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetListByShareHash returns a link share by its hash
|
||||
func GetListByShareHash(s *xorm.Session, hash string) (list *List, err error) {
|
||||
// GetProjectByShareHash returns a link share by its hash
|
||||
func GetProjectByShareHash(s *xorm.Session, hash string) (project *Project, err error) {
|
||||
share, err := GetLinkShareByHash(s, hash)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
list, err = GetListSimpleByID(s, share.ListID)
|
||||
project, err = GetProjectSimpleByID(s, share.ProjectID)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -306,7 +306,7 @@ func GetLinkShareByID(s *xorm.Session, id int64) (share *LinkSharing, err error)
|
|||
return
|
||||
}
|
||||
if !has {
|
||||
return share, ErrListShareDoesNotExist{ID: id}
|
||||
return share, ErrProjectShareDoesNotExist{ID: id}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ func (share *LinkSharing) CanRead(s *xorm.Session, a web.Auth) (bool, int, error
|
|||
return false, 0, nil
|
||||
}
|
||||
|
||||
l, err := GetListByShareHash(s, share.Hash)
|
||||
l, err := GetProjectByShareHash(s, share.Hash)
|
||||
if err != nil {
|
||||
return false, 0, err
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ func (share *LinkSharing) canDoLinkShare(s *xorm.Session, a web.Auth) (bool, err
|
|||
return false, nil
|
||||
}
|
||||
|
||||
l, err := GetListSimpleByID(s, share.ListID)
|
||||
l, err := GetProjectSimpleByID(s, share.ProjectID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user