Compare commits
438 Commits
v0.11
...
release/0.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be88c69c04 | ||
|
|
27e39d2563 | ||
|
|
cfa51c63c2 | ||
|
|
eaa9b18d0e | ||
|
|
d92c469026 | ||
|
|
eb6327a147 | ||
|
|
279c5f6b73 | ||
|
|
a840880692 | ||
|
|
4bb6842fb3 | ||
|
|
adf1172c39 | ||
|
|
f0af97e411 | ||
|
|
ea5dd87b12 | ||
|
|
ac23536c36 | ||
|
|
787044628f | ||
|
|
c842b70cb5 | ||
|
|
456af4d4a9 | ||
|
|
8c97f45cb7 | ||
|
|
b8312469d7 | ||
|
|
be7b53b9bc | ||
|
|
d6cb4f94f6 | ||
|
|
9baf6d39bd | ||
|
|
8d1a09b5a2 | ||
|
|
fa68e89c04 | ||
|
|
2d4e2e452c | ||
|
|
111efd5fae | ||
|
|
03e4bf30c2 | ||
|
|
18325e964d | ||
|
|
92bcce3f7c | ||
|
|
9508d0faee | ||
|
|
90817b6dae | ||
|
|
b2e4fde63a | ||
|
|
7dc2abb8e5 | ||
|
|
27c44d6d5e | ||
|
|
8a6f8922f8 | ||
|
|
b0d4902406 | ||
|
|
f15a8baee3 | ||
|
|
0bd2632d29 | ||
|
|
8d739b2cf9 | ||
|
|
ccfa019870 | ||
|
|
969e467f48 | ||
|
|
5281ca548b | ||
|
|
f5b5204776 | ||
|
|
25deb24849 | ||
|
|
d91c2e10b0 | ||
|
|
9f3d898150 | ||
|
|
a7c585e086 | ||
|
|
00ed5884b4 | ||
|
|
90ae940a6b | ||
|
|
e4de700a10 | ||
|
|
1e020c9c50 | ||
|
|
d1e7d5116a | ||
|
|
da064e9429 | ||
|
|
67faa26cbc | ||
|
|
b0b6d5926b | ||
|
|
22b626c9e0 | ||
|
|
eeb0006531 | ||
|
|
9db56f86ca | ||
|
|
c71265aafe | ||
|
|
24c8792150 | ||
|
|
2f69c6342a | ||
|
|
c99ed212da | ||
|
|
c8af41738c | ||
|
|
f3801843a4 | ||
|
|
08430f1951 | ||
|
|
80ce8f80ba | ||
|
|
9334b29366 | ||
|
|
87048818ce | ||
|
|
d2d610e0f5 | ||
|
|
ae6144c042 | ||
|
|
d1c65935e6 | ||
|
|
2b5c9ae7a8 | ||
|
|
f67fe2ce25 | ||
|
|
23e84f3fa4 | ||
|
|
70a076c4fe | ||
|
|
58c3b1616f | ||
|
|
24d27a93c8 | ||
|
|
27aa8662c0 | ||
|
|
479f9238ff | ||
|
|
2b84be5167 | ||
|
|
5d45af707b | ||
|
|
e9a8d8c157 | ||
|
|
1cc49806e0 | ||
|
|
316ac0558b | ||
|
|
ffce9b51cc | ||
|
|
760278fde6 | ||
|
|
980fe21050 | ||
|
|
707709deb1 | ||
|
|
6270bb3e77 | ||
|
|
2c2701c054 | ||
|
|
2b8c9c698d | ||
|
|
80367d60d4 | ||
|
|
688ca65edf | ||
|
|
dcefc18b98 | ||
|
|
214f2f008e | ||
|
|
004e432e7c | ||
|
|
47486af06d | ||
|
|
8b9b1984fc | ||
|
|
6dd7bcb0fe | ||
|
|
b94950d7c2 | ||
|
|
618353bf95 | ||
|
|
1555081939 | ||
|
|
7fe9e6d3f6 | ||
|
|
cbba0695a8 | ||
|
|
921526e086 | ||
|
|
1b21339bf8 | ||
|
|
e9d6daa1a3 | ||
|
|
8b001313f4 | ||
|
|
e0c8eca669 | ||
|
|
c629130b3b | ||
|
|
9252225d7f | ||
|
|
38b5c7fb6c | ||
|
|
e26df26f78 | ||
|
|
158e07e581 | ||
|
|
699d3d6060 | ||
|
|
d56a611be7 | ||
|
|
bf5d8af3f6 | ||
|
|
0769098357 | ||
|
|
71094d981f | ||
|
|
32d97f1451 | ||
|
|
bf9d1c634a | ||
|
|
ebd96d7766 | ||
|
|
089d156259 | ||
|
|
dd589022e4 | ||
|
|
b850f65295 | ||
|
|
b3d09cd2d4 | ||
|
|
64d125afd9 | ||
|
|
dd5d64da3e | ||
|
|
1776eb56fe | ||
|
|
f8c135f22e | ||
|
|
0fb2edf051 | ||
|
|
a6fdf114d1 | ||
|
|
ca1b33d24d | ||
|
|
c98b9bbee6 | ||
|
|
19a0a85c73 | ||
|
|
77122b8f1b | ||
|
|
436af467d6 | ||
|
|
04130e4ea3 | ||
|
|
7e6e44e787 | ||
|
|
9e7ca8df51 | ||
|
|
6bdddd462a | ||
|
|
e5559137dd | ||
|
|
ecf09e17a8 | ||
|
|
14d706c91e | ||
|
|
5317a89623 | ||
|
|
e9b1786188 | ||
|
|
313289d28d | ||
|
|
05e237560d | ||
|
|
e4dd314079 | ||
|
|
118c7f25b5 | ||
|
|
b64a80da0b | ||
|
|
bb26c9d97c | ||
|
|
edf3854632 | ||
|
|
19a66450ec | ||
|
|
3a839dfb86 | ||
|
|
78b261e440 | ||
|
|
471d1b0ec5 | ||
|
|
58dfbe13ed | ||
|
|
1a4eef1056 | ||
|
|
8da7db3e26 | ||
|
|
d359130bcf | ||
|
|
05099e1784 | ||
|
|
79970ebb4a | ||
|
|
27b4086351 | ||
|
|
ae7eafd6ad | ||
|
|
21b5aee054 | ||
|
|
2b34a8d4e6 | ||
|
|
b4771c1bce | ||
|
|
c83858bf7e | ||
|
|
08b8964b3d | ||
|
|
d88551e99d | ||
|
|
ebd71d1f04 | ||
|
|
a61ab0c5cf | ||
|
|
fa718e2576 | ||
|
|
c517a87b85 | ||
|
|
28fd0e91ee | ||
|
|
14c27600d8 | ||
|
|
dedce20780 | ||
|
|
a58b932743 | ||
|
|
301bebf8d3 | ||
|
|
d192c36c39 | ||
|
|
bdfb804bb2 | ||
|
|
16dbcfda7e | ||
|
|
0169ecc37e | ||
|
|
4a70c81b33 | ||
|
|
5e84ce639f | ||
|
|
6c45388da9 | ||
|
|
bd8c1c3bb7 | ||
|
|
28b8cabea5 | ||
|
|
7c91803056 | ||
|
|
b375e1d043 | ||
|
|
d718d247c8 | ||
|
|
6a82d4e2af | ||
|
|
11722bf029 | ||
|
|
dfb7730b63 | ||
|
|
c9117dd037 | ||
|
|
e4539ef232 | ||
|
|
0ba6ae7a18 | ||
|
|
25ecc4a510 | ||
|
|
259c2195dc | ||
|
|
7bdc9dd428 | ||
|
|
5f8872f8cc | ||
|
|
5794ede6f6 | ||
|
|
41cf73a473 | ||
|
|
f8d84139fa | ||
|
|
d3964ff4bd | ||
|
|
9acba7d3f0 | ||
|
|
2d567bfe0f | ||
|
|
7207aa60fb | ||
|
|
2b9af951bf | ||
|
|
c84efcbbcc | ||
|
|
092aae3260 | ||
|
|
222582fb0c | ||
|
|
a99367bc5f | ||
|
|
c47d5c7228 | ||
|
|
3d709e3bb7 | ||
|
|
3a9360a57b | ||
|
|
dd3c4cd032 | ||
|
|
2a4a622518 | ||
|
|
4db06ba9a1 | ||
|
|
96f366f5e7 | ||
|
|
78791f31a4 | ||
|
|
ec3fa9300b | ||
|
|
9fa7e30a0a | ||
|
|
158d98c2bd | ||
|
|
ae12871bd7 | ||
|
|
7141050f8b | ||
|
|
5d900fd40d | ||
|
|
f57b7e989f | ||
|
|
556ccd68c1 | ||
|
|
1181039249 | ||
|
|
6da137cd4a | ||
|
|
9750a23dbe | ||
|
|
1c93aab7e0 | ||
|
|
53c4637fb6 | ||
|
|
3b2289c8fa | ||
|
|
b55b21373c | ||
|
|
53cfa99912 | ||
|
|
f30e720c7b | ||
|
|
57c84f3adc | ||
|
|
2820d92ab3 | ||
|
|
780a654f65 | ||
|
|
08205008e7 | ||
|
|
e17cac854a | ||
|
|
974d028e51 | ||
|
|
be11362533 | ||
|
|
912abb3a10 | ||
|
|
0add1dce01 | ||
|
|
922c41236c | ||
|
|
c8605d6b3d | ||
|
|
caee123f9d | ||
|
|
7b31301f09 | ||
|
|
f2025c2658 | ||
|
|
f324e3fb28 | ||
|
|
894d35e4fe | ||
|
|
1d9cd82d68 | ||
|
|
2bc26fe4fa | ||
|
|
4168b9ac26 | ||
|
|
8b030135de | ||
|
|
150c3f032c | ||
|
|
bfc4dd05ed | ||
|
|
cd812b4232 | ||
|
|
e4f150bbe3 | ||
|
|
db0126968a | ||
|
|
c12bac0c96 | ||
|
|
fba333866d | ||
|
|
d02d413c5e | ||
|
|
c51662c270 | ||
|
|
021e3e307b | ||
|
|
b4cdb75d15 | ||
|
|
ad6ef03c0c | ||
|
|
5930d5aabf | ||
|
|
3c5925c827 | ||
|
|
b7c8c1f533 | ||
|
|
bf41b2ed9f | ||
|
|
525a547500 | ||
|
|
ca4d5000ed | ||
|
|
24c1098736 | ||
|
|
47d7e713af | ||
|
|
3ba26ce67e | ||
|
|
ab1d58959f | ||
|
|
f17e378a93 | ||
|
|
f5e44d9eb3 | ||
|
|
d9d00ba60c | ||
|
|
a15b1bebba | ||
|
|
980be0e4e0 | ||
|
|
a0ffe89056 | ||
|
|
bd11c9650e | ||
|
|
32a5dff78d | ||
|
|
8ef2da8f5f | ||
|
|
60e802d77f | ||
|
|
966acf51d8 | ||
|
|
b5b84e6bbd | ||
|
|
f72aa4e52f | ||
|
|
d9f1d456d3 | ||
|
|
67f4c9f941 | ||
|
|
d63666cece | ||
|
|
d8a6acda96 | ||
|
|
03ef48a0ae | ||
|
|
8f35b9d579 | ||
|
|
ebfa982c72 | ||
|
|
2fa4fcc202 | ||
|
|
ad67154e26 | ||
|
|
250c45d1b9 | ||
|
|
dc036d44db | ||
|
|
9c4c265767 | ||
|
|
a525787ab7 | ||
|
|
aae1bc3cab | ||
|
|
42ddee8d6f | ||
|
|
54b18b3c59 | ||
|
|
5a04f1ecf4 | ||
|
|
a0fb8bd32d | ||
|
|
c685250c96 | ||
|
|
9ea1104764 | ||
|
|
2ef2c7523d | ||
|
|
e5e30d0915 | ||
|
|
c37b776f7a | ||
|
|
e9bc3246ce | ||
|
|
e89e6d47d4 | ||
|
|
292c815000 | ||
|
|
f0b1de5ec6 | ||
|
|
4204af255c | ||
|
|
b0948a37d4 | ||
|
|
cc47d11792 | ||
|
|
b6b82e6f54 | ||
|
|
16ed5fd982 | ||
|
|
24aa68f090 | ||
|
|
98ef052967 | ||
|
|
8a01b20e1e | ||
|
|
5a86f44fc3 | ||
|
|
fe43173b6c | ||
|
|
587ce92dc9 | ||
|
|
8758fb6ac5 | ||
|
|
92e5e2db6a | ||
|
|
851f0d6c08 | ||
|
|
333444b4e1 | ||
|
|
28a5c1f7b2 | ||
|
|
4533ac6b28 | ||
|
|
91a3b7aba2 | ||
|
|
4137d4aed2 | ||
|
|
cb095d70df | ||
|
|
e43e601912 | ||
|
|
55cd40d175 | ||
|
|
55cd74efca | ||
|
|
a9d0079bf3 | ||
|
|
8ac158cdb4 | ||
|
|
def2362682 | ||
|
|
18f6e31b54 | ||
|
|
7e1d0a81bf | ||
|
|
f30e405229 | ||
|
|
563fe16bd4 | ||
|
|
a30a358b73 | ||
|
|
497499e221 | ||
|
|
1fdc51078e | ||
|
|
60f343a926 | ||
|
|
fd3ccd6130 | ||
|
|
a97ce76f1f | ||
|
|
83cf6e191c | ||
|
|
f0ac6e89be | ||
|
|
23950c0602 | ||
|
|
19a05a7c2c | ||
|
|
299e856736 | ||
|
|
6d95dc16c2 | ||
|
|
fa28ddc2f7 | ||
|
|
56dbb564ea | ||
|
|
711124f5c0 | ||
|
|
5606f987fa | ||
|
|
f0cb3b5e36 | ||
|
|
231dc3913f | ||
|
|
2b1fc441e6 | ||
|
|
e433289832 | ||
|
|
86bdd1e386 | ||
|
|
4f1c0d99ea | ||
|
|
f0792cfb0b | ||
|
|
dece744685 | ||
|
|
68ab6d2e75 | ||
|
|
c69f0bf3cb | ||
|
|
59037e4eee | ||
|
|
1bd8348be7 | ||
|
|
ecdecdd94e | ||
|
|
28ec44c91f | ||
|
|
895d9613b5 | ||
|
|
87d0c9088d | ||
|
|
24904585a2 | ||
|
|
1dca8e96a7 | ||
|
|
e1ab2095fa | ||
|
|
12e18de8ad | ||
|
|
025b00247d | ||
|
|
5832991c4d | ||
|
|
81990f3f80 | ||
|
|
368cbdf979 | ||
|
|
b8d7c97eb7 | ||
|
|
fb8ac92abf | ||
|
|
0bfb3a4709 | ||
|
|
9559a68416 | ||
|
|
d28f005552 | ||
|
|
713560702b | ||
|
|
0ba121fdfb | ||
|
|
0e2449482f | ||
|
|
8d1a3f4fd7 | ||
|
|
1e0f2dde6a | ||
|
|
7abac07837 | ||
|
|
15d718fb1a | ||
|
|
4ca3f714ea | ||
|
|
6d1b123a25 | ||
|
|
13ebb98644 | ||
|
|
dda558fe1c | ||
|
|
76f19db6e7 | ||
|
|
f776b799b8 | ||
|
|
746a443660 | ||
|
|
026d3dc80d | ||
|
|
fd7dd47d5e | ||
|
|
1e5a1b4892 | ||
|
|
c7f6748761 | ||
|
|
7efa0069cb | ||
|
|
424cf80e5c | ||
|
|
1448d9de98 | ||
|
|
cbf01e118a | ||
|
|
191942fcd1 | ||
|
|
d5c8f2541d | ||
|
|
6e9ad3d808 | ||
|
|
36fdff5b8b | ||
|
|
8f4139f44a | ||
|
|
e7706a20dd | ||
|
|
6d1018e647 | ||
|
|
11246b948f | ||
|
|
7b619bd084 | ||
|
|
35f05cbbec | ||
|
|
0a1887d6e7 | ||
|
|
c6d22bb1f8 | ||
|
|
6132863f4a | ||
|
|
26ffd2fd22 | ||
|
|
51c74de1de | ||
|
|
5126330a10 | ||
|
|
4472020ee9 | ||
|
|
7de26a462f | ||
|
|
f2f17b11e8 | ||
|
|
cdd068cdb6 | ||
|
|
584e3af237 |
639
.drone1.yml
639
.drone1.yml
@@ -16,6 +16,11 @@ services:
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: vikunjatest
|
||||
MYSQL_DATABASE: vikunjatest
|
||||
- name: test-mysql-migration
|
||||
image: mariadb:10
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: vikunjatest
|
||||
MYSQL_DATABASE: vikunjatest
|
||||
- name: test-postgres-unit
|
||||
image: postgres:12
|
||||
environment:
|
||||
@@ -26,6 +31,11 @@ services:
|
||||
environment:
|
||||
POSTGRES_PASSWORD: vikunjatest
|
||||
POSTGRES_DB: vikunjatest
|
||||
- name: test-postgres-migration
|
||||
image: postgres:12
|
||||
environment:
|
||||
POSTGRES_PASSWORD: vikunjatest
|
||||
POSTGRES_DB: vikunjatest
|
||||
|
||||
trigger:
|
||||
branch:
|
||||
@@ -42,31 +52,115 @@ steps:
|
||||
commands:
|
||||
- git fetch --tags
|
||||
|
||||
# We're statically compiling the magefile to avoid race condition issues caused by multiple pipeline 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
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
commands:
|
||||
- mage -compile ./mage-static
|
||||
when:
|
||||
event: [ push, tag, pull_request ]
|
||||
|
||||
- name: build
|
||||
image: vikunja/golang-build:latest
|
||||
pull: true
|
||||
environment:
|
||||
GOFLAGS: '-mod=vendor'
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
depends_on: [ mage ]
|
||||
commands:
|
||||
- make generate
|
||||
- make lint
|
||||
- make fmt-check
|
||||
# - make got-swag # Commented out until we figured out how to get this working on drone
|
||||
- make ineffassign-check
|
||||
- make misspell-check
|
||||
- make goconst-check
|
||||
- make gocyclo-check
|
||||
- make static-check
|
||||
- make build
|
||||
- ./mage-static build:build
|
||||
when:
|
||||
event: [ push, tag, pull_request ]
|
||||
|
||||
- name: lint
|
||||
image: vikunja/golang-build:latest
|
||||
pull: true
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
depends_on: [ build ]
|
||||
commands:
|
||||
- ./mage-static build:generate
|
||||
- ./mage-static check:got-swag
|
||||
- wget -O - -q https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.31.0
|
||||
- ./mage-static check:golangci
|
||||
when:
|
||||
event: [ push, tag, pull_request ]
|
||||
|
||||
- name: test-migration-prepare
|
||||
image: kolaente/toolbox:latest
|
||||
pull: true
|
||||
commands:
|
||||
# Get the latest version
|
||||
- wget https://dl.vikunja.io/api/master/vikunja-master-linux-amd64-full.zip -q -O vikunja-latest.zip
|
||||
- unzip vikunja-latest.zip vikunja-master-linux-amd64
|
||||
|
||||
- name: test-migration-sqlite
|
||||
image: kolaente/toolbox:latest
|
||||
pull: true
|
||||
depends_on: [ test-migration-prepare, build ]
|
||||
environment:
|
||||
VIKUNJA_DATABASE_TYPE: sqlite
|
||||
VIKUNJA_DATABASE_PATH: ./vikunja-migration-test.db
|
||||
VIKUNJA_LOG_DATABASE: stdout
|
||||
VIKUNJA_LOG_DATABASELEVEL: debug
|
||||
commands:
|
||||
- ./vikunja-master-linux-amd64 migrate
|
||||
# Run the migrations from the binary build in the step before
|
||||
- ./vikunja migrate
|
||||
when:
|
||||
event: [ push, tag, pull_request ]
|
||||
|
||||
- name: test-migration-mysql
|
||||
image: kolaente/toolbox:latest
|
||||
pull: true
|
||||
depends_on: [ test-migration-prepare, build ]
|
||||
environment:
|
||||
VIKUNJA_DATABASE_TYPE: mysql
|
||||
VIKUNJA_DATABASE_HOST: test-mysql-migration
|
||||
VIKUNJA_DATABASE_USER: root
|
||||
VIKUNJA_DATABASE_PASSWORD: vikunjatest
|
||||
VIKUNJA_DATABASE_DATABASE: vikunjatest
|
||||
VIKUNJA_LOG_DATABASE: stdout
|
||||
VIKUNJA_LOG_DATABASELEVEL: debug
|
||||
commands:
|
||||
- ./vikunja-master-linux-amd64 migrate
|
||||
# Run the migrations from the binary build in the step before
|
||||
- ./vikunja migrate
|
||||
when:
|
||||
event: [ push, tag, pull_request ]
|
||||
|
||||
- name: test-migration-psql
|
||||
image: kolaente/toolbox:latest
|
||||
pull: true
|
||||
depends_on: [ test-migration-prepare, build ]
|
||||
environment:
|
||||
VIKUNJA_DATABASE_TYPE: postgres
|
||||
VIKUNJA_DATABASE_HOST: test-postgres-migration
|
||||
VIKUNJA_DATABASE_USER: postgres
|
||||
VIKUNJA_DATABASE_PASSWORD: vikunjatest
|
||||
VIKUNJA_DATABASE_DATABASE: vikunjatest
|
||||
VIKUNJA_DATABASE_SSLMODE: disable
|
||||
VIKUNJA_LOG_DATABASE: stdout
|
||||
VIKUNJA_LOG_DATABASELEVEL: debug
|
||||
commands:
|
||||
- ./vikunja-master-linux-amd64 migrate
|
||||
# Run the migrations from the binary build in the step before
|
||||
- ./vikunja migrate
|
||||
when:
|
||||
event: [ push, tag, pull_request ]
|
||||
|
||||
- name: test
|
||||
image: vikunja/golang-build:latest
|
||||
pull: true
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
commands:
|
||||
- make test
|
||||
depends_on: [ build ]
|
||||
- ./mage-static build:generate
|
||||
- ./mage-static test:unit
|
||||
depends_on: [ fetch-tags, mage ]
|
||||
when:
|
||||
event: [ push, tag, pull_request ]
|
||||
|
||||
@@ -74,11 +168,13 @@ steps:
|
||||
image: vikunja/golang-build:latest
|
||||
pull: true
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
VIKUNJA_TESTS_USE_CONFIG: 1
|
||||
VIKUNJA_DATABASE_TYPE: sqlite
|
||||
commands:
|
||||
- make test
|
||||
depends_on: [ build ]
|
||||
- ./mage-static build:generate
|
||||
- ./mage-static test:unit
|
||||
depends_on: [ fetch-tags, mage ]
|
||||
when:
|
||||
event: [ push, tag, pull_request ]
|
||||
|
||||
@@ -86,6 +182,7 @@ steps:
|
||||
image: vikunja/golang-build:latest
|
||||
pull: true
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
VIKUNJA_TESTS_USE_CONFIG: 1
|
||||
VIKUNJA_DATABASE_TYPE: mysql
|
||||
VIKUNJA_DATABASE_HOST: test-mysql-unit
|
||||
@@ -93,8 +190,9 @@ steps:
|
||||
VIKUNJA_DATABASE_PASSWORD: vikunjatest
|
||||
VIKUNJA_DATABASE_DATABASE: vikunjatest
|
||||
commands:
|
||||
- make test
|
||||
depends_on: [ build ]
|
||||
- ./mage-static build:generate
|
||||
- ./mage-static test:unit
|
||||
depends_on: [ fetch-tags, mage ]
|
||||
when:
|
||||
event: [ push, tag, pull_request ]
|
||||
|
||||
@@ -102,6 +200,7 @@ steps:
|
||||
image: vikunja/golang-build:latest
|
||||
pull: true
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
VIKUNJA_TESTS_USE_CONFIG: 1
|
||||
VIKUNJA_DATABASE_TYPE: postgres
|
||||
VIKUNJA_DATABASE_HOST: test-postgres-unit
|
||||
@@ -110,17 +209,21 @@ steps:
|
||||
VIKUNJA_DATABASE_DATABASE: vikunjatest
|
||||
VIKUNJA_DATABASE_SSLMODE: disable
|
||||
commands:
|
||||
- make test
|
||||
depends_on: [ build ]
|
||||
- ./mage-static build:generate
|
||||
- ./mage-static test:unit
|
||||
depends_on: [ fetch-tags, mage ]
|
||||
when:
|
||||
event: [ push, tag, pull_request ]
|
||||
|
||||
- name: integration-test
|
||||
image: vikunja/golang-build:latest
|
||||
pull: true
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
commands:
|
||||
- make integration-test
|
||||
depends_on: [ build ]
|
||||
- ./mage-static build:generate
|
||||
- ./mage-static test:integration
|
||||
depends_on: [ fetch-tags, mage ]
|
||||
when:
|
||||
event: [ push, tag, pull_request ]
|
||||
|
||||
@@ -128,11 +231,13 @@ steps:
|
||||
image: vikunja/golang-build:latest
|
||||
pull: true
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
VIKUNJA_TESTS_USE_CONFIG: 1
|
||||
VIKUNJA_DATABASE_TYPE: sqlite
|
||||
commands:
|
||||
- make integration-test
|
||||
depends_on: [ build ]
|
||||
- ./mage-static build:generate
|
||||
- ./mage-static test:integration
|
||||
depends_on: [ fetch-tags, mage ]
|
||||
when:
|
||||
event: [ push, tag, pull_request ]
|
||||
|
||||
@@ -140,6 +245,7 @@ steps:
|
||||
image: vikunja/golang-build:latest
|
||||
pull: true
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
VIKUNJA_TESTS_USE_CONFIG: 1
|
||||
VIKUNJA_DATABASE_TYPE: mysql
|
||||
VIKUNJA_DATABASE_HOST: test-mysql-integration
|
||||
@@ -147,8 +253,9 @@ steps:
|
||||
VIKUNJA_DATABASE_PASSWORD: vikunjatest
|
||||
VIKUNJA_DATABASE_DATABASE: vikunjatest
|
||||
commands:
|
||||
- make integration-test
|
||||
depends_on: [ build ]
|
||||
- ./mage-static build:generate
|
||||
- ./mage-static test:integration
|
||||
depends_on: [ fetch-tags, mage ]
|
||||
when:
|
||||
event: [ push, tag, pull_request ]
|
||||
|
||||
@@ -156,6 +263,7 @@ steps:
|
||||
image: vikunja/golang-build:latest
|
||||
pull: true
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
VIKUNJA_TESTS_USE_CONFIG: 1
|
||||
VIKUNJA_DATABASE_TYPE: postgres
|
||||
VIKUNJA_DATABASE_HOST: test-postgres-integration
|
||||
@@ -164,19 +272,20 @@ steps:
|
||||
VIKUNJA_DATABASE_DATABASE: vikunjatest
|
||||
VIKUNJA_DATABASE_SSLMODE: disable
|
||||
commands:
|
||||
- make integration-test
|
||||
depends_on: [ build ]
|
||||
- ./mage-static build:generate
|
||||
- ./mage-static test:integration
|
||||
depends_on: [ fetch-tags, mage ]
|
||||
when:
|
||||
event: [ push, tag, pull_request ]
|
||||
|
||||
---
|
||||
########
|
||||
# Build a release when pushing to master
|
||||
# We need to copy this because drone currently can't run a pipeline on either a push to master or a tag.
|
||||
# Build a release when tagging
|
||||
########
|
||||
|
||||
kind: pipeline
|
||||
name: deploy-master
|
||||
name: release
|
||||
|
||||
depends_on:
|
||||
- testing
|
||||
|
||||
@@ -185,10 +294,9 @@ workspace:
|
||||
path: src/code.vikunja.io/api
|
||||
|
||||
trigger:
|
||||
branch:
|
||||
- master
|
||||
event:
|
||||
- push
|
||||
ref:
|
||||
- refs/heads/master
|
||||
- "refs/tags/**"
|
||||
|
||||
steps:
|
||||
# Needed to get the versions right as they depend on tags
|
||||
@@ -197,14 +305,27 @@ steps:
|
||||
commands:
|
||||
- git fetch --tags
|
||||
|
||||
# We're statically compiling the magefile to avoid race condition issues caused by multiple pipeline 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
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
commands:
|
||||
- mage -compile ./mage-static
|
||||
when:
|
||||
event: [ push, tag, pull_request ]
|
||||
|
||||
- name: before-static-build
|
||||
image: techknowlogick/xgo:latest
|
||||
pull: true
|
||||
commands:
|
||||
- export PATH=$PATH:$GOPATH/bin
|
||||
- make generate
|
||||
- make release-dirs
|
||||
depends_on: [ fetch-tags ]
|
||||
- go install github.com/magefile/mage
|
||||
- ./mage-static build:generate
|
||||
- ./mage-static release:dirs
|
||||
depends_on: [ fetch-tags, mage ]
|
||||
|
||||
- name: static-build-windows
|
||||
image: techknowlogick/xgo:latest
|
||||
@@ -215,7 +336,8 @@ steps:
|
||||
GOPATH: /srv/app
|
||||
commands:
|
||||
- export PATH=$PATH:$GOPATH/bin
|
||||
- make release-windows
|
||||
- go install github.com/magefile/mage
|
||||
- ./mage-static release:windows
|
||||
depends_on: [ before-static-build ]
|
||||
|
||||
- name: static-build-linux
|
||||
@@ -227,7 +349,8 @@ steps:
|
||||
GOPATH: /srv/app
|
||||
commands:
|
||||
- export PATH=$PATH:$GOPATH/bin
|
||||
- make release-linux
|
||||
- go install github.com/magefile/mage
|
||||
- ./mage-static release:linux
|
||||
depends_on: [ before-static-build ]
|
||||
|
||||
- name: static-build-darwin
|
||||
@@ -239,7 +362,8 @@ steps:
|
||||
GOPATH: /srv/app
|
||||
commands:
|
||||
- export PATH=$PATH:$GOPATH/bin
|
||||
- make release-darwin
|
||||
- go install github.com/magefile/mage
|
||||
- ./mage-static release:darwin
|
||||
depends_on: [ before-static-build ]
|
||||
|
||||
- name: after-build-compress
|
||||
@@ -250,7 +374,7 @@ steps:
|
||||
- static-build-linux
|
||||
- static-build-darwin
|
||||
commands:
|
||||
- make release-compress
|
||||
- ./mage-static release:compress
|
||||
|
||||
- name: after-build-static
|
||||
image: techknowlogick/xgo:latest
|
||||
@@ -258,10 +382,11 @@ steps:
|
||||
depends_on:
|
||||
- after-build-compress
|
||||
commands:
|
||||
- make release-copy
|
||||
- make release-check
|
||||
- make release-os-package
|
||||
- make release-zip
|
||||
- go install github.com/magefile/mage
|
||||
- ./mage-static release:copy
|
||||
- ./mage-static release:check
|
||||
- ./mage-static release:os-package
|
||||
- ./mage-static release:zip
|
||||
|
||||
- name: sign-release
|
||||
image: plugins/gpgsign:1
|
||||
@@ -276,290 +401,100 @@ steps:
|
||||
- dist/zip/*
|
||||
detach_sign: true
|
||||
|
||||
# Push the releases to our pseudo-s3-bucket
|
||||
# Push the releases to our pseudo-s3-bucket
|
||||
- name: release-latest
|
||||
image: plugins/s3:1
|
||||
pull: true
|
||||
settings:
|
||||
bucket: vikunja
|
||||
bucket: vikunja-releases
|
||||
access_key:
|
||||
from_secret: aws_access_key_id
|
||||
secret_key:
|
||||
from_secret: aws_secret_access_key
|
||||
endpoint: https://storage.kolaente.de
|
||||
endpoint: https://s3.fr-par.scw.cloud
|
||||
region: fr-par
|
||||
path_style: true
|
||||
strip_prefix: dist/zip/
|
||||
source: dist/zip/*
|
||||
target: /master/
|
||||
target: /api/master/
|
||||
when:
|
||||
branch:
|
||||
- master
|
||||
event:
|
||||
- push
|
||||
depends_on: [ sign-release ]
|
||||
|
||||
# Build a debian package and push it to our bucket
|
||||
- name: build-deb
|
||||
image: kolaente/fpm
|
||||
pull: true
|
||||
commands:
|
||||
- make build-deb
|
||||
depends_on: [ static-build-linux ]
|
||||
|
||||
- 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
|
||||
- make reprepro
|
||||
depends_on: [ build-deb ]
|
||||
|
||||
# Push the releases to our pseudo-s3-bucket
|
||||
- name: release-deb
|
||||
image: plugins/s3:1
|
||||
pull: true
|
||||
settings:
|
||||
bucket: vikunja-deb
|
||||
access_key:
|
||||
from_secret: aws_access_key_id
|
||||
secret_key:
|
||||
from_secret: aws_secret_access_key
|
||||
endpoint: https://storage.kolaente.de
|
||||
path_style: true
|
||||
strip_prefix: debian
|
||||
source: debian/*/*/*/*/*
|
||||
target: /
|
||||
depends_on: [ deb-structure ]
|
||||
|
||||
# Build the docker image and push it to docker hub
|
||||
- name: docker
|
||||
image: plugins/docker
|
||||
pull: true
|
||||
settings:
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
repo: vikunja/api
|
||||
auto_tag: true
|
||||
depends_on: [ fetch-tags ]
|
||||
|
||||
- name: telegram
|
||||
image: appleboy/drone-telegram
|
||||
depends_on:
|
||||
- release-latest
|
||||
settings:
|
||||
token:
|
||||
from_secret: TELEGRAM_TOKEN
|
||||
to:
|
||||
from_secret: TELEGRAM_TO
|
||||
message: >
|
||||
{{repo.owner}}/{{repo.name}}: \[{{build.status}}] Build {{build.number}}
|
||||
{{commit.author}} pushed to {{commit.branch}} {{commit.sha}}: `{{commit.message}}`
|
||||
Build started at {{datetime build.started "2006-Jan-02T15:04:05Z" "GMT+2"}} finished at {{datetime build.finished "2006-Jan-02T15:04:05Z" "GMT+2"}}.
|
||||
when:
|
||||
status:
|
||||
- success
|
||||
- failure
|
||||
---
|
||||
########
|
||||
# Build a release when tagging
|
||||
# We need to copy this because drone currently can't run a pipeline on either a push to master or a tag.
|
||||
########
|
||||
|
||||
kind: pipeline
|
||||
name: deploy-version
|
||||
depends_on:
|
||||
- testing
|
||||
|
||||
workspace:
|
||||
base: /go
|
||||
path: src/code.vikunja.io/api
|
||||
|
||||
trigger:
|
||||
event:
|
||||
- tag
|
||||
|
||||
steps:
|
||||
- name: fetch-tags
|
||||
image: docker:git
|
||||
commands:
|
||||
- git fetch --tags
|
||||
|
||||
- name: before-static-build
|
||||
image: techknowlogick/xgo:latest
|
||||
pull: true
|
||||
commands:
|
||||
- make generate
|
||||
- make release-dirs
|
||||
depends_on: [ fetch-tags ]
|
||||
|
||||
- name: static-build-windows
|
||||
image: techknowlogick/xgo:latest
|
||||
pull: true
|
||||
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.
|
||||
GOPATH: /srv/app
|
||||
commands:
|
||||
- export PATH=$PATH:$GOPATH/bin
|
||||
- make release-windows
|
||||
depends_on: [ before-static-build ]
|
||||
|
||||
- name: static-build-linux
|
||||
image: techknowlogick/xgo:latest
|
||||
pull: true
|
||||
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.
|
||||
GOPATH: /srv/app
|
||||
commands:
|
||||
- export PATH=$PATH:$GOPATH/bin
|
||||
- make release-linux
|
||||
depends_on: [ before-static-build ]
|
||||
|
||||
- name: static-build-darwin
|
||||
image: techknowlogick/xgo:latest
|
||||
pull: true
|
||||
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.
|
||||
GOPATH: /srv/app
|
||||
commands:
|
||||
- export PATH=$PATH:$GOPATH/bin
|
||||
- make release-darwin
|
||||
depends_on: [ before-static-build ]
|
||||
|
||||
- name: after-build-compress
|
||||
image: kolaente/upx
|
||||
pull: true
|
||||
depends_on:
|
||||
- static-build-windows
|
||||
- static-build-linux
|
||||
- static-build-darwin
|
||||
commands:
|
||||
- make release-compress
|
||||
|
||||
- name: after-build-static
|
||||
image: techknowlogick/xgo:latest
|
||||
pull: true
|
||||
depends_on:
|
||||
- after-build-compress
|
||||
commands:
|
||||
- make release-copy
|
||||
- make release-check
|
||||
- make release-os-package
|
||||
- make release-zip
|
||||
|
||||
- name: sign-release
|
||||
image: plugins/gpgsign:1
|
||||
pull: true
|
||||
depends_on: [ after-build-static ]
|
||||
settings:
|
||||
key:
|
||||
from_secret: gpg_privkey
|
||||
passphrase:
|
||||
from_secret: gpg_password
|
||||
files:
|
||||
- dist/zip/*
|
||||
detach_sign: true
|
||||
|
||||
# Push the releases to our pseudo-s3-bucket
|
||||
- name: release-version
|
||||
image: plugins/s3:1
|
||||
pull: true
|
||||
settings:
|
||||
bucket: vikunja
|
||||
bucket: vikunja-releases
|
||||
access_key:
|
||||
from_secret: aws_access_key_id
|
||||
secret_key:
|
||||
from_secret: aws_secret_access_key
|
||||
endpoint: https://storage.kolaente.de
|
||||
endpoint: https://s3.fr-par.scw.cloud
|
||||
region: fr-par
|
||||
path_style: true
|
||||
strip_prefix: dist/zip/
|
||||
source: dist/zip/*
|
||||
target: /${DRONE_TAG##v}/
|
||||
target: /api/${DRONE_TAG##v}/
|
||||
when:
|
||||
event:
|
||||
- tag
|
||||
depends_on: [ sign-release ]
|
||||
|
||||
# Build a debian package and push it to our bucket
|
||||
- name: build-deb
|
||||
image: kolaente/fpm
|
||||
# Build os packages and push it to our bucket
|
||||
- name: build-os-packages
|
||||
image: goreleaser/nfpm
|
||||
pull: true
|
||||
commands:
|
||||
- make build-deb
|
||||
- apk add git go
|
||||
- ./mage-static release:packages
|
||||
depends_on: [ static-build-linux ]
|
||||
|
||||
- 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
|
||||
- make reprepro
|
||||
depends_on: [ build-deb ]
|
||||
|
||||
# Push the releases to our pseudo-s3-bucket
|
||||
- name: release-deb
|
||||
# Push the os releases to our pseudo-s3-bucket
|
||||
- name: release-os-latest
|
||||
image: plugins/s3:1
|
||||
pull: true
|
||||
settings:
|
||||
bucket: vikunja-deb
|
||||
bucket: vikunja-releases
|
||||
access_key:
|
||||
from_secret: aws_access_key_id
|
||||
secret_key:
|
||||
from_secret: aws_secret_access_key
|
||||
endpoint: https://storage.kolaente.de
|
||||
endpoint: https://s3.fr-par.scw.cloud
|
||||
region: fr-par
|
||||
path_style: true
|
||||
strip_prefix: debian
|
||||
source: debian/*/*/*/*/*
|
||||
target: /
|
||||
depends_on: [ deb-structure ]
|
||||
strip_prefix: dist/os-packages/
|
||||
source: dist/os-packages/*
|
||||
target: /api/master/
|
||||
when:
|
||||
branch:
|
||||
- master
|
||||
event:
|
||||
- push
|
||||
depends_on: [ build-os-packages ]
|
||||
|
||||
# Build the docker image and push it to docker hub
|
||||
- name: docker
|
||||
image: plugins/docker
|
||||
- name: release-os-version
|
||||
image: plugins/s3:1
|
||||
pull: true
|
||||
settings:
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
repo: vikunja/api
|
||||
auto_tag: true
|
||||
depends_on: [ fetch-tags ]
|
||||
|
||||
- name: telegram
|
||||
image: appleboy/drone-telegram
|
||||
depends_on:
|
||||
- docker
|
||||
- release-version
|
||||
settings:
|
||||
token:
|
||||
from_secret: TELEGRAM_TOKEN
|
||||
to:
|
||||
from_secret: TELEGRAM_TO
|
||||
message: >
|
||||
{{repo.owner}}/{{repo.name}}: \[{{build.status}}] Build {{build.number}}
|
||||
{{commit.author}} pushed to {{commit.branch}} {{commit.sha}}: `{{commit.message}}`
|
||||
Build started at {{datetime build.started "2006-Jan-02T15:04:05Z" "GMT+2"}} finished at {{datetime build.finished "2006-Jan-02T15:04:05Z" "GMT+2"}}.
|
||||
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: dist/os-packages/
|
||||
source: dist/os-packages/*
|
||||
target: /api/${DRONE_TAG##v}/
|
||||
when:
|
||||
status:
|
||||
- success
|
||||
- failure
|
||||
event:
|
||||
- tag
|
||||
depends_on: [ build-os-packages ]
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
@@ -592,7 +527,7 @@ steps:
|
||||
commands:
|
||||
- cd docs/themes/vikunja
|
||||
- yarn --production=false
|
||||
- ./node_modules/.bin/gulp
|
||||
- ./node_modules/.bin/gulp prod
|
||||
|
||||
- name: build
|
||||
image: monachus/hugo:v0.54.0
|
||||
@@ -613,3 +548,131 @@ steps:
|
||||
repo: vikunja/docs
|
||||
context: docs/
|
||||
dockerfile: docs/Dockerfile
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: docker-arm-release
|
||||
|
||||
depends_on:
|
||||
- testing
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: arm
|
||||
|
||||
trigger:
|
||||
ref:
|
||||
- refs/heads/master
|
||||
- "refs/tags/**"
|
||||
|
||||
steps:
|
||||
- name: fetch-tags
|
||||
image: docker:git
|
||||
commands:
|
||||
- git fetch --tags
|
||||
- name: docker
|
||||
image: plugins/docker:linux-arm
|
||||
pull: true
|
||||
settings:
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
repo: vikunja/api
|
||||
auto_tag: true
|
||||
auto_tag_suffix: linux-arm
|
||||
depends_on: [ fetch-tags ]
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: docker-amd64-release
|
||||
|
||||
depends_on:
|
||||
- testing
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: amd64
|
||||
|
||||
trigger:
|
||||
ref:
|
||||
- refs/heads/master
|
||||
- "refs/tags/**"
|
||||
|
||||
steps:
|
||||
- name: fetch-tags
|
||||
image: docker:git
|
||||
commands:
|
||||
- git fetch --tags
|
||||
- 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 ]
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: docker-manifest
|
||||
|
||||
trigger:
|
||||
ref:
|
||||
- refs/heads/master
|
||||
- "refs/tags/**"
|
||||
|
||||
depends_on:
|
||||
- docker-amd64-release
|
||||
- docker-arm-release
|
||||
|
||||
steps:
|
||||
- name: manifest
|
||||
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
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: notify
|
||||
|
||||
trigger:
|
||||
ref:
|
||||
- refs/heads/master
|
||||
- "refs/tags/**"
|
||||
|
||||
depends_on:
|
||||
- release
|
||||
- docker-manifest
|
||||
|
||||
steps:
|
||||
- name: telegram
|
||||
image: appleboy/drone-telegram:1-linux-amd64
|
||||
settings:
|
||||
token:
|
||||
from_secret: TELEGRAM_TOKEN
|
||||
to:
|
||||
from_secret: TELEGRAM_TO
|
||||
message: >
|
||||
{{repo.owner}}/{{repo.name}}: \[{{build.status}}] Build {{build.number}}
|
||||
{{commit.author}} pushed to {{commit.branch}} {{commit.sha}}: `{{commit.message}}`
|
||||
Build started at {{datetime build.started "2006-Jan-02T15:04:05Z" "GMT+2"}} finished at {{datetime build.finished "2006-Jan-02T15:04:05Z" "GMT+2"}}.
|
||||
when:
|
||||
status:
|
||||
- success
|
||||
- failure
|
||||
|
||||
@@ -5,8 +5,7 @@
|
||||
# Checklist
|
||||
|
||||
* [ ] I added or improved tests
|
||||
* [ ] I pushed new or updated dependencies to the repo using `go mod vendor`
|
||||
* [ ] I added or improved docs for my feature
|
||||
* [ ] Swagger (including `make do-the-swag`)
|
||||
* [ ] Swagger (including `mage do-the-swag`)
|
||||
* [ ] Error codes
|
||||
* [ ] New config options
|
||||
* [ ] New config options (including adding them to `config.yml.saml` and running `mage generate-docs`)
|
||||
|
||||
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1 @@
|
||||
custom: https://www.buymeacoffee.com/kolaente
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -20,3 +20,6 @@ docs/resources/
|
||||
pkg/static/templates_vfsdata.go
|
||||
files/
|
||||
!pkg/files/
|
||||
vikunja-dump*
|
||||
vendor/
|
||||
os-packages/
|
||||
|
||||
82
.golangci.yml
Normal file
82
.golangci.yml
Normal file
@@ -0,0 +1,82 @@
|
||||
run:
|
||||
timeout: 5m
|
||||
tests: true
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- megacheck
|
||||
- govet
|
||||
- goconst
|
||||
- gocritic
|
||||
- gocyclo
|
||||
- goerr113
|
||||
- goheader
|
||||
- gofmt
|
||||
- goimports
|
||||
- golint
|
||||
- misspell
|
||||
disable:
|
||||
- scopelint # Obsolete, using exportloopref instead
|
||||
presets:
|
||||
- bugs
|
||||
- unused
|
||||
fast: false
|
||||
|
||||
linters-settings:
|
||||
nestif:
|
||||
min-complexity: 6
|
||||
goheader:
|
||||
template-path: code-header-template.txt
|
||||
|
||||
issues:
|
||||
exclude-rules:
|
||||
# Exclude some linters from running on tests files.
|
||||
- path: _test\.go
|
||||
linters:
|
||||
- gocyclo
|
||||
- deadcode
|
||||
- path: pkg/integrations/*
|
||||
linters:
|
||||
- gocyclo
|
||||
- deadcode
|
||||
- varcheck
|
||||
- unparam
|
||||
- bodyclose
|
||||
- path: pkg/integrations/*
|
||||
text: "unlambda"
|
||||
linters:
|
||||
- gocritic
|
||||
- path: pkg/modules/background/unsplash/unsplash\.go
|
||||
linters:
|
||||
- bodyclose
|
||||
- path: pkg/migration/*
|
||||
linters:
|
||||
- exhaustive
|
||||
- goconst
|
||||
- goerr113
|
||||
- path: pkg/models/task_collection_filter\.go
|
||||
linters:
|
||||
- exhaustive
|
||||
- path: pkg/utils/random_string\.go
|
||||
text: "G404:" # We don't care about cryptographically secure randomness when we're using that utility function.
|
||||
linters:
|
||||
- gosec
|
||||
- path: pkg/modules/dump/*
|
||||
linters:
|
||||
- goerr113
|
||||
- path: pkg/
|
||||
text: "err113: do not define dynamic errors, use wrapped static errors instead:"
|
||||
linters:
|
||||
- goerr113
|
||||
- text: "commentFormatting: put a space between `//` and comment text"
|
||||
linters:
|
||||
- gocritic
|
||||
- path: pkg/modules/migration
|
||||
linters:
|
||||
- gocyclo
|
||||
- path: pkg/routes/api/v1/docs.go
|
||||
linters:
|
||||
- goheader
|
||||
- text: "Missed string"
|
||||
linters:
|
||||
- goheader
|
||||
17
.vscode/launch.json
vendored
Normal file
17
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Launch",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "auto",
|
||||
"program": "${workspaceRoot}",
|
||||
"env": {},
|
||||
"args": []
|
||||
}
|
||||
]
|
||||
}
|
||||
502
CHANGELOG.md
502
CHANGELOG.md
@@ -7,6 +7,508 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
All releases can be found on https://code.vikunja.io/api/releases.
|
||||
|
||||
## [0.16.1] - 2021-04-22
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fix checking list rights when accessing a bucket
|
||||
* Remove old deb-structure ci step
|
||||
* Fix docker from
|
||||
|
||||
## [0.16.0] - 2021-01-10
|
||||
|
||||
### Added
|
||||
|
||||
* Add colors for caldav (#738)
|
||||
* Add email reminders (#743)
|
||||
* Add "like" filter comparator
|
||||
* Add login via email (#740)
|
||||
* Add Microsoft Todo migration (#737)
|
||||
* Add name field to users
|
||||
* Add support for migrating todoist boards (#732)
|
||||
* Add task filter for assignees (#746)
|
||||
* Add task filter for labels (#747)
|
||||
* Add task filter for lists and namespaces (#748)
|
||||
* Add task filter for reminders (#745)
|
||||
* Add task filters for kanban
|
||||
* Add testing endpoint to reset db tables (#716)
|
||||
* Add tests for sending task reminders (#757)
|
||||
* Add trello migration (#734)
|
||||
* Authentication with OpenID Connect providers (#713)
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fix completion status in DAV for OpenTasks and multiline descriptions (#697)
|
||||
* Fix docs about caldav tasks.org
|
||||
* Fix drone badge in README
|
||||
* Fix getting current user when updating avatar or user name
|
||||
* Fix go header lint
|
||||
* Fix /info endpoint 500 error when no openid providers were configured
|
||||
* Fix missing auto increments from b0d4902406 on mysql
|
||||
* Fix not possible to create tasks if metrics were enabled
|
||||
* Fix password reset without a reseet token
|
||||
* Fix task updated timestamp not being updated in the response after updating a task
|
||||
|
||||
### Changed
|
||||
|
||||
* Change avatar endpoint
|
||||
* Change license to AGPLv3
|
||||
* Clarify docs about cors configuration
|
||||
* Don't create a list identifier by default
|
||||
* Make sure all int64 db fields are using bigint when actually storing the data (#741)
|
||||
* Make sure a password reset token can be used only once
|
||||
* Make the debian repo structure for buster instead of strech
|
||||
* Refactor adding more details to tasks (#739)
|
||||
* Simplify updating task reminders
|
||||
* Update code header template
|
||||
* Update github.com/gordonklaus/ineffassign commit hash to 3b93a88 (#701)
|
||||
* Update github.com/gordonklaus/ineffassign commit hash to 8eed68e (#755)
|
||||
* Update github.com/jgautheron/goconst commit hash to b58d7cf (#702)
|
||||
* Update github.com/jgautheron/goconst commit hash to ccae5bf (#712)
|
||||
* Update github.com/jgautheron/goconst commit hash to f8e4fe8 (#703)
|
||||
* Update golang.org/x/crypto commit hash to 0c6587e (#706)
|
||||
* Update golang.org/x/crypto commit hash to 5f87f34 (#729)
|
||||
* Update golang.org/x/crypto commit hash to 8b5274c (#733)
|
||||
* Update golang.org/x/crypto commit hash to 9d13527 (#736)
|
||||
* Update golang.org/x/crypto commit hash to be400ae (#719)
|
||||
* Update golang.org/x/crypto commit hash to c8d3bf9 (#710)
|
||||
* Update golang.org/x/crypto commit hash to eec23a3 (#749)
|
||||
* Update golang.org/x/image commit hash to 35266b9 (#727)
|
||||
* Update golang.org/x/lint commit hash to 83fdc39 (#728)
|
||||
* Update golang.org/x/oauth2 commit hash to 08078c5 (#722)
|
||||
* Update golang.org/x/oauth2 commit hash to 0b49973 (#718)
|
||||
* Update golang.org/x/oauth2 commit hash to 9fd6049 (#714)
|
||||
* Update golang.org/x/sync commit hash to 09787c9 (#725)
|
||||
* Update golang.org/x/sync commit hash to 67f06af (#695)
|
||||
* Update golang.org/x/term commit hash to 2321bbc (#731)
|
||||
* Update golang.org/x/term commit hash to ee85cb9 (#726)
|
||||
* Update module cweill/gotests to v1.6.0 (#752)
|
||||
* Update module fzipp/gocyclo to v0.3.1 (#696)
|
||||
* Update module gabriel-vasile/mimetype to v1.1.2 (#708)
|
||||
* Update module getsentry/sentry-go to v0.8.0 (#709)
|
||||
* Update module getsentry/sentry-go to v0.9.0 (#723)
|
||||
* Update module go-redis/redis/v8 to v8.4.4 (#742)
|
||||
* Update module go-redis/redis/v8 to v8.4.6 (#756)
|
||||
* Update module go-redis/redis/v8 to v8.4.7 (#758)
|
||||
* Update module go-redis/redis/v8 to v8.4.8 (#759)
|
||||
* Update module lib/pq to v1.9.0 (#717)
|
||||
* Update module magefile/mage to v1.11.0 (#754)
|
||||
* Update module mattn/go-sqlite3 to v1.14.5 (#711)
|
||||
* Update module mattn/go-sqlite3 to v1.14.6 (#751)
|
||||
* Update module pquerna/otp to v1.3.0 (#705)
|
||||
* Update module prometheus/client_golang to v1.9.0 (#735)
|
||||
* Update module spf13/afero to v1.5.0 (#724)
|
||||
* Update module spf13/afero to v1.5.1 (#730)
|
||||
* Update module src.techknowlogick.com/xgo to v1.2.0+1.15.6 (#720)
|
||||
* Update module src.techknowlogick.com/xormigrate to v1.4.0 (#700)
|
||||
* Update module swaggo/swag to v1.6.9 (#694)
|
||||
* Update module swaggo/swag to v1.7.0 (#721)
|
||||
* Update module ulule/limiter/v3 to v3.8.0 (#699)
|
||||
* Update nfpm config for nfpm v2
|
||||
* Use db sessions everywere (#750)
|
||||
|
||||
## [0.15.1] - 2020-10-20
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fix not possible to create tasks if metrics were enabled
|
||||
|
||||
## [0.15.0] - 2020-10-19
|
||||
|
||||
### Added
|
||||
|
||||
* Add app support info for DAV (#692)
|
||||
* Add better tests for namespaces
|
||||
* Add caldav enabled/disabled to /info endpoint
|
||||
* Add checks if tasks exist in maps before trying to access them
|
||||
* Add config option to force ssl connections to connect with the mailer
|
||||
* Add dav proxy directions to example proxy configurations
|
||||
* Add docs about using vikunja with utf-8 characters
|
||||
* Add FreeBSD guide to installation docs
|
||||
* Add github sponsor link
|
||||
* Add Golangci Lint (#676)
|
||||
* Add mage command to create a new migration
|
||||
* Add option to configure legal urls
|
||||
* Add rootpath to deb command to not include everything in the deb file
|
||||
* Add toc to docs
|
||||
* Add update route to toggle team member admin status
|
||||
* Add util function to move files
|
||||
* Disable gocyclo for migration modules
|
||||
* Favorite lists (#654)
|
||||
* Favorite tasks (#653)
|
||||
* Generate config docs from sample config (#684)
|
||||
* Kanban bucket limits (#652)
|
||||
* Key-Value Storages (#674)
|
||||
* Manage users via cli (#632)
|
||||
* Mention client_max_body_size in nginx proxy settings
|
||||
* More avatar providers (#622)
|
||||
* Return rights when reading a single item (#626)
|
||||
* Saved filters (#655)
|
||||
|
||||
### Fixed
|
||||
|
||||
* Cleanup references to make
|
||||
* Don't add a subtask to the top level of tasks to not add it twice in the list
|
||||
* Fetch tasks for caldav lists (#641)
|
||||
* Fix building for darwin with mage
|
||||
* Fix creating lists with non ascii characters (#607)
|
||||
* Fix decoding active users from redis
|
||||
* Fix dockerimage build
|
||||
* Fix docs index links
|
||||
* Fix duplicating a list with background
|
||||
* "Fix" gocyclo
|
||||
* Fix loading list background information for uploaded backgrounds
|
||||
* Fix migrating items with large items from todoist
|
||||
* Fix nfpm command in drone
|
||||
* Fix parsing todoist reminder dates
|
||||
* Fix reading passwords on windows
|
||||
* Fix release commands in drone
|
||||
* Fix release trigger
|
||||
* Fix release trigger in drone
|
||||
* Fix token renew for link shares
|
||||
* Fix trigger for pushing release artifacts to drone
|
||||
* Fix updating team admin status
|
||||
* Fix upload avatar not working
|
||||
* Fix users with disabled totp but not enrolled being unable to login
|
||||
* Makefile: make add EXTRA_GOFLAG to GOFLAGS (#605)
|
||||
* Make sure built binary files are executable when compressing with upx
|
||||
* Make sure lists which would have a duplicate identifier can still be duplicated
|
||||
* Make sure the metrics map accesses only happen explicitly
|
||||
* Make sure to copy the permissions as well when moving files
|
||||
* Make sure to only initialize all variables when needed
|
||||
* Make sure to require admin rights when modifying list/namespace users to be consistent with teams
|
||||
* Make sure we have git installed when building os packages
|
||||
* Make sure we have go installed when building os packages (for build step dependencies)
|
||||
* Only check if a bucket limit is exceeded when moving a task between buckets
|
||||
* Only try to download attachments from todoist when there is a url
|
||||
* Pin telegram notification plugin in drone
|
||||
* Regenerate swagger docs
|
||||
* Skip directories when moving build release artefacts in drone
|
||||
* Support absolute iCal timestamps in CalDAV requests (#691)
|
||||
* Work around tasks with attachments not being duplicated
|
||||
|
||||
### Changed
|
||||
|
||||
* Replace renovate tokens with env
|
||||
* Switch s3 release bucket to scaleway
|
||||
* Switch to mage (#651)
|
||||
* Testing improvements (#666)
|
||||
* Update docs with testmail command + reorder
|
||||
* Update github.com/asaskevich/govalidator commit hash to 29e1ff8 (#639)
|
||||
* Update github.com/asaskevich/govalidator commit hash to 50839af (#637)
|
||||
* Update github.com/asaskevich/govalidator commit hash to 7a23bdc (#657)
|
||||
* Update github.com/asaskevich/govalidator commit hash to df4adff (#552)
|
||||
* Update github.com/c2h5oh/datasize commit hash to 48ed595 (#644)
|
||||
* Update github.com/gordonklaus/ineffassign commit hash to e36bfde (#625)
|
||||
* Update github.com/jgautheron/goconst commit hash to 8f5268c (#658)
|
||||
* Update github.com/shurcooL/vfsgen commit hash to 0d455de (#642)
|
||||
* Update golang.org/x/crypto commit hash to 123391f (#619)
|
||||
* Update golang.org/x/crypto commit hash to 5c72a88 (#640)
|
||||
* Update golang.org/x/crypto commit hash to 7f63de1 (#672)
|
||||
* Update golang.org/x/crypto commit hash to 84dcc77 (#678)
|
||||
* Update golang.org/x/crypto commit hash to 948cd5f (#609)
|
||||
* Update golang.org/x/crypto commit hash to 9e8e0b3 (#685)
|
||||
* Update golang.org/x/crypto commit hash to ab33eee (#608)
|
||||
* Update golang.org/x/crypto commit hash to afb6bcd (#668)
|
||||
* Update golang.org/x/crypto commit hash to c90954c (#671)
|
||||
* Update golang.org/x/crypto commit hash to eb9a90e (#669)
|
||||
* Update golang.org/x/image commit hash to 4578eab (#663)
|
||||
* Update golang.org/x/image commit hash to a67d67e (#664)
|
||||
* Update golang.org/x/image commit hash to e162460 (#665)
|
||||
* Update golang.org/x/image commit hash to e59bae6 (#659)
|
||||
* Update golang.org/x/sync commit hash to 3042136 (#667)
|
||||
* Update golang.org/x/sync commit hash to b3e1573 (#675)
|
||||
* Update module 4d63.com/tz to v1.2.0 (#631)
|
||||
* Update module fzipp/gocyclo to v0.2.0 (#686)
|
||||
* Update module fzipp/gocyclo to v0.3.0 (#687)
|
||||
* Update module getsentry/sentry-go to v0.7.0 (#617)
|
||||
* Update module go-errors/errors to v1.1.1 (#677)
|
||||
* Update module go-testfixtures/testfixtures/v3 to v3.4.0 (#627)
|
||||
* Update module go-testfixtures/testfixtures/v3 to v3.4.1 (#693)
|
||||
* Update module iancoleman/strcase to v0.1.0 (#636)
|
||||
* Update module iancoleman/strcase to v0.1.1 (#645)
|
||||
* Update module iancoleman/strcase to v0.1.2 (#660)
|
||||
* Update module imdario/mergo to v0.3.10 (#615)
|
||||
* Update module imdario/mergo to v0.3.11 (#629)
|
||||
* Update module labstack/echo/v4 to v4.1.17 (#646)
|
||||
* Update module lib/pq to v1.7.1 (#616)
|
||||
* Update module lib/pq to v1.8.0 (#618)
|
||||
* Update module mattn/go-sqlite3 to v1.14.1 (#638)
|
||||
* Update module mattn/go-sqlite3 to v1.14.2 (#647)
|
||||
* Update module mattn/go-sqlite3 to v1.14.3 (#661)
|
||||
* Update module mattn/go-sqlite3 to v1.14.4 (#670)
|
||||
* Update module prometheus/client_golang to v1.8.0 (#681)
|
||||
* Update module spf13/afero to v1.3.2 (#610)
|
||||
* Update module spf13/afero to v1.3.3 (#623)
|
||||
* Update module spf13/afero to v1.3.4 (#628)
|
||||
* Update module spf13/afero to v1.3.5 (#650)
|
||||
* Update module spf13/afero to v1.4.0 (#662)
|
||||
* Update module spf13/afero to v1.4.1 (#673)
|
||||
* Update module spf13/cobra to v1.1.0 (#679)
|
||||
* Update module spf13/cobra to v1.1.1 (#690)
|
||||
* Update module spf13/viper to v1.7.1 (#620)
|
||||
* Update module src.techknowlogick.com/xgo to v1.1.0+1.15.0 (#630)
|
||||
* Update module src.techknowlogick.com/xgo to v1 (#613)
|
||||
* Update module swaggo/swag to v1.6.8 (#680)
|
||||
* Update renovate token
|
||||
* Update src.techknowlogick.com/xgo commit hash to 7c2e3c9 (#611)
|
||||
* Update src.techknowlogick.com/xgo commit hash to 96de19c (#612)
|
||||
* update theme
|
||||
* Update xgo to v1.0.0+1.14.6
|
||||
* Use db sessions for task-related things (#621)
|
||||
* Use nfpm to build deb, rpm and apk packages (#689)
|
||||
|
||||
## [0.14.1] - 2020-07-07
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fix creating lists with non ascii characters (#607)
|
||||
* Fix decoding active users from redis
|
||||
* Fix parsing todoist reminder dates
|
||||
* Make sure the metrics map accesses only happen explicitly
|
||||
|
||||
### Changed
|
||||
|
||||
* Update docs theme
|
||||
|
||||
## [0.14.0] - 2020-07-01
|
||||
|
||||
### Added
|
||||
|
||||
* Add ability to run the docker container with configurable user and group ids
|
||||
* Add better errors if the sqlite db file is not writable
|
||||
* Add cache for initial unsplash collection
|
||||
* Add docker setup guide from start to finish
|
||||
* Add docs for restore
|
||||
* Add dump command (#592)
|
||||
* Add section to full-docker-example.md for Caddy v2 (#595)
|
||||
* Add go version to version command
|
||||
* Add list background information when getting all lists
|
||||
* Add logging if downloading an image from unsplash fails
|
||||
* Add migration test in drone (#585)
|
||||
* Add option to disable totp for everyone
|
||||
* Add plausible to docs
|
||||
* Add restarting commands to all example docker compose files
|
||||
* Add seperate docker pipeline for amd64 and arm
|
||||
* Add test mail command (#571)
|
||||
* Add todoist migrator to available migrators in info endpoint if it is enabled
|
||||
* Add unsplash image proxy for images and thumbnails
|
||||
* Add returning unsplash info when searching
|
||||
* Don't return all tasks when a user has no lists
|
||||
* Duplicate Lists (#603)
|
||||
* Enable upload backgrounds by default
|
||||
* Generate a random list identifier based on the list title
|
||||
* List Backgrounds (#568)
|
||||
* List Background upload (#582)
|
||||
* Repeat tasks after completion (#587)
|
||||
* Restore command (#593)
|
||||
* Sentry integration (#591)
|
||||
* Todoist Migration (#566)
|
||||
|
||||
### Fixed
|
||||
|
||||
* Ensure consistent naming of title fields (#528)
|
||||
* Ensure task dates are in the future if a task has a repeating interval (#586)
|
||||
* Fix caching of initial unsplash results per page
|
||||
* Fix case-insensitive task search for postgresql (#524)
|
||||
* Fix docker manifest build
|
||||
* Fix docker multiarch build
|
||||
* Fix docs theme build
|
||||
* Fix getting unsplash thumbnails for non "photo-*" urls
|
||||
* Fix migration 20200425182634
|
||||
* Fix migration 20200516123847
|
||||
* Fix migration to add position to task
|
||||
* Fix misspell
|
||||
* Fix namespace title not being updated
|
||||
* Fix not loading timezones on all operating systems
|
||||
* Fix proxying unsplash images (security)
|
||||
* Fix removing existing sqlite files
|
||||
* Fix resetting list, label & namespace colors
|
||||
* Fix searching for unsplash pictures with words that contain a space
|
||||
* Fix setting a list identifier to empty
|
||||
* Fix sqlite db not working when creating a new one
|
||||
* Fix sqlite path in default config
|
||||
* Fix swagger docs
|
||||
* Fix updating the index when moving a task
|
||||
* Prevent crashing when trying to register with an empty payload
|
||||
* Properly ping unsplash when using unsplash images
|
||||
* Return errors when dumping
|
||||
* Set the list identifier when creating a new task
|
||||
|
||||
### Changed
|
||||
|
||||
* Expose namespace id when querying lists
|
||||
* Improve getting all namespaces performance (#526)
|
||||
* Improve memory usage of dump by not loading all files in memory prior to adding them to the zip
|
||||
* Improve metrics performance
|
||||
* Load the list when setting a background
|
||||
* Make the db timezone migration mysql compatible
|
||||
* Make the `_unix` suffix optional when sorting tasks
|
||||
* Migrate all timestamps to real iso dates (#594)
|
||||
* Make sure docker images are only built when tests pass
|
||||
* Remove build date from binary
|
||||
* Remove dependencies on build step to speed up test pipeline (#521)
|
||||
* Remove go mod vendor todo from pr template now that we don't keep dependencies in the repo anymore
|
||||
* Remove migration dependency to models
|
||||
* Remove min length for labels, lists, namespaces, tasks and teams
|
||||
* Remove vendored dependencies
|
||||
* Reorganize cmd init functions
|
||||
* Set unsplash empty collection caching to one hour
|
||||
* Simplify pipeline & add docker manifest step
|
||||
* Update alpine Docker tag to v3.12 (#573)
|
||||
* Update and fix staticcheck
|
||||
* Update dependency github.com/mattn/go-sqlite3 to v1.14.0
|
||||
* Update github.com/shurcooL/vfsgen commit hash to 92b8a71 (#599)
|
||||
* Update golang.org/x/crypto commit hash to 279210d (#577)
|
||||
* Update golang.org/x/crypto commit hash to 70a84ac (#578)
|
||||
* Update golang.org/x/crypto commit hash to 75b2880 (#596)
|
||||
* Update module go-redis/redis/v7 to v7.3.0 (#565)
|
||||
* Update module go-redis/redis/v7 to v7.4.0 (#579)
|
||||
* Update module go-testfixtures/testfixtures/v3 to v3.3.0 (#600)
|
||||
* Update module lib/pq to v1.6.0 (#572)
|
||||
* Update module lib/pq to v1.7.0 (#581)
|
||||
* Update module prometheus/client_golang to v1.7.0 (#589)
|
||||
* Update module prometheus/client_golang to v1.7.1 (#597)
|
||||
* Update module spf13/afero to v1.3.0 (#588)
|
||||
* Update module spf13/afero to v1.3.1 (#602)
|
||||
* Update module spf13/cobra to v1 (#511)
|
||||
* Update module src.techknowlogick.com/xormigrate to v1.2.1 (#574)
|
||||
* Update module src.techknowlogick.com/xormigrate to v1.3.0 (#590)
|
||||
* Update module stretchr/testify to v1.6.0 (#570)
|
||||
* Update module stretchr/testify to v1.6.1 (#580)
|
||||
* Update module swaggo/swag to v1.6.7 (#601)
|
||||
* Update src.techknowlogick.com/xgo commit hash to 209a5cf (#523)
|
||||
* Update src.techknowlogick.com/xgo commit hash to a09175e (#576)
|
||||
* Update src.techknowlogick.com/xgo commit hash to eeb7c0a (#575)
|
||||
* update theme
|
||||
* Update theme
|
||||
* Update web handler
|
||||
* Update xorm.io/xorm 1.0.1 -> 1.0.2
|
||||
* Use the db logger instance for logging migration related stuff
|
||||
|
||||
## [0.13.1] - 2020-05-19
|
||||
|
||||
### Fixed
|
||||
|
||||
* Don't get all tasks if a user has no lists
|
||||
|
||||
## [0.13] - 2020-05-12
|
||||
|
||||
#### Added
|
||||
|
||||
* Add 2fa for authentification (#383)
|
||||
* Add categories to error docs
|
||||
* Add changing email for users
|
||||
* Add community link
|
||||
* Add configuration options for log level
|
||||
* Add creating a new first bucket when creating a new list
|
||||
* Add docs for changing frontend url
|
||||
* Add endpoint to disable totp auth
|
||||
* Add endpoint to get the current users totp status
|
||||
* Add explanation to docs about cors
|
||||
* Add github token for renovate (#164)
|
||||
* Add gosec static analysis
|
||||
* Add moving tasks between lists (#389)
|
||||
* Add real buckets for tasks which don't have one (#446)
|
||||
* Add traefik 2 example configuration
|
||||
* Configure Renovate (#159)
|
||||
* Kanban (#393)
|
||||
* Task filters (#243)
|
||||
* Task Position (#412)
|
||||
|
||||
#### Fixed
|
||||
|
||||
* Add checking and logging when trying to put a task into a nonexisting bucket
|
||||
* Fix bucket ID being reset with no need to do so
|
||||
* Fix creating new things with a link share auth
|
||||
* Fix dependencies
|
||||
* Fix gosec in drone
|
||||
* Fix link share creation & creating admin link shares without admin rights
|
||||
* Fix moving tasks back into the empty (ID: 0) bucket
|
||||
* Fix moving tasks in buckets
|
||||
* Fix not moving its bucket when moving a task between lists
|
||||
* Fix pagination count for task collection
|
||||
* Fix parsing array style comparators by query param
|
||||
* Fix reference to reverse proxies in docs
|
||||
* Fix removing the last bucket
|
||||
* Fix replace statements for tail
|
||||
* Fix team rights not updating for namespace rights
|
||||
* Fix tests after renaming json fields to snake_case
|
||||
* Fix total label count when getting all labels (#477)
|
||||
* Remove setting task bucket to 0
|
||||
* Task Filter Fixes (#495)
|
||||
|
||||
#### Changed
|
||||
|
||||
* Change all json fields to snake_case
|
||||
* Change totp secret datatype from varchar to text
|
||||
* Update alpine Docker tag to v3.11 (#160)
|
||||
* Update docs theme
|
||||
* Update github.com/c2h5oh/datasize commit hash to 28bbd47 (#212)
|
||||
* Update github.com/gordonklaus/ineffassign commit hash to 7953dde (#233)
|
||||
* Update github.com/jgautheron/goconst commit hash to cda7ea3 (#228)
|
||||
* Update github.com/shurcooL/httpfs commit hash to 8d4bc4b (#229)
|
||||
* Update golang.org/x/crypto commit hash to 056763e (#222)
|
||||
* Update golang.org/x/crypto commit hash to 06a226f (#504)
|
||||
* Update golang.org/x/crypto commit hash to 0848c95 (#371)
|
||||
* Update golang.org/x/crypto commit hash to 3c4aac8 (#419)
|
||||
* Update golang.org/x/crypto commit hash to 44a6062 (#429)
|
||||
* Update golang.org/x/crypto commit hash to 4b2356b (#475)
|
||||
* Update golang.org/x/crypto commit hash to 4bdfaf4 (#438)
|
||||
* Update golang.org/x/crypto commit hash to 729f1e8 (#458)
|
||||
* Update golang.org/x/crypto commit hash to a76a400 (#411)
|
||||
* Update golang.org/x/lint commit hash to 738671d (#223)
|
||||
* Update module go-redis/redis to v6.15.7 (#234)
|
||||
* Update module go-redis/redis to v6.15.7 (#290)
|
||||
* Update module go-redis/redis to v7 (#277)
|
||||
* Update module go-redis/redis to v7 (#309)
|
||||
* Update module go-testfixtures/testfixtures/v3 to v3.1.2 (#457)
|
||||
* Update module go-testfixtures/testfixtures/v3 to v3.2.0 (#505)
|
||||
* Update module imdario/mergo to v0.3.9 (#238)
|
||||
* Update module labstack/echo/v4 to v4.1.16 (#241)
|
||||
* Update module lib/pq to v1.4.0 (#428)
|
||||
* Update module lib/pq to v1.5.0 (#476)
|
||||
* Update module lib/pq to v1.5.1 (#485)
|
||||
* Update module lib/pq to v1.5.2 (#491)
|
||||
* Update module olekukonko/tablewriter to v0.0.4 (#240)
|
||||
* Update module prometheus/client_golang to v0.9.4 (#245)
|
||||
* Update module prometheus/client_golang to v1
|
||||
* Update module prometheus/client_golang to v1.6.0 (#463)
|
||||
* Update module spf13/cobra to v0.0.7 (#271)
|
||||
* Update module spf13/viper to v1.6.2 (#272)
|
||||
* Update module spf13/viper to v1.6.3 (#291)
|
||||
* Update module spf13/viper to v1.7.0 (#494)
|
||||
* Update module stretchr/testify to v1.5.1 (#274)
|
||||
* Update Renovate Configuration (#161)
|
||||
* Update src.techknowlogick.com/xgo commit hash to bb0faa3 (#279)
|
||||
* Update src.techknowlogick.com/xgo commit hash to c43d4c4 (#224)
|
||||
* Update xorm redis cacher to use the xorm logger instead of a special seperate one
|
||||
* Update xorm to v1 (#323)
|
||||
|
||||
## [0.12] - 2020-04-04
|
||||
|
||||
#### Added
|
||||
|
||||
* Add support for archiving lists and namespaces (#152)
|
||||
* Colors for lists and namespaces (#155)
|
||||
* Add build time to compile flags
|
||||
* Add proxying gravatar requests for user avatars (#148)
|
||||
* Add empty avatar provider (#149)
|
||||
* expand relative path ~/.config/vikunja to $HOME/.config/vikunja **WINDOWS** (#147)
|
||||
* Show lists as archived if their namespace is archived
|
||||
|
||||
#### Fixed
|
||||
|
||||
* Workaround for timezones on windows (#151)
|
||||
* Fix getting one namespace
|
||||
* Fix getting the authenticated user with caldav
|
||||
* Fix searching for config in home directories
|
||||
* Fix updating lists with an identifier
|
||||
|
||||
#### Changed
|
||||
|
||||
* Change release bucket
|
||||
|
||||
## [0.11] - 2020-03-01
|
||||
|
||||
### Added
|
||||
|
||||
22
Dockerfile
22
Dockerfile
@@ -1,12 +1,11 @@
|
||||
|
||||
##############
|
||||
# Build stage
|
||||
FROM golang:1-alpine AS build-env
|
||||
FROM golang:1-alpine3.12 AS build-env
|
||||
|
||||
ARG VIKUNJA_VERSION
|
||||
ENV TAGS "sqlite"
|
||||
ENV GO111MODULE=on
|
||||
ENV GOFLAGS=-mod=vendor
|
||||
|
||||
# Build deps
|
||||
RUN apk --no-cache add build-base git
|
||||
@@ -17,21 +16,29 @@ WORKDIR ${GOPATH}/src/code.vikunja.io/api
|
||||
|
||||
# Checkout version if set
|
||||
RUN if [ -n "${VIKUNJA_VERSION}" ]; then git checkout "${VIKUNJA_VERSION}"; fi \
|
||||
&& make clean generate build
|
||||
&& go install github.com/magefile/mage \
|
||||
&& mage build:clean build:build
|
||||
|
||||
###################
|
||||
# The actual image
|
||||
# Note: I wanted to use the scratch image here, but unfortunatly the go-sqlite bindings require cgo and
|
||||
# because of this, the container would not start when I compiled the image without cgo.
|
||||
FROM alpine:3.9
|
||||
FROM alpine:3.12
|
||||
LABEL maintainer="maintainers@vikunja.io"
|
||||
|
||||
WORKDIR /app/vikunja/
|
||||
COPY --from=build-env /go/src/code.vikunja.io/api/vikunja .
|
||||
RUN adduser -S -D vikunja -h /app/vikunja -H \
|
||||
&& chown vikunja -R /app/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
|
||||
|
||||
# Fix time zone settings not working
|
||||
RUN apk --no-cache add tzdata
|
||||
|
||||
@@ -40,6 +47,5 @@ RUN mkdir /app/vikunja/files && \
|
||||
chown -R vikunja /app/vikunja/files
|
||||
VOLUME /app/vikunja/files
|
||||
|
||||
USER vikunja
|
||||
CMD ["/app/vikunja/vikunja"]
|
||||
CMD ["/run.sh"]
|
||||
EXPOSE 3456
|
||||
|
||||
143
LICENSE
143
LICENSE
@@ -1,5 +1,5 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
@@ -7,17 +7,15 @@
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
our General Public Licenses are intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
software for all its users.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
@@ -26,44 +24,34 @@ them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing under
|
||||
this license.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
@@ -72,7 +60,7 @@ modification follow.
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
@@ -549,35 +537,45 @@ to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your version
|
||||
supports such interaction) an opportunity to receive the Corresponding
|
||||
Source of your version by providing access to the Corresponding Source
|
||||
from a network server at no charge, through some standard or customary
|
||||
means of facilitating copying of software. This Corresponding Source
|
||||
shall include the Corresponding Source for any work covered by version 3
|
||||
of the GNU General Public License that is incorporated pursuant to the
|
||||
following paragraph.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
under version 3 of the GNU General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
but the work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
the GNU Affero General Public License from time to time. Such new versions
|
||||
will be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Program specifies that a certain numbered version of the GNU Affero General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
GNU Affero General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
versions of the GNU Affero General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
@@ -635,40 +633,29 @@ the "copyright" line and a pointer to where the full notice is found.
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for the
|
||||
specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
245
Makefile
245
Makefile
@@ -1,245 +0,0 @@
|
||||
DIST := dist
|
||||
IMPORT := code.vikunja.io/api
|
||||
|
||||
SED_INPLACE := sed -i
|
||||
|
||||
ifeq ($(OS), Windows_NT)
|
||||
EXECUTABLE := vikunja.exe
|
||||
else
|
||||
EXECUTABLE := vikunja
|
||||
UNAME_S := $(shell uname -s)
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
SED_INPLACE := sed -i ''
|
||||
endif
|
||||
endif
|
||||
|
||||
GOFILES := $(shell find . -name "*.go" -type f ! -path "./vendor/*" ! -path "*/bindata.go")
|
||||
GOFMT ?= gofmt -s
|
||||
|
||||
GOFLAGS := -v -mod=vendor
|
||||
EXTRA_GOFLAGS ?=
|
||||
|
||||
LDFLAGS := -X "code.vikunja.io/api/pkg/version.Version=$(shell git describe --tags --always --abbrev=10 | sed 's/-/+/' | sed 's/^v//' | sed 's/-g/-/')" -X "main.Tags=$(TAGS)"
|
||||
|
||||
PACKAGES ?= $(filter-out code.vikunja.io/api/pkg/integrations,$(shell go list -mod=vendor ./... | grep -v /vendor/))
|
||||
SOURCES ?= $(shell find . -name "*.go" -type f)
|
||||
|
||||
TAGS ?=
|
||||
|
||||
ifeq ($(OS), Windows_NT)
|
||||
EXECUTABLE := vikunja.exe
|
||||
else
|
||||
EXECUTABLE := vikunja
|
||||
endif
|
||||
|
||||
ifneq ($(DRONE_TAG),)
|
||||
VERSION ?= $(subst v,,$(DRONE_TAG))
|
||||
else
|
||||
ifneq ($(DRONE_BRANCH),)
|
||||
VERSION ?= $(subst release/v,,$(DRONE_BRANCH))
|
||||
else
|
||||
VERSION ?= master
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(DRONE_WORKSPACE),'')
|
||||
BINLOCATION := $(EXECUTABLE)
|
||||
else
|
||||
BINLOCATION := $(DIST)/binaries/$(EXECUTABLE)-$(VERSION)-linux-amd64
|
||||
endif
|
||||
|
||||
ifeq ($(VERSION),master)
|
||||
PKGVERSION := $(shell git describe --tags --always --abbrev=10 | sed 's/-/+/' | sed 's/^v//' | sed 's/-g/-/')
|
||||
else
|
||||
PKGVERSION := $(VERSION)
|
||||
endif
|
||||
|
||||
.PHONY: all
|
||||
all: build
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
go clean ./...
|
||||
rm -rf $(EXECUTABLE) $(DIST) $(BINDATA)
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
# We run everything sequentially and not in parallel to prevent issues with real test databases
|
||||
VIKUNJA_SERVICE_ROOTPATH=$(shell pwd) go test $(GOFLAGS) -p 1 -cover -coverprofile cover.out $(PACKAGES)
|
||||
|
||||
.PHONY: test-coverage
|
||||
test-coverage: test
|
||||
go tool cover -html=cover.out -o cover.html
|
||||
|
||||
.PHONY: integration-test
|
||||
integration-test:
|
||||
# We run everything sequentially and not in parallel to prevent issues with real test databases
|
||||
VIKUNJA_SERVICE_ROOTPATH=$(shell pwd) go test $(GOFLAGS) -p 1 code.vikunja.io/api/pkg/integrations
|
||||
|
||||
.PHONY: lint
|
||||
lint:
|
||||
@hash golint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
go install $(GOFLAGS) golang.org/x/lint/golint; \
|
||||
fi
|
||||
for PKG in $(PACKAGES); do golint -set_exit_status $$PKG || exit 1; done;
|
||||
|
||||
.PHONY: fmt
|
||||
fmt:
|
||||
$(GOFMT) -w $(GOFILES)
|
||||
|
||||
.PHONY: fmt-check
|
||||
fmt-check:
|
||||
# get all go files and run go fmt on them
|
||||
@diff=$$($(GOFMT) -d $(GOFILES)); \
|
||||
if [ -n "$$diff" ]; then \
|
||||
echo "Please run 'make fmt' and commit the result:"; \
|
||||
echo "$${diff}"; \
|
||||
exit 1; \
|
||||
fi;
|
||||
|
||||
.PHONY: build
|
||||
build: generate $(EXECUTABLE)
|
||||
|
||||
.PHONY: generate
|
||||
generate:
|
||||
go generate code.vikunja.io/api/pkg/static
|
||||
|
||||
$(EXECUTABLE): $(SOURCES)
|
||||
go build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)' -o $@
|
||||
|
||||
.PHONY: compress-build
|
||||
compress-build:
|
||||
upx -9 $(EXECUTABLE)
|
||||
|
||||
.PHONY: release
|
||||
release: release-dirs release-windows release-linux release-darwin release-copy release-check release-os-package release-zip
|
||||
|
||||
.PHONY: release-dirs
|
||||
release-dirs:
|
||||
mkdir -p $(DIST)/binaries $(DIST)/release $(DIST)/zip
|
||||
|
||||
.PHONY: release-windows
|
||||
release-windows:
|
||||
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
go install $(GOFLAGS) src.techknowlogick.com/xgo; \
|
||||
fi
|
||||
xgo -dest $(DIST)/binaries -tags 'netgo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out vikunja-$(VERSION) .
|
||||
ifneq ($(DRONE_WORKSPACE),'')
|
||||
mv /build/* $(DIST)/binaries
|
||||
endif
|
||||
|
||||
.PHONY: release-linux
|
||||
release-linux:
|
||||
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
go install $(GOFLAGS) src.techknowlogick.com/xgo; \
|
||||
fi
|
||||
xgo -dest $(DIST)/binaries -tags 'netgo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'linux/*' -out vikunja-$(VERSION) .
|
||||
ifneq ($(DRONE_WORKSPACE),'')
|
||||
mv /build/* $(DIST)/binaries
|
||||
endif
|
||||
|
||||
.PHONY: release-darwin
|
||||
release-darwin:
|
||||
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
go install $(GOFLAGS) src.techknowlogick.com/xgo; \
|
||||
fi
|
||||
xgo -dest $(DIST)/binaries -tags 'netgo $(TAGS)' -ldflags '$(LDFLAGS)' -targets 'darwin/*' -out vikunja-$(VERSION) .
|
||||
ifneq ($(DRONE_WORKSPACE),'')
|
||||
mv /build/* $(DIST)/binaries
|
||||
endif
|
||||
|
||||
# Compresses all releases made by make release-* but not mips* releases since upx can't handle these.
|
||||
.PHONY: release-compress
|
||||
release-compress:
|
||||
$(foreach file,$(filter-out $(wildcard $(wildcard $(DIST)/binaries/$(EXECUTABLE)-*mips*)),$(wildcard $(DIST)/binaries/$(EXECUTABLE)-*)), upx -9 $(file);)
|
||||
|
||||
.PHONY: release-copy
|
||||
release-copy:
|
||||
$(foreach file,$(wildcard $(DIST)/binaries/$(EXECUTABLE)-*),cp $(file) $(DIST)/release/$(notdir $(file));)
|
||||
|
||||
.PHONY: release-check
|
||||
release-check:
|
||||
cd $(DIST)/release; $(foreach file,$(wildcard $(DIST)/release/$(EXECUTABLE)-*),sha256sum $(notdir $(file)) > $(notdir $(file)).sha256;)
|
||||
|
||||
.PHONY: release-os-package
|
||||
release-os-package:
|
||||
$(foreach file,$(filter-out %.sha256,$(wildcard $(DIST)/release/$(EXECUTABLE)-*)),mkdir $(file)-full;mv $(file) $(file)-full/; mv $(file).sha256 $(file)-full/; cp config.yml.sample $(file)-full/config.yml; cp LICENSE $(file)-full/; )
|
||||
|
||||
.PHONY: release-zip
|
||||
release-zip:
|
||||
$(foreach file,$(wildcard $(DIST)/release/$(EXECUTABLE)-*),cd $(file); zip -r ../../zip/$(shell basename $(file)).zip *; cd ../../../; )
|
||||
|
||||
# Builds a deb package using fpm from a previously created binary (using make build)
|
||||
.PHONY: build-deb
|
||||
build-deb:
|
||||
fpm -s dir -t deb --url https://vikunja.io -n vikunja -v $(PKGVERSION) --license GPLv3 --directories /opt/vikunja --after-install ./build/after-install.sh --description 'Vikunja is an open-source todo application, written in Go. It lets you create lists,tasks and share them via teams or directly between users.' -m maintainers@vikunja.io ./$(BINLOCATION)=/opt/vikunja/vikunja ./config.yml.sample=/etc/vikunja/config.yml;
|
||||
|
||||
.PHONY: reprepro
|
||||
reprepro:
|
||||
reprepro_expect debian includedeb strech ./$(EXECUTABLE)_$(PKGVERSION)_amd64.deb
|
||||
|
||||
.PHONY: got-swag
|
||||
got-swag: do-the-swag
|
||||
@diff=$$(git diff docs/swagger/swagger.json); \
|
||||
if [ -n "$$diff" ]; then \
|
||||
echo "Please run 'make do-the-swag' and commit the result:"; \
|
||||
echo "$${diff}"; \
|
||||
exit 1; \
|
||||
fi;
|
||||
|
||||
.PHONY: do-the-swag
|
||||
do-the-swag:
|
||||
@hash swag > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
go install $(GOFLAGS) github.com/swaggo/swag/cmd/swag; \
|
||||
fi
|
||||
swag init -g pkg/routes/routes.go -o ./pkg/swagger;
|
||||
# Fix the generated swagger file, currently a workaround until swaggo can properly use go mod
|
||||
sed -i '/"definitions": {/a "code.vikunja.io.web.HTTPError": {"type": "object","properties": {"code": {"type": "integer"},"message": {"type": "string"}}},' pkg/swagger/docs.go;
|
||||
sed -i 's/code.vikunja.io\/web.HTTPError/code.vikunja.io.web.HTTPError/g' pkg/swagger/docs.go;
|
||||
sed -i 's/package\ docs/package\ swagger/g' pkg/swagger/docs.go;
|
||||
sed -i 's/` + \\"`\\" + `/` + "`" + `/g' pkg/swagger/docs.go;
|
||||
|
||||
.PHONY: misspell-check
|
||||
misspell-check:
|
||||
@hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
go install $(GOFLAGS) github.com/client9/misspell/cmd/misspell; \
|
||||
fi
|
||||
for S in $(GOFILES); do misspell -error $$S || exit 1; done;
|
||||
|
||||
.PHONY: ineffassign-check
|
||||
ineffassign-check:
|
||||
@hash ineffassign > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
go install $(GOFLAGS) github.com/gordonklaus/ineffassign; \
|
||||
fi
|
||||
for S in $(GOFILES); do ineffassign $$S || exit 1; done;
|
||||
|
||||
.PHONY: gocyclo-check
|
||||
gocyclo-check:
|
||||
@hash gocyclo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
go get -u github.com/fzipp/gocyclo; \
|
||||
go install $(GOFLAGS) github.com/fzipp/gocyclo; \
|
||||
fi
|
||||
for S in $(GOFILES); do gocyclo -over 24 $$S || exit 1; done;
|
||||
|
||||
.PHONY: static-check
|
||||
static-check:
|
||||
@hash staticcheck > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
go get -u honnef.co/go/tools; \
|
||||
go install $(GOFLAGS) honnef.co/go/tools/cmd/staticcheck; \
|
||||
fi
|
||||
staticcheck $(PACKAGES);
|
||||
|
||||
.PHONY: gosec-check
|
||||
gosec-check:
|
||||
@hash ./bin/gosec > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
curl -sfL https://raw.githubusercontent.com/securego/gosec/master/install.sh | sh -s 1.2.0; \
|
||||
fi
|
||||
for S in $(PACKAGES); do ./bin/gosec $$S || exit 1; done;
|
||||
|
||||
.PHONY: goconst-check
|
||||
goconst-check:
|
||||
@hash goconst > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
go get -u github.com/jgautheron/goconst/cmd/goconst; \
|
||||
go install $(GOFLAGS) github.com/jgautheron/goconst/cmd/goconst; \
|
||||
fi
|
||||
for S in $(PACKAGES); do goconst $$S || exit 1; done;
|
||||
37
README.md
37
README.md
@@ -1,8 +1,8 @@
|
||||
<img src="https://vikunja.io/images/vikunja-logo.svg" alt="" style="display: block;width: 50%;margin: 0 auto;" width="50%"/>
|
||||
|
||||
[](https://drone1.kolaente.de/vikunja/api)
|
||||
[](LICENSE)
|
||||
[](https://storage.kolaente.de/minio/vikunja/)
|
||||
[](https://drone.kolaente.de/vikunja/api)
|
||||
[](LICENSE)
|
||||
[](https://dl.vikunja.io)
|
||||
[](https://hub.docker.com/r/vikunja/api/)
|
||||
[](https://try.vikunja.io/api/v1/docs)
|
||||
[](https://goreportcard.com/report/git.kolaente.de/vikunja/api)
|
||||
@@ -35,38 +35,14 @@ try it on [try.vikunja.io](https://try.vikunja.io)!
|
||||
* [Installing](https://vikunja.io/docs/installing/)
|
||||
* [Build from source](https://vikunja.io/docs/build-from-sources/)
|
||||
* [Development setup](https://vikunja.io/docs/development/)
|
||||
* [Makefile](https://vikunja.io/docs/makefile/)
|
||||
* [Magefile](https://vikunja.io/docs/mage/)
|
||||
* [Testing](https://vikunja.io/docs/testing/)
|
||||
|
||||
All docs can be found on [the vikunja home page](https://vikunja.io/docs/).
|
||||
|
||||
### Roadmap
|
||||
|
||||
> I know, it's still a long way to go. I'm currently working on a lot of "basic" features, the exiting things will come later. Don't worry, they'll come.
|
||||
|
||||
* [x] Prioritize tasks
|
||||
* [x] Subtasks
|
||||
* [x] Repeating tasks
|
||||
* [x] Get tasks via caldav
|
||||
* [x] Get all your tasks for an interval (day/month/period)
|
||||
* [x] Labels for tasks
|
||||
* [x] Assign users to tasks
|
||||
* [x] Attachments on tasks
|
||||
* [x] More sharing features
|
||||
* [x] Share with individual users
|
||||
* [x] Share via a world-readable link with or without password, like Nextcloud
|
||||
* [x] Disable registration, making an instance "invite-only"
|
||||
* [ ] SSE to notify multiple clients of updates when something was changed
|
||||
* [ ] "Smart Lists" - Create lists based on filters
|
||||
* [ ] IMAP-Integration - Send an email to Vikunja to create a new task
|
||||
* [ ] Webhooks - Trigger other events when an action is done (like completing a task)
|
||||
* [ ] Performace statistics - Get an overview and beautiful charts about what you got done this month
|
||||
* [ ] Activity feeds - Get a quick overview about who did what
|
||||
* [ ] Bulk-edit multiple tasks at once
|
||||
* [ ] Team-efforts - Requiring a task to be marked as done by multiple members until it's done
|
||||
* [ ] Global limits for namespaces/lists/tasks
|
||||
|
||||
See [our roadmap](https://my.vikunja.cloud/share/QFyzYEmEYfSyQfTOmIRSwLUpkFjboaBqQCnaPmWd/auth) (hosted on Vikunja!) for even more!
|
||||
See [the roadmap](https://my.vikunja.cloud/share/QFyzYEmEYfSyQfTOmIRSwLUpkFjboaBqQCnaPmWd/auth) (hosted on Vikunja!) for more!
|
||||
|
||||
* [ ] [Mobile apps](https://code.vikunja.io/app) (seperate repo) *In Progress*
|
||||
* [ ] [Webapp](https://code.vikunja.io/frontend) (seperate repo) *In Progress*
|
||||
@@ -77,4 +53,5 @@ Fork -> Push -> Pull-Request. Also see the [dev docs](https://vikunja.io/docs/de
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the GPLv3 License. See the [LICENSE](LICENSE) file for the full license text.
|
||||
This project is licensed under the AGPLv3 License. See the [LICENSE](LICENSE) file for the full license text.
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#!/bin/bash
|
||||
ln -s /opt/vikunja/vikunja /usr/bin/vikunja
|
||||
|
||||
# Fix the config to contain proper values
|
||||
NEW_SECRET=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
Origin: dl.vikunja.io
|
||||
Label: Vikunja
|
||||
Codename: strech
|
||||
Codename: buster
|
||||
Architectures: amd64
|
||||
Components: main
|
||||
Description: The debian repo for Vikunja builds.
|
||||
SignWith: yes
|
||||
Pull: strech
|
||||
Pull: buster
|
||||
|
||||
15
code-header-template.txt
Normal file
15
code-header-template.txt
Normal file
@@ -0,0 +1,15 @@
|
||||
Vikunja is a to-do list application to facilitate your life.
|
||||
Copyright 2018-2020 Vikunja and contributors. All rights reserved.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU 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/>.
|
||||
@@ -30,6 +30,18 @@ service:
|
||||
timezone: GMT
|
||||
# Whether task comments should be enabled or not
|
||||
enabletaskcomments: true
|
||||
# Whether totp is enabled. In most cases you want to leave that enabled.
|
||||
enabletotp: true
|
||||
# If not empty, enables logging of crashes and unhandled errors in sentry.
|
||||
sentrydsn: ''
|
||||
# If not empty, this will enable `/test/{table}` endpoints which allow to put any content in the database.
|
||||
# Used to reset the db before frontend tests. Because this is quite a dangerous feature allowing for lots of harm,
|
||||
# each request made to this endpoint neefs to provide an `Authorization: <token>` header with the token from below. <br/>
|
||||
# **You should never use this unless you know exactly what you're doing**
|
||||
testingtoken: ''
|
||||
# If enabled, vikunja will send an email to everyone who is either assigned to a task or created it when a task reminder
|
||||
# is due.
|
||||
enableemailreminders: true
|
||||
|
||||
database:
|
||||
# Database type to use. Supported types are mysql, postgres and sqlite.
|
||||
@@ -43,7 +55,7 @@ database:
|
||||
# Databse to use
|
||||
database: "vikunja"
|
||||
# When using sqlite, this is the path where to store the data
|
||||
Path: "./vikunja.db"
|
||||
path: "./vikunja.db"
|
||||
# Sets the max open connections to the database. Only used when using mysql and postgres.
|
||||
maxopenconnections: 100
|
||||
# Sets the maximum number of idle connections to the db.
|
||||
@@ -57,8 +69,10 @@ database:
|
||||
cache:
|
||||
# If cache is enabled or not
|
||||
enabled: false
|
||||
# Cache type. Possible values are memory or redis, you'll need to enable redis below when using redis
|
||||
type: memory
|
||||
# Cache type. Possible values are "keyvalue", "memory" or "redis".
|
||||
# When choosing "keyvalue" this setting follows the one configured in the "keyvalue" section.
|
||||
# When choosing "redis" you will need to configure the redis connection seperately.
|
||||
type: keyvalue
|
||||
# When using memory this defines the maximum size an element can take
|
||||
maxelementsize: 1000
|
||||
|
||||
@@ -74,10 +88,12 @@ redis:
|
||||
|
||||
cors:
|
||||
# Whether to enable or disable cors headers.
|
||||
# Note: If you want to put the frontend and the api on seperate domains or ports, you will need to enable this.
|
||||
# Otherwise the frontend won't be able to make requests to the api through the browser.
|
||||
enable: true
|
||||
# A list of origins which may access the api.
|
||||
# A list of origins which may access the api. These need to include the protocol (`http://` or `https://`) and port, if any.
|
||||
origins:
|
||||
- *
|
||||
- "*"
|
||||
# How long (in seconds) the results of a preflight request can be cached.
|
||||
maxage: 0
|
||||
|
||||
@@ -100,18 +116,22 @@ mailer:
|
||||
queuelength: 100
|
||||
# The timeout in seconds after which the current open connection to the mailserver will be closed.
|
||||
queuetimeout: 30
|
||||
# By default, vikunja will try to connect with starttls, use this option to force it to use ssl.
|
||||
forcessl: false
|
||||
|
||||
log:
|
||||
# A folder where all the logfiles should go.
|
||||
path: <rootpath>logs
|
||||
# Whether to show any logging at all or none
|
||||
enabled: true
|
||||
# Where the error log should go. Possible values are stdout, stderr, file or off to disable error logging.
|
||||
errors: "stdout"
|
||||
# Where the normal log should go. Possible values are stdout, stderr, file or off to disable standard logging.
|
||||
standard: "stdout"
|
||||
# Change the log level. Possible values (case-insensitive) are CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG.
|
||||
level: "INFO"
|
||||
# Whether or not to log database queries. Useful for debugging. Possible values are stdout, stderr, file or off to disable database logging.
|
||||
database: "off"
|
||||
# The log level for database log messages. Possible values (case-insensitive) are CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG.
|
||||
databaselevel: "WARNING"
|
||||
# Whether to log http requests or not. Possible values are stdout, stderr, file or off to disable http logging.
|
||||
http: "stdout"
|
||||
# Echo has its own logging which usually is unnessecary, which is why it is disabled by default. Possible values are stdout, stderr, file or off to disable standard logging.
|
||||
@@ -126,8 +146,10 @@ ratelimit:
|
||||
period: 60
|
||||
# The max number of requests a user is allowed to do in the configured time period
|
||||
limit: 100
|
||||
# The store where the limit counter for each user is stored. Possible values are "memory" or "redis"
|
||||
store: memory
|
||||
# The store where the limit counter for each user is stored.
|
||||
# Possible values are "keyvalue", "memory" or "redis".
|
||||
# When choosing "keyvalue" this setting follows the one configured in the "keyvalue" section.
|
||||
store: keyvalue
|
||||
|
||||
files:
|
||||
# The path where files are stored
|
||||
@@ -152,3 +174,108 @@ migration:
|
||||
# with the code obtained from the wunderlist api.
|
||||
# Note that the vikunja frontend expects this to be /migrate/wunderlist
|
||||
redirecturl:
|
||||
todoist:
|
||||
# Wheter to enable the todoist migrator or not
|
||||
enable: false
|
||||
# The client id, required for making requests to the todoist api
|
||||
# You need to register your vikunja instance at https://developer.todoist.com/appconsole.html to get this
|
||||
clientid:
|
||||
# The client secret, also required for making requests to the todoist api
|
||||
clientsecret:
|
||||
# The url where clients are redirected after they authorized Vikunja to access their todoist items.
|
||||
# This needs to match the url you entered when registering your Vikunja instance at todoist.
|
||||
# This is usually the frontend url where the frontend then makes a request to /migration/todoist/migrate
|
||||
# with the code obtained from the todoist api.
|
||||
# Note that the vikunja frontend expects this to be /migrate/todoist
|
||||
redirecturl:
|
||||
trello:
|
||||
# Wheter to enable the trello migrator or not
|
||||
enable: false
|
||||
# The client id, required for making requests to the trello api
|
||||
# You need to register your vikunja instance at https://trello.com/app-key (log in before you visit that link) to get this
|
||||
key:
|
||||
# The url where clients are redirected after they authorized Vikunja to access their trello cards.
|
||||
# This needs to match the url you entered when registering your Vikunja instance at trello.
|
||||
# This is usually the frontend url where the frontend then makes a request to /migration/trello/migrate
|
||||
# with the code obtained from the trello api.
|
||||
# Note that the vikunja frontend expects this to end on /migrate/trello.
|
||||
redirecturl: <frontend url>/migrate/trello
|
||||
microsofttodo:
|
||||
# Wheter to enable the microsoft todo migrator or not
|
||||
enable: false
|
||||
# The client id, required for making requests to the microsoft graph api
|
||||
# See https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app#register-an-application
|
||||
# for information about how to register your vikuinja instance.
|
||||
clientid:
|
||||
# The client secret, also required for making requests to the microsoft graph api
|
||||
clientsecret:
|
||||
# The url where clients are redirected after they authorized Vikunja to access their microsoft todo tasks.
|
||||
# This needs to match the url you entered when registering your Vikunja instance at microsoft.
|
||||
# This is usually the frontend url where the frontend then makes a request to /migration/microsoft-todo/migrate
|
||||
# with the code obtained from the microsoft graph api.
|
||||
# Note that the vikunja frontend expects this to be /migrate/microsoft-todo
|
||||
redirecturl: <frontend url>/migrate/microsoft-todo
|
||||
|
||||
avatar:
|
||||
# When using gravatar, this is the duration in seconds until a cached gravatar user avatar expires
|
||||
gravatarexpiration: 3600
|
||||
|
||||
backgrounds:
|
||||
# Whether to enable backgrounds for lists at all.
|
||||
enabled: true
|
||||
providers:
|
||||
upload:
|
||||
# Whethere to enable uploaded list backgrounds
|
||||
enabled: true
|
||||
unsplash:
|
||||
# Whether to enable setting backgrounds from unsplash as list backgrounds
|
||||
enabled: false
|
||||
# You need to create an application for your installation at https://unsplash.com/oauth/applications/new
|
||||
# and set the access token below.
|
||||
accesstoken:
|
||||
# The unsplash application id is only used for pingback and required as per their api guidelines.
|
||||
# You can find the Application ID in the dashboard for your API application. It should be a numeric ID.
|
||||
# It will only show in the UI if your application has been approved for Enterprise usage, therefore if
|
||||
# you’re in Demo mode, you can also find the ID in the URL at the end: https://unsplash.com/oauth/applications/:application_id
|
||||
applicationid:
|
||||
|
||||
# Legal urls
|
||||
# Will be shown in the frontend if configured here
|
||||
legal:
|
||||
imprinturl:
|
||||
privacyurl:
|
||||
|
||||
# Key Value Storage settings
|
||||
# The Key Value Storage is used for different kinds of things like metrics and a few cache systems.
|
||||
keyvalue:
|
||||
# The type of the storage backend. Can be either "memory" or "redis". If "redis" is chosen it needs to be configured seperately.
|
||||
type: "memory"
|
||||
|
||||
auth:
|
||||
# Local authentication will let users log in and register (if enabled) through the db.
|
||||
# This is the default auth mechanism and does not require any additional configuration.
|
||||
local:
|
||||
# Enable or disable local authentication
|
||||
enabled: true
|
||||
# OpenID configuration will allow users to authenticate through a third-party OpenID Connect compatible provider.<br/>
|
||||
# The provider needs to support the `openid`, `profile` and `email` scopes.<br/>
|
||||
# **Note:** The frontend expects to be redirected after authentication by the third party
|
||||
# to <frontend-url>/auth/openid/<auth key>. Please make sure to configure the redirect url with your third party
|
||||
# auth service accordingy if you're using the default vikunja frontend.
|
||||
# Take a look at the [default config file](https://kolaente.dev/vikunja/api/src/branch/master/config.yml.sample) for more information about how to configure openid authentication.
|
||||
openid:
|
||||
# Enable or disable OpenID Connect authentication
|
||||
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.
|
||||
redirecturl: <frontend url>
|
||||
# A list of enabled providers
|
||||
providers:
|
||||
# The name of the provider as it will appear in the frontend.
|
||||
- name:
|
||||
# The auth url to send users to if they want to authenticate using OpenID Connect.
|
||||
authurl:
|
||||
# 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.
|
||||
clientsecret:
|
||||
|
||||
18
docker-manifest.tmpl
Normal file
18
docker-manifest.tmpl
Normal file
@@ -0,0 +1,18 @@
|
||||
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-arm
|
||||
platform:
|
||||
architecture: arm
|
||||
os: linux
|
||||
@@ -16,9 +16,14 @@ params:
|
||||
description: The to-do app to organize your life
|
||||
author: The Vikunja Authors
|
||||
website: https://vikunja.io
|
||||
fanthomEnabled: false
|
||||
fathomUrl: fathom.kolaente.de
|
||||
fathomSiteID: RYKSD
|
||||
plausibleEnabled: true
|
||||
plausibleDomain: vikunja.io
|
||||
plausibleURL: https://analytics.kolaente.de
|
||||
|
||||
markup:
|
||||
goldmark:
|
||||
renderer:
|
||||
unsafe: true
|
||||
|
||||
menu:
|
||||
page:
|
||||
@@ -37,3 +42,6 @@ menu:
|
||||
- name: Code
|
||||
url: https://code.vikunja.io/
|
||||
weight: 50
|
||||
- name: Community
|
||||
url: https://community.vikunja.io/
|
||||
weight: 60
|
||||
|
||||
@@ -17,9 +17,9 @@ To learn more about the what, why and how, take a look at [the features page](ht
|
||||
|
||||
## Start
|
||||
|
||||
A good starting point if you want to install and host Vikunja on your server are [the install documentation](installing)
|
||||
and [available configuration options](config-options).
|
||||
A good starting point if you want to install and host Vikunja on your server are [the install documentation]({{< ref "./setup/install.md">}})
|
||||
and [available configuration options]({{< ref "./setup/config.md">}}).
|
||||
|
||||
## Developing
|
||||
|
||||
If you want to start contributing to Vikunja, take a look at [the development docs](development).
|
||||
If you want to start contributing to Vikunja, take a look at [the development docs]({{< ref "./development/development.md">}}).
|
||||
@@ -16,6 +16,8 @@ Additionally, they can also be run directly by using the `migrate` command.
|
||||
We use [xormigrate](https://github.com/techknowlogick/xormigrate) to handle migrations,
|
||||
which is based on gormigrate.
|
||||
|
||||
{{< table_of_contents >}}
|
||||
|
||||
## Add a new migration
|
||||
|
||||
All migrations are stored in `pkg/migrations` and files should have the same name as their id.
|
||||
|
||||
@@ -17,7 +17,9 @@ If you don't intend to add new dependencies, go `1.9` and above should be fine.
|
||||
|
||||
To contribute to Vikunja, fork the project and work on the master branch.
|
||||
|
||||
A lot of developing tasks are automated using a Makefile, so make sure to [take a look at it]({{< ref "make.md">}}).
|
||||
A lot of developing tasks are automated using a Magefile, so make sure to [take a look at it]({{< ref "mage.md">}}).
|
||||
|
||||
{{< table_of_contents >}}
|
||||
|
||||
## Libraries
|
||||
|
||||
@@ -32,9 +34,6 @@ See [testing]({{< ref "test.md">}}).
|
||||
|
||||
If you're able to use go modules, you can clone the project wherever you want to and work from there.
|
||||
|
||||
However, when building or running tests, please supply the `-mod=vendor` flag to go so it builds using the
|
||||
dependencies from the `vendor/` folder.
|
||||
|
||||
#### Development-setup without go modules
|
||||
|
||||
Some internal packages are referenced using their respective package URL. This can become problematic.
|
||||
@@ -53,8 +52,8 @@ git remote add origin git@git.kolaente.de:<USERNAME>/api.git
|
||||
git fetch --all --prune
|
||||
{{< /highlight >}}
|
||||
|
||||
This should provide a working development environment for Vikunja. Take a look at the Makefile to get an overview about
|
||||
the available tasks. The most common tasks should be `make test` which will start our test environment and `make build`
|
||||
This should provide a working development environment for Vikunja. Take a look at the Magefile to get an overview about
|
||||
the available tasks. The most common tasks should be `mage test:unit` which will start our test environment and `mage build:build`
|
||||
which will build a vikunja binary into the working directory. Writing test cases is not mandatory to contribute, but it
|
||||
is highly encouraged and helps developers sleep at night.
|
||||
|
||||
@@ -65,4 +64,4 @@ That’s it! You are ready to hack on Vikunja. Test changes, push them to the re
|
||||
Each Vikunja release contains all static assets directly compiled into the binary.
|
||||
To prevent this during development, use the `dev` tag when developing.
|
||||
|
||||
See the [make docs](make.md#statically-compile-all-templates-into-the-binary) about how to compile with static assets for a release.
|
||||
See the [mage docs](mage.md#statically-compile-all-templates-into-the-binary) about how to compile with static assets for a release.
|
||||
|
||||
192
docs/content/doc/development/mage.md
Normal file
192
docs/content/doc/development/mage.md
Normal file
@@ -0,0 +1,192 @@
|
||||
---
|
||||
date: "2019-02-12:00:00+02:00"
|
||||
title: "Magefile"
|
||||
draft: false
|
||||
type: "doc"
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "development"
|
||||
---
|
||||
|
||||
# Mage
|
||||
|
||||
Vikunja uses [Mage](https://magefile.org/) to script common development tasks and even releasing.
|
||||
Mage is a pure go solution which allows for greater flexibility and things like better paralelization.
|
||||
|
||||
This document explains what taks are available and what they do.
|
||||
|
||||
{{< table_of_contents >}}
|
||||
|
||||
## Installation
|
||||
|
||||
To use mage, you'll need to install the mage cli.
|
||||
To install it, run the following command:
|
||||
|
||||
```
|
||||
go install github.com/magefile/mage
|
||||
```
|
||||
|
||||
## Categories
|
||||
|
||||
There are multiple categories of subcommands in the magefile:
|
||||
|
||||
* `build`: Contains commands to build a single binary
|
||||
* `check`: Contains commands to statically check the source code
|
||||
* `release`: Contains commands to release Vikunja with everything that's required
|
||||
* `test`: Contains commands to run all kinds of tests
|
||||
* `dev`: Contains commands to run development tasks
|
||||
* `misc`: Commands which do not belong in either of the other categories
|
||||
|
||||
## CI
|
||||
|
||||
These tasks are automatically run in our CI every time someone pushes to master or you update a pull request:
|
||||
|
||||
* `mage check:lint`
|
||||
* `mage check:fmt`
|
||||
* `mage check:ineffassign`
|
||||
* `mage check:misspell`
|
||||
* `mage check:goconst`
|
||||
* `mage build:generate`
|
||||
* `mage build:build`
|
||||
|
||||
## Build
|
||||
|
||||
### Build Vikunja
|
||||
|
||||
{{< highlight bash >}}
|
||||
mage build:build
|
||||
{{< /highlight >}}
|
||||
|
||||
Builds a `vikunja`-binary in the root directory of the repo for the platform it is run on.
|
||||
|
||||
### Statically compile all templates into the binary
|
||||
|
||||
{{< highlight bash >}}
|
||||
mage build:generate
|
||||
{{< /highlight >}}
|
||||
|
||||
This generates static code with all templates, meaning no template need to be referenced at runtime.
|
||||
|
||||
### clean
|
||||
|
||||
{{< highlight bash >}}
|
||||
mage build:clean
|
||||
{{< /highlight >}}
|
||||
|
||||
Cleans all build, executable and bindata files
|
||||
|
||||
## Check
|
||||
|
||||
All check sub-commands exit with a status code of 1 if the check fails.
|
||||
|
||||
Various code-checks are available:
|
||||
|
||||
* `mage check:all`: Runs fmt-check, lint, got-swag, misspell-check, ineffasign-check, gocyclo-check, static-check, gosec-check, goconst-check all in parallel
|
||||
* `mage check:fmt`: Checks if the code is properly formatted with go fmt
|
||||
* `mage check:go-sec`: Checks the source code for potential security issues by scanning the Go AST using the [gosec tool](https://github.com/securego/gosec)
|
||||
* `mage check:goconst`: Checks for repeated strings that could be replaced by a constant using [goconst](https://github.com/jgautheron/goconst/)
|
||||
* `mage check:gocyclo`: Checks for the cyclomatic complexity of the source code using [gocyclo](https://github.com/fzipp/gocyclo)
|
||||
* `mage check:got-swag`: Checks if the swagger docs need to be re-generated from the code annotations
|
||||
* `mage check:ineffassign`: Checks the source code for ineffectual assigns using [ineffassign](https://github.com/gordonklaus/ineffassign)
|
||||
* `mage check:lint`: Runs golint on all packages
|
||||
* `mage check:misspell`: Checks the source code for misspellings
|
||||
* `mage check:static`: Statically analyzes the source code about a range of different problems using [staticcheck](https://staticcheck.io/docs/)
|
||||
|
||||
## Release
|
||||
|
||||
### Build Releases
|
||||
|
||||
{{< highlight bash >}}
|
||||
mage release
|
||||
{{< /highlight >}}
|
||||
|
||||
Builds binaries for all platforms and zips them with a copy of the `templates/` folder.
|
||||
All built zip files are stored into `dist/zips/`. Binaries are stored in `dist/binaries/`,
|
||||
binaries bundled with `templates` are stored in `dist/releases/`.
|
||||
|
||||
All cross-platform binaries built using this series of commands are built with the help of
|
||||
[xgo](https://github.com/techknowlogick/xgo). The mage command will automatically install the
|
||||
binary to be able to use it.
|
||||
|
||||
`mage release:release` is a shortcut to execute `mage release:dirs release:windows release:linux release:darwin release:copy release:check release:os-package release:zip`.
|
||||
|
||||
* `mage release:dirs` creates all directories needed
|
||||
* `mage release:windows`/`release:linux`/`release:darwin` execute xgo to build for their respective platforms
|
||||
* `mage release:copy` bundles binaries with a copy of the `LICENSE` and sample config files to then be zipped
|
||||
* `mage release:check` creates sha256 checksums for each binary which will be included in the zip file
|
||||
* `mage release:os-package` bundles a binary with the `sha256` checksum file, a sample `config.yml` and a copy of the license in a folder for each architecture
|
||||
* `mage release:compress` compresses all build binaries with `upx` to save space
|
||||
* `mage release:zip` paclages a zip file for the files created by `release:os-package`
|
||||
|
||||
### Build os packages
|
||||
|
||||
{{< highlight bash >}}
|
||||
mage release:packages
|
||||
{{< /highlight >}}
|
||||
|
||||
Will build `.deb`, `.rpm` and `.apk` packages to `dist/os-packages`.
|
||||
|
||||
### Make a debian repo
|
||||
|
||||
{{< highlight bash >}}
|
||||
mage release:reprepro
|
||||
{{< /highlight >}}
|
||||
|
||||
Takes an already built debian package and creates a debian repo structure around it.
|
||||
|
||||
Used to be run inside a [docker container](https://git.kolaente.de/konrad/reprepro-docker) in the CI process when releasing.
|
||||
|
||||
## Test
|
||||
|
||||
### unit
|
||||
|
||||
{{< highlight bash >}}
|
||||
mage test:unit
|
||||
{{< /highlight >}}
|
||||
|
||||
Runs all tests except integration tests.
|
||||
|
||||
### coverage
|
||||
|
||||
{{< highlight bash >}}
|
||||
mage test:coverage
|
||||
{{< /highlight >}}
|
||||
|
||||
Runs all tests except integration tests and generates a `coverage.html` file to inspect the code coverage.
|
||||
|
||||
### integration
|
||||
|
||||
{{< highlight bash >}}
|
||||
mage test:integration
|
||||
{{< /highlight >}}
|
||||
|
||||
Runs all integration tests.
|
||||
|
||||
## Dev
|
||||
|
||||
### Create a new migration
|
||||
|
||||
{{< highlight bash >}}
|
||||
mage dev:create-migration
|
||||
{{< /highlight >}}
|
||||
|
||||
Creates a new migration with the current date.
|
||||
Will ask for the name of the struct you want to create a migration for.
|
||||
|
||||
## Misc
|
||||
|
||||
### Format the code
|
||||
|
||||
{{< highlight bash >}}
|
||||
mage fmt
|
||||
{{< /highlight >}}
|
||||
|
||||
Formats all source code using `go fmt`.
|
||||
|
||||
### Generate swagger definitions from code comments
|
||||
|
||||
{{< highlight bash >}}
|
||||
mage do-the-swag
|
||||
{{< /highlight >}}
|
||||
|
||||
Generates swagger definitions from the comment annotations in the code.
|
||||
@@ -1,151 +0,0 @@
|
||||
---
|
||||
date: "2019-02-12:00:00+02:00"
|
||||
title: "Makefile"
|
||||
draft: false
|
||||
type: "doc"
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "development"
|
||||
---
|
||||
|
||||
# Makefile
|
||||
|
||||
We scripted a lot of tasks used mostly for developing into the makefile. This documents explains what
|
||||
taks are available and what they do.
|
||||
|
||||
## CI
|
||||
|
||||
These tasks are automatically run in our CI every time someone pushes to master or you update a pull request:
|
||||
|
||||
* `make lint`
|
||||
* `make fmt-check`
|
||||
* `make ineffassign-check`
|
||||
* `make misspell-check`
|
||||
* `make goconst-check`
|
||||
* `make generate`
|
||||
* `make build`
|
||||
|
||||
### clean
|
||||
|
||||
{{< highlight bash >}}
|
||||
make clean
|
||||
{{< /highlight >}}
|
||||
|
||||
Clears all builds and binaries.
|
||||
|
||||
### test
|
||||
|
||||
{{< highlight bash >}}
|
||||
make test
|
||||
{{< /highlight >}}
|
||||
|
||||
Runs all tests in Vikunja.
|
||||
|
||||
### Format the code
|
||||
|
||||
{{< highlight bash >}}
|
||||
make fmt
|
||||
{{< /highlight >}}
|
||||
|
||||
Formats all source code using `go fmt`.
|
||||
|
||||
#### Check formatting
|
||||
|
||||
{{< highlight bash >}}
|
||||
make fmt-check
|
||||
{{< /highlight >}}
|
||||
|
||||
Checks if the code needs to be formatted. Fails if it does.
|
||||
|
||||
### Build Vikunja
|
||||
|
||||
{{< highlight bash >}}
|
||||
make build
|
||||
{{< /highlight >}}
|
||||
|
||||
Builds a `vikunja`-binary in the root directory of the repo for the platform it is run on.
|
||||
|
||||
### Statically compile all templates into the binary
|
||||
|
||||
{{< highlight bash >}}
|
||||
make generate
|
||||
{{< /highlight >}}
|
||||
|
||||
This generates static code with all templates, meaning no template need to be referenced at runtime.
|
||||
|
||||
### Compress the built binary
|
||||
|
||||
{{< highlight bash >}}
|
||||
make compress-build
|
||||
{{< /highlight >}}
|
||||
|
||||
Go binaries are very big.
|
||||
To make the vikunja binary smaller, we can compress it using [upx](https://upx.github.io/).
|
||||
|
||||
### Build Releases
|
||||
|
||||
{{< highlight bash >}}
|
||||
make release
|
||||
{{< /highlight >}}
|
||||
|
||||
Builds binaries for all platforms and zips them with a copy of the `templates/` folder.
|
||||
All built zip files are stored into `dist/zips/`. Binaries are stored in `dist/binaries/`,
|
||||
binaries bundled with `templates` are stored in `dist/releases/`.
|
||||
|
||||
All cross-platform binaries built using this series of commands are built with the help of
|
||||
[xgo](https://github.com/techknowlogick/xgo). The make command will automatically install the
|
||||
binary to be able to use it.
|
||||
|
||||
`make release` is actually just a shortcut to execute `make release-dirs release-windows release-linux release-darwin release-copy release-check release-os-package release-zip`.
|
||||
|
||||
* `release-dirs` creates all directories needed
|
||||
* `release-windows`/`release-linux`/`release-darwin` execute xgo to build for their respective platforms
|
||||
* `release-copy` bundles binaries with a copy of `templates/` to then be zipped
|
||||
* `release-check` creates sha256 checksums for each binary which will be included in the zip file
|
||||
* `release-os-package` bundles a binary with a copy of the `templates/` folder, the `sha256` checksum file, a sample `config.yml` and a copy of the license in a folder for each architecture
|
||||
* `release-compress` compresses all build binaries, see `compress-build`
|
||||
* `release-zip` makes a zip file for the files created by `release-os-package`
|
||||
|
||||
### Build debian packages
|
||||
|
||||
{{< highlight bash >}}
|
||||
make build-deb
|
||||
{{< /highlight >}}
|
||||
|
||||
Will build a `.deb` package into the current folder. You need to have [fpm](https://fpm.readthedocs.io/en/latest/intro.html) installed to be able to do this.
|
||||
|
||||
#### Make a debian repo
|
||||
|
||||
{{< highlight bash >}}
|
||||
make reprepro
|
||||
{{< /highlight >}}
|
||||
|
||||
Takes an already built debian package and creates a debian repo structure around it.
|
||||
|
||||
Used to be run inside a [docker container](https://git.kolaente.de/konrad/reprepro-docker) in the CI process when releasing.
|
||||
|
||||
### Generate swagger definitions from code comments
|
||||
|
||||
{{< highlight bash >}}
|
||||
make do-the-swag
|
||||
{{< /highlight >}}
|
||||
|
||||
Generates swagger definitions from the comments in the code.
|
||||
|
||||
#### Check if swagger generation is needed
|
||||
|
||||
{{< highlight bash >}}
|
||||
make got-swag
|
||||
{{< /highlight >}}
|
||||
|
||||
This command is currently more an experiment, use it with caution.
|
||||
It may bring up wrong results.
|
||||
|
||||
### Code-Checks
|
||||
|
||||
* `misspell-check`: Checks for commonly misspelled words
|
||||
* `ineffassign-check`: Checks for ineffectual assignments in the code using [ineffassign](https://github.com/gordonklaus/ineffassign).
|
||||
* `gocyclo-check`: Calculates cyclomatic complexities of functions using [gocyclo](https://github.com/fzipp/gocyclo).
|
||||
* `static-check`: Analyzes the code for bugs, improvements and more using [staticcheck](https://staticcheck.io/docs/).
|
||||
* `gosec-check`: Inspects source code for security problems by scanning the Go AST using the [gosec tool](https://github.com/securego/gosec).
|
||||
* `goconst-check`: Finds repeated strings that could be replaced by a constant using [goconst](https://github.com/jgautheron/goconst/).
|
||||
@@ -16,12 +16,14 @@ To make this easier, we have put together a few helpers which are documented on
|
||||
In general, each migrator implements a migrator interface which is then called from a client.
|
||||
The interface makes it possible to use helper methods which handle http an focus only on the implementation of the migrator itself.
|
||||
|
||||
### Structure
|
||||
{{< table_of_contents >}}
|
||||
|
||||
## Structure
|
||||
|
||||
All migrator implementations live in their own package in `pkg/modules/migration/<name-of-the-service>`.
|
||||
When creating a new migrator, you should place all related code inside that module.
|
||||
|
||||
### Migrator interface
|
||||
## Migrator interface
|
||||
|
||||
The migrator interface is defined as follows:
|
||||
|
||||
@@ -41,7 +43,7 @@ type Migrator interface {
|
||||
}
|
||||
```
|
||||
|
||||
### Defining http routes
|
||||
## Defining http routes
|
||||
|
||||
Once your migrator implements the migration interface, it becomes possible to use the helper http handlers.
|
||||
Their usage is very similar to the [general web handler](https://kolaente.dev/vikunja/web#user-content-defining-routes-using-the-standard-web-handler):
|
||||
@@ -63,7 +65,7 @@ if config.MigrationWunderlistEnable.GetBool() {
|
||||
|
||||
You should also document the routes with [swagger annotations]({{< ref "../practical-instructions/swagger-docs.md" >}}).
|
||||
|
||||
### Insertion helper method
|
||||
## 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.
|
||||
@@ -81,7 +83,7 @@ if err != nil {
|
||||
err = migration.InsertFromStructure(fullVikunjaHierachie, user)
|
||||
```
|
||||
|
||||
### Configuration
|
||||
## Configuration
|
||||
|
||||
You should add at least an option to enable or disable the migration.
|
||||
Chances are, you'll need some more options for things like client ID and secret
|
||||
@@ -90,7 +92,7 @@ Chances are, you'll need some more options for things like client ID and secret
|
||||
The easiest way to implement an on/off switch is to check whether your migration service is enabled or not when
|
||||
registering the routes, and then simply don't registering the routes in the case it is disabled.
|
||||
|
||||
#### Making the migrator public in `/info`
|
||||
### Making the migrator public in `/info`
|
||||
|
||||
You should make your migrator available in the `/info` endpoint so that frontends can display options to enable them or not.
|
||||
To do this, add an entry to `pkg/routes/api/v1/info.go`.
|
||||
|
||||
@@ -45,9 +45,11 @@ In general, this api repo has the following structure:
|
||||
|
||||
This document will explain what these mean and what you can find where.
|
||||
|
||||
{{< table_of_contents >}}
|
||||
|
||||
## Root level
|
||||
|
||||
The root directory is where [the config file]({{< ref "../setup/config.md">}}), [Makefile]({{< ref "make.md">}}), license, drone config,
|
||||
The root directory is where [the config file]({{< ref "../setup/config.md">}}), [Magefile]({{< ref "mage.md">}}), license, drone config,
|
||||
application entry point (`main.go`) and so on are located.
|
||||
|
||||
## docker
|
||||
@@ -152,11 +154,11 @@ Every handler function which does not use the standard web handler should live h
|
||||
|
||||
### static
|
||||
|
||||
All static files generated by `make generate` live here.
|
||||
All static files generated by `mage generate` live here.
|
||||
|
||||
### swagger
|
||||
|
||||
This is where the [generated]({{< ref "make.md#generate-swagger-definitions-from-code-comments">}} [api docs]({{< ref "../usage/api.md">}}) live.
|
||||
This is where the [generated]({{< ref "mage.md#generate-swagger-definitions-from-code-comments">}} [api docs]({{< ref "../usage/api.md">}}) live.
|
||||
You usually don't need to touch this package.
|
||||
|
||||
### user
|
||||
@@ -175,7 +177,7 @@ See their function definitions for instructions on how to use them.
|
||||
### version
|
||||
|
||||
The single purpouse of this package is to hold the current vikunja version which gets overridden through build flags
|
||||
each time `make release` or `make build` is run.
|
||||
each time `mage release` or `mage build` is run.
|
||||
It is a seperate package to avoid import cycles with other packages.
|
||||
|
||||
## REST-Tests
|
||||
|
||||
@@ -10,40 +10,42 @@ menu:
|
||||
|
||||
# Testing
|
||||
|
||||
You can run unit tests with [our `Makefile`]({{< ref "make.md">}}) with
|
||||
You can run unit tests with [our `Magefile`]({{< ref "mage.md">}}) with
|
||||
|
||||
{{< highlight bash >}}
|
||||
make test
|
||||
mage test:unit
|
||||
{{< /highlight >}}
|
||||
|
||||
### Running tests with config
|
||||
{{< table_of_contents >}}
|
||||
|
||||
## Running tests with config
|
||||
|
||||
You can run tests with all available config variables if you want, enabeling you to run tests for a lot of scenarios.
|
||||
|
||||
To use the normal config set the enviroment variable `VIKUNJA_TESTS_USE_CONFIG=1`.
|
||||
|
||||
### Show sql queries
|
||||
## Show sql queries
|
||||
|
||||
When `UNIT_TESTS_VERBOSE=1` is set, all sql queries will be shown when tests are run.
|
||||
|
||||
### Fixtures
|
||||
## Fixtures
|
||||
|
||||
All tests are run against a set of db fixtures.
|
||||
These fixtures are defined in `pkg/models/fixtures` in YAML-Files which represent the database structure.
|
||||
|
||||
When you add a new test case which requires new database entries to test against, update these files.
|
||||
|
||||
# Integration tests
|
||||
## Integration tests
|
||||
|
||||
All integration tests live in `pkg/integrations`.
|
||||
You can run them by executing `make integration-test`.
|
||||
You can run them by executing `mage test:integration`.
|
||||
|
||||
The integration tests use the same config and fixtures as the unit tests and therefor have the same options available,
|
||||
see at the beginning of this document.
|
||||
|
||||
To run integration tests, use `make integration-test`.
|
||||
To run integration tests, use `mage test:integration`.
|
||||
|
||||
# Initializing db fixtures when writing tests
|
||||
## Initializing db fixtures when writing tests
|
||||
|
||||
All db fixtures for all tests live in the `pkg/db/fixtures/` folder as yaml files.
|
||||
Each file has the same name as the table the fixtures are for.
|
||||
|
||||
@@ -18,7 +18,9 @@ This is used whenever you make a call to the database to get or update data.
|
||||
|
||||
This xorm instance is set up and initialized every time vikunja is started.
|
||||
|
||||
### Adding new database tables
|
||||
{{< table_of_contents >}}
|
||||
|
||||
## Adding new database tables
|
||||
|
||||
To add a new table to the database, add a an instance of your struct to the `tables` variable in the
|
||||
init function in `pkg/models/models.go`. Xorm will sync them automatically.
|
||||
@@ -27,7 +29,7 @@ You also need to add a pointer to the `tablesWithPointer` slice to enable cachin
|
||||
|
||||
To learn more about how to configure your struct to create "good" tables, refer to [the xorm documentaion](http://xorm.io/docs/).
|
||||
|
||||
### Adding data to test fixtures
|
||||
## Adding data to test fixtures
|
||||
|
||||
Adding data for test fixtures is done in via `yaml` files insinde of `pkg/models/fixtures`.
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ menu:
|
||||
|
||||
This document explains how to use the mailer to send emails and what to do to create a new kind of email to be sent.
|
||||
|
||||
{{< table_of_contents >}}
|
||||
|
||||
## Sending emails
|
||||
|
||||
**Note:** You should use mail templates whenever possible (see below).
|
||||
@@ -30,7 +32,7 @@ type Opts struct {
|
||||
}
|
||||
{{< /highlight >}}
|
||||
|
||||
## Sending emails based on a template
|
||||
### Sending emails based on a template
|
||||
|
||||
For each mail with a template, there are two email templates: One for plaintext emails, one for html emails.
|
||||
|
||||
@@ -41,7 +43,7 @@ To send a mail based on a template, use the function `mail.SendMailWithTemplate(
|
||||
`to` and `subject` are pretty much self-explanatory, `tpl` is the name of the template, without `.html.tmpl` or `.plain.tmpl`.
|
||||
`data` is a map you can pass additional data to your template.
|
||||
|
||||
#### Sending a mail with a template
|
||||
### Sending a mail with a template
|
||||
|
||||
A basic html email template would look like this:
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@ Metrics work by exposing a `/metrics` endpoint which can then be accessed by pro
|
||||
To keep the load on the database minimal, metrics are stored and updated in redis.
|
||||
The `metrics` package provides several functions to create and update metrics.
|
||||
|
||||
{{< table_of_contents >}}
|
||||
|
||||
## New metrics
|
||||
|
||||
First, define a `const` with the metric key in redis. This is done in `pkg/metrics/metrics.go`.
|
||||
@@ -41,6 +43,6 @@ Because metrics are stored in redis, you are responsible to increase or decrease
|
||||
To do this, use `metrics.UpdateCount(value, key)` where `value` is the amount you want to cange it (you can pass
|
||||
negative values to decrease it) and `key` it the redis key used to define the metric.
|
||||
|
||||
# Using it
|
||||
## Using it
|
||||
|
||||
A Prometheus config with a Grafana template is available at [our git repo](https://git.kolaente.de/vikunja/monitoring).
|
||||
|
||||
@@ -12,7 +12,7 @@ menu:
|
||||
|
||||
The api documentation is generated using [swaggo](https://github.com/swaggo/swag) from comments.
|
||||
|
||||
### Documenting structs
|
||||
## Documenting structs
|
||||
|
||||
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.
|
||||
@@ -23,13 +23,13 @@ As an example, this is the definition of a list with all comments:
|
||||
// List represents a list of tasks
|
||||
type List struct {
|
||||
// The unique, numeric id of this list.
|
||||
ID int64 `xorm:"int(11) autoincr not null unique pk" json:"id" param:"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:"int(11) INDEX" json:"-"`
|
||||
NamespaceID int64 `xorm:"int(11) INDEX" json:"-" param:"namespace"`
|
||||
OwnerID int64 `xorm:"bigint INDEX" json:"-"`
|
||||
NamespaceID int64 `xorm:"bigint INDEX" json:"-" param:"namespace"`
|
||||
|
||||
// The user who created this list.
|
||||
Owner User `xorm:"-" json:"owner" valid:"-"`
|
||||
|
||||
@@ -13,6 +13,8 @@ menu:
|
||||
Vikunja does not store any data outside of the database.
|
||||
So, all you need to backup are the contents of that database and maybe the config file.
|
||||
|
||||
{{< table_of_contents >}}
|
||||
|
||||
## MySQL
|
||||
|
||||
To create a backup from mysql use the `mysqldump` command:
|
||||
|
||||
@@ -14,16 +14,16 @@ Vikunja being a go application, has no other dependencies than go itself.
|
||||
All libraries are bundeled inside the repo in the `vendor/` folder, so all it boils down to are these steps:
|
||||
|
||||
1. Make sure [Go](https://golang.org/doc/install) is properly installed on your system. You'll need at least Go `1.9`.
|
||||
2. Make sure [Make](https://www.gnu.org/software/make/) is properly installed on your system.
|
||||
2. Make sure [Mage](https://magefile) is properly installed on your system.
|
||||
3. Clone the repo with `git clone https://code.vikunja.io/api`
|
||||
3. Run `make build` in the source of this repo. This will build a binary in the root of the repo which will be able to run on your system.
|
||||
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.
|
||||
|
||||
*Note:* Static ressources such as email templates are built into the binary.
|
||||
For these to work, you may need to run `make generate` before building the vikunja binary.
|
||||
When builing entirely with `make`, you dont need to do this, `make generate` will be run automatically when running `make build`.
|
||||
For these to work, you may need to run `mage build:generate` before building the vikunja binary.
|
||||
When builing entirely with `mage`, you dont need to do this, `mage build:generate` will be run automatically when running `mage build:build`.
|
||||
|
||||
# Build for different architectures
|
||||
|
||||
To build for other platforms and architectures than the one you're currently on, simply run `make release` or `make release-{linux|windows|darwin}`.
|
||||
To build for other platforms and architectures than the one you're currently on, simply run `mage release:release` or `mage release:{linux|windows|darwin}`.
|
||||
|
||||
More options are available, please refer to the [makefile docs]({{< ref "../development/make.md">}}) for more details.
|
||||
More options are available, please refer to the [magefile docs]({{< ref "../development/mage.md">}}) for more details.
|
||||
@@ -27,7 +27,14 @@ first:
|
||||
child: true
|
||||
{{< /highlight >}}
|
||||
|
||||
## Config file locations
|
||||
# Formats
|
||||
|
||||
Vikunja supports using `toml`, `yaml`, `hcl`, `ini`, `json`, envfile, env variables and Java Properties files.
|
||||
We reccomend yaml or toml, but you're free to use whatever you want.
|
||||
|
||||
Vikunja provides a default [`config.yml`](https://kolaente.dev/vikunja/api/src/branch/master/config.yml.sample) file which you can use as a starting point.
|
||||
|
||||
# Config file locations
|
||||
|
||||
Vikunja will search on various places for a config file:
|
||||
|
||||
@@ -38,161 +45,569 @@ Vikunja will search on various places for a config file:
|
||||
|
||||
# Default configuration with explanations
|
||||
|
||||
This is the same as the `config.yml.sample` file you'll find in the root of vikunja.
|
||||
The following explains all possible config variables and their defaults.
|
||||
You can find a full example configuration file in [here](https://code.vikunja.io/api/src/branch/master/config.yml.sample).
|
||||
|
||||
{{< highlight yaml >}}
|
||||
service:
|
||||
# This token is used to verify issued JWT tokens.
|
||||
# Default is a random token which will be generated at each startup of vikunja.
|
||||
# (This means all already issued tokens will be invalid once you restart vikunja)
|
||||
JWTSecret: "cei6gaezoosah2bao3ieZohkae5aicah"
|
||||
# The interface on which to run the webserver
|
||||
interface: ":3456"
|
||||
# The URL of the frontend, used to send password reset emails.
|
||||
frontendurl: ""
|
||||
# The base path on the file system where the binary and assets are.
|
||||
# Vikunja will also look in this path for a config file, so you could provide only this variable to point to a folder
|
||||
# with a config file which will then be used.
|
||||
rootpath: <the path of the executable>
|
||||
# The max number of items which can be returned per page
|
||||
maxitemsperpage: 50
|
||||
# If set to true, enables a /metrics endpoint for prometheus to collect metrics about the system
|
||||
# You'll need to use redis for this in order to enable common metrics over multiple nodes
|
||||
enablemetrics: false
|
||||
# Enable the caldav endpoint, see the docs for more details
|
||||
enablecaldav: true
|
||||
# Set the motd message, available from the /info endpoint
|
||||
motd: ""
|
||||
# Enable sharing of lists via a link
|
||||
enablelinksharing: true
|
||||
# Whether to let new users registering themselves or not
|
||||
enableregistration: true
|
||||
# Whether to enable task attachments or not
|
||||
enabletaskattachments: true
|
||||
# The time zone all timestamps are in
|
||||
timezone: GMT
|
||||
# Whether task comments should be enabled or not
|
||||
enabletaskcomments: true
|
||||
If you don't provide a value in your config file, their default will be used.
|
||||
|
||||
database:
|
||||
# Database type to use. Supported types are mysql, postgres and sqlite.
|
||||
type: "sqlite"
|
||||
# Database user which is used to connect to the database.
|
||||
user: "vikunja"
|
||||
# Databse password
|
||||
password: ""
|
||||
# Databse host
|
||||
host: "localhost"
|
||||
# Databse to use
|
||||
database: "vikunja"
|
||||
# When using sqlite, this is the path where to store the data
|
||||
Path: "./vikunja.db"
|
||||
# Sets the max open connections to the database. Only used when using mysql and postgres.
|
||||
maxopenconnections: 100
|
||||
# Sets the maximum number of idle connections to the db.
|
||||
maxidleconnections: 50
|
||||
# The maximum lifetime of a single db connection in miliseconds.
|
||||
maxconnectionlifetime: 10000
|
||||
# Secure connection mode. Only used with postgres.
|
||||
# (see https://pkg.go.dev/github.com/lib/pq?tab=doc#hdr-Connection_String_Parameters)
|
||||
sslmode: disable
|
||||
## Nesting
|
||||
|
||||
cache:
|
||||
# If cache is enabled or not
|
||||
enabled: false
|
||||
# Cache type. Possible values are memory or redis, you'll need to enable redis below when using redis
|
||||
type: memory
|
||||
# When using memory this defines the maximum size an element can take
|
||||
maxelementsize: 1000
|
||||
Most config variables are nested under some "higher-level" key.
|
||||
For example, the `interface` config variable is a child of the `service` key.
|
||||
|
||||
redis:
|
||||
# Whether to enable redis or not
|
||||
enabled: false
|
||||
# The host of the redis server including its port.
|
||||
host: 'localhost:6379'
|
||||
# The password used to authenicate against the redis server
|
||||
password: ''
|
||||
# 0 means default database
|
||||
db: 0
|
||||
The docs below aim to reflect that leveling, but please also have a lookt at [the default config](https://code.vikunja.io/api/src/branch/master/config.yml.sample) file
|
||||
to better grasp how the nesting looks like.
|
||||
|
||||
cors:
|
||||
# Whether to enable or disable cors headers.
|
||||
enable: true
|
||||
# A list of origins which may access the api.
|
||||
origins:
|
||||
- *
|
||||
# How long (in seconds) the results of a preflight request can be cached.
|
||||
maxage: 0
|
||||
<!-- Generated config will be injected here -->
|
||||
|
||||
mailer:
|
||||
# Whether to enable the mailer or not. If it is disabled, all users are enabled right away and password reset is not possible.
|
||||
enabled: false
|
||||
# SMTP Host
|
||||
host: ""
|
||||
# SMTP Host port
|
||||
port: 587
|
||||
# SMTP username
|
||||
username: "user"
|
||||
# SMTP password
|
||||
password: ""
|
||||
# Wether to skip verification of the tls certificate on the server
|
||||
skiptlsverify: false
|
||||
# The default from address when sending emails
|
||||
fromemail: "mail@vikunja"
|
||||
# The length of the mail queue.
|
||||
queuelength: 100
|
||||
# The timeout in seconds after which the current open connection to the mailserver will be closed.
|
||||
queuetimeout: 30
|
||||
---
|
||||
|
||||
log:
|
||||
# A folder where all the logfiles should go.
|
||||
path: <rootpath>logs
|
||||
# Whether to show any logging at all or none
|
||||
enabled: true
|
||||
# Where the error log should go. Possible values are stdout, stderr, file or off to disable error logging.
|
||||
errors: "stdout"
|
||||
# Where the normal log should go. Possible values are stdout, stderr, file or off to disable standard logging.
|
||||
standard: "stdout"
|
||||
# Whether or not to log database queries. Useful for debugging. Possible values are stdout, stderr, file or off to disable database logging.
|
||||
database: "off"
|
||||
# Whether to log http requests or not. Possible values are stdout, stderr, file or off to disable http logging.
|
||||
http: "stdout"
|
||||
# Echo has its own logging which usually is unnessecary, which is why it is disabled by default. Possible values are stdout, stderr, file or off to disable standard logging.
|
||||
echo: "off"
|
||||
|
||||
ratelimit:
|
||||
# whether or not to enable the rate limit
|
||||
enabled: false
|
||||
# The kind on which rates are based. Can be either "user" for a rate limit per user or "ip" for an ip-based rate limit.
|
||||
kind: user
|
||||
# The time period in seconds for the limit
|
||||
period: 60
|
||||
# The max number of requests a user is allowed to do in the configured time period
|
||||
limit: 100
|
||||
# The store where the limit counter for each user is stored. Possible values are "memory" or "redis"
|
||||
store: memory
|
||||
## service
|
||||
|
||||
files:
|
||||
# The path where files are stored
|
||||
basepath: ./files # relative to the binary
|
||||
# The maximum size of a file, as a human-readable string.
|
||||
# Warning: The max size is limited 2^64-1 bytes due to the underlying datatype
|
||||
maxsize: 20MB
|
||||
|
||||
migration:
|
||||
# These are the settings for the wunderlist migrator
|
||||
wunderlist:
|
||||
# Wheter to enable the wunderlist migrator or not
|
||||
enable: false
|
||||
# The client id, required for making requests to the wunderlist api
|
||||
# You need to register your vikunja instance at https://developer.wunderlist.com/apps/new to get this
|
||||
clientid:
|
||||
# The client secret, also required for making requests to the wunderlist api
|
||||
clientsecret:
|
||||
# The url where clients are redirected after they authorized Vikunja to access their wunderlist stuff.
|
||||
# This needs to match the url you entered when registering your Vikunja instance at wunderlist.
|
||||
# This is usually the frontend url where the frontend then makes a request to /migration/wunderlist/migrate
|
||||
# with the code obtained from the wunderlist api.
|
||||
# Note that the vikunja frontend expects this to be /migrate/wunderlist
|
||||
redirecturl:
|
||||
{{< /highlight >}}
|
||||
|
||||
### JWTSecret
|
||||
|
||||
This token is used to verify issued JWT tokens.
|
||||
Default is a random token which will be generated at each startup of vikunja.
|
||||
(This means all already issued tokens will be invalid once you restart vikunja)
|
||||
|
||||
Default: `<jwt-secret>`
|
||||
|
||||
### interface
|
||||
|
||||
The interface on which to run the webserver
|
||||
|
||||
Default: `:3456`
|
||||
|
||||
### frontendurl
|
||||
|
||||
The URL of the frontend, used to send password reset emails.
|
||||
|
||||
Default: `<empty>`
|
||||
|
||||
### rootpath
|
||||
|
||||
The base path on the file system where the binary and assets are.
|
||||
Vikunja will also look in this path for a config file, so you could provide only this variable to point to a folder
|
||||
with a config file which will then be used.
|
||||
|
||||
Default: `<rootpath>`
|
||||
|
||||
### maxitemsperpage
|
||||
|
||||
The max number of items which can be returned per page
|
||||
|
||||
Default: `50`
|
||||
|
||||
### enablemetrics
|
||||
|
||||
If set to true, enables a /metrics endpoint for prometheus to collect metrics about the system
|
||||
You'll need to use redis for this in order to enable common metrics over multiple nodes
|
||||
|
||||
Default: `false`
|
||||
|
||||
### enablecaldav
|
||||
|
||||
Enable the caldav endpoint, see the docs for more details
|
||||
|
||||
Default: `true`
|
||||
|
||||
### motd
|
||||
|
||||
Set the motd message, available from the /info endpoint
|
||||
|
||||
Default: `<empty>`
|
||||
|
||||
### enablelinksharing
|
||||
|
||||
Enable sharing of lists via a link
|
||||
|
||||
Default: `true`
|
||||
|
||||
### enableregistration
|
||||
|
||||
Whether to let new users registering themselves or not
|
||||
|
||||
Default: `true`
|
||||
|
||||
### enabletaskattachments
|
||||
|
||||
Whether to enable task attachments or not
|
||||
|
||||
Default: `true`
|
||||
|
||||
### timezone
|
||||
|
||||
The time zone all timestamps are in
|
||||
|
||||
Default: `GMT`
|
||||
|
||||
### enabletaskcomments
|
||||
|
||||
Whether task comments should be enabled or not
|
||||
|
||||
Default: `true`
|
||||
|
||||
### enabletotp
|
||||
|
||||
Whether totp is enabled. In most cases you want to leave that enabled.
|
||||
|
||||
Default: `true`
|
||||
|
||||
### sentrydsn
|
||||
|
||||
If not empty, enables logging of crashes and unhandled errors in sentry.
|
||||
|
||||
Default: `<empty>`
|
||||
|
||||
### testingtoken
|
||||
|
||||
If not empty, this will enable `/test/{table}` endpoints which allow to put any content in the database.
|
||||
Used to reset the db before frontend tests. Because this is quite a dangerous feature allowing for lots of harm,
|
||||
each request made to this endpoint neefs to provide an `Authorization: <token>` header with the token from below. <br/>
|
||||
**You should never use this unless you know exactly what you're doing**
|
||||
|
||||
Default: `<empty>`
|
||||
|
||||
### enableemailreminders
|
||||
|
||||
If enabled, vikunja will send an email to everyone who is either assigned to a task or created it when a task reminder
|
||||
is due.
|
||||
|
||||
Default: `true`
|
||||
|
||||
---
|
||||
|
||||
## database
|
||||
|
||||
|
||||
|
||||
### type
|
||||
|
||||
Database type to use. Supported types are mysql, postgres and sqlite.
|
||||
|
||||
Default: `sqlite`
|
||||
|
||||
### user
|
||||
|
||||
Database user which is used to connect to the database.
|
||||
|
||||
Default: `vikunja`
|
||||
|
||||
### password
|
||||
|
||||
Databse password
|
||||
|
||||
Default: `<empty>`
|
||||
|
||||
### host
|
||||
|
||||
Databse host
|
||||
|
||||
Default: `localhost`
|
||||
|
||||
### database
|
||||
|
||||
Databse to use
|
||||
|
||||
Default: `vikunja`
|
||||
|
||||
### path
|
||||
|
||||
When using sqlite, this is the path where to store the data
|
||||
|
||||
Default: `./vikunja.db`
|
||||
|
||||
### maxopenconnections
|
||||
|
||||
Sets the max open connections to the database. Only used when using mysql and postgres.
|
||||
|
||||
Default: `100`
|
||||
|
||||
### maxidleconnections
|
||||
|
||||
Sets the maximum number of idle connections to the db.
|
||||
|
||||
Default: `50`
|
||||
|
||||
### maxconnectionlifetime
|
||||
|
||||
The maximum lifetime of a single db connection in miliseconds.
|
||||
|
||||
Default: `10000`
|
||||
|
||||
### sslmode
|
||||
|
||||
Secure connection mode. Only used with postgres.
|
||||
(see https://pkg.go.dev/github.com/lib/pq?tab=doc#hdr-Connection_String_Parameters)
|
||||
|
||||
Default: `disable`
|
||||
|
||||
---
|
||||
|
||||
## cache
|
||||
|
||||
|
||||
|
||||
### enabled
|
||||
|
||||
If cache is enabled or not
|
||||
|
||||
Default: `false`
|
||||
|
||||
### type
|
||||
|
||||
Cache type. Possible values are "keyvalue", "memory" or "redis".
|
||||
When choosing "keyvalue" this setting follows the one configured in the "keyvalue" section.
|
||||
When choosing "redis" you will need to configure the redis connection seperately.
|
||||
|
||||
Default: `keyvalue`
|
||||
|
||||
### maxelementsize
|
||||
|
||||
When using memory this defines the maximum size an element can take
|
||||
|
||||
Default: `1000`
|
||||
|
||||
---
|
||||
|
||||
## redis
|
||||
|
||||
|
||||
|
||||
### enabled
|
||||
|
||||
Whether to enable redis or not
|
||||
|
||||
Default: `false`
|
||||
|
||||
### host
|
||||
|
||||
The host of the redis server including its port.
|
||||
|
||||
Default: `localhost:6379`
|
||||
|
||||
### password
|
||||
|
||||
The password used to authenicate against the redis server
|
||||
|
||||
Default: `<empty>`
|
||||
|
||||
### db
|
||||
|
||||
0 means default database
|
||||
|
||||
Default: `0`
|
||||
|
||||
---
|
||||
|
||||
## cors
|
||||
|
||||
|
||||
|
||||
### enable
|
||||
|
||||
Whether to enable or disable cors headers.
|
||||
Note: If you want to put the frontend and the api on seperate domains or ports, you will need to enable this.
|
||||
Otherwise the frontend won't be able to make requests to the api through the browser.
|
||||
|
||||
Default: `true`
|
||||
|
||||
### origins
|
||||
|
||||
A list of origins which may access the api. These need to include the protocol (`http://` or `https://`) and port, if any.
|
||||
|
||||
Default: `<empty>`
|
||||
|
||||
### maxage
|
||||
|
||||
How long (in seconds) the results of a preflight request can be cached.
|
||||
|
||||
Default: `0`
|
||||
|
||||
---
|
||||
|
||||
## mailer
|
||||
|
||||
|
||||
|
||||
### enabled
|
||||
|
||||
Whether to enable the mailer or not. If it is disabled, all users are enabled right away and password reset is not possible.
|
||||
|
||||
Default: `false`
|
||||
|
||||
### host
|
||||
|
||||
SMTP Host
|
||||
|
||||
Default: `<empty>`
|
||||
|
||||
### port
|
||||
|
||||
SMTP Host port
|
||||
|
||||
Default: `587`
|
||||
|
||||
### username
|
||||
|
||||
SMTP username
|
||||
|
||||
Default: `user`
|
||||
|
||||
### password
|
||||
|
||||
SMTP password
|
||||
|
||||
Default: `<empty>`
|
||||
|
||||
### skiptlsverify
|
||||
|
||||
Wether to skip verification of the tls certificate on the server
|
||||
|
||||
Default: `false`
|
||||
|
||||
### fromemail
|
||||
|
||||
The default from address when sending emails
|
||||
|
||||
Default: `mail@vikunja`
|
||||
|
||||
### queuelength
|
||||
|
||||
The length of the mail queue.
|
||||
|
||||
Default: `100`
|
||||
|
||||
### queuetimeout
|
||||
|
||||
The timeout in seconds after which the current open connection to the mailserver will be closed.
|
||||
|
||||
Default: `30`
|
||||
|
||||
### forcessl
|
||||
|
||||
By default, vikunja will try to connect with starttls, use this option to force it to use ssl.
|
||||
|
||||
Default: `false`
|
||||
|
||||
---
|
||||
|
||||
## log
|
||||
|
||||
|
||||
|
||||
### path
|
||||
|
||||
A folder where all the logfiles should go.
|
||||
|
||||
Default: `<rootpath>logs`
|
||||
|
||||
### enabled
|
||||
|
||||
Whether to show any logging at all or none
|
||||
|
||||
Default: `true`
|
||||
|
||||
### standard
|
||||
|
||||
Where the normal log should go. Possible values are stdout, stderr, file or off to disable standard logging.
|
||||
|
||||
Default: `stdout`
|
||||
|
||||
### level
|
||||
|
||||
Change the log level. Possible values (case-insensitive) are CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG.
|
||||
|
||||
Default: `INFO`
|
||||
|
||||
### database
|
||||
|
||||
Whether or not to log database queries. Useful for debugging. Possible values are stdout, stderr, file or off to disable database logging.
|
||||
|
||||
Default: `off`
|
||||
|
||||
### databaselevel
|
||||
|
||||
The log level for database log messages. Possible values (case-insensitive) are CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG.
|
||||
|
||||
Default: `WARNING`
|
||||
|
||||
### http
|
||||
|
||||
Whether to log http requests or not. Possible values are stdout, stderr, file or off to disable http logging.
|
||||
|
||||
Default: `stdout`
|
||||
|
||||
### 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.
|
||||
|
||||
Default: `off`
|
||||
|
||||
---
|
||||
|
||||
## ratelimit
|
||||
|
||||
|
||||
|
||||
### enabled
|
||||
|
||||
whether or not to enable the rate limit
|
||||
|
||||
Default: `false`
|
||||
|
||||
### kind
|
||||
|
||||
The kind on which rates are based. Can be either "user" for a rate limit per user or "ip" for an ip-based rate limit.
|
||||
|
||||
Default: `user`
|
||||
|
||||
### period
|
||||
|
||||
The time period in seconds for the limit
|
||||
|
||||
Default: `60`
|
||||
|
||||
### limit
|
||||
|
||||
The max number of requests a user is allowed to do in the configured time period
|
||||
|
||||
Default: `100`
|
||||
|
||||
### store
|
||||
|
||||
The store where the limit counter for each user is stored.
|
||||
Possible values are "keyvalue", "memory" or "redis".
|
||||
When choosing "keyvalue" this setting follows the one configured in the "keyvalue" section.
|
||||
|
||||
Default: `keyvalue`
|
||||
|
||||
---
|
||||
|
||||
## files
|
||||
|
||||
|
||||
|
||||
### basepath
|
||||
|
||||
The path where files are stored
|
||||
|
||||
Default: `./files`
|
||||
|
||||
### maxsize
|
||||
|
||||
The maximum size of a file, as a human-readable string.
|
||||
Warning: The max size is limited 2^64-1 bytes due to the underlying datatype
|
||||
|
||||
Default: `20MB`
|
||||
|
||||
---
|
||||
|
||||
## migration
|
||||
|
||||
|
||||
|
||||
### wunderlist
|
||||
|
||||
These are the settings for the wunderlist migrator
|
||||
|
||||
Default: `<empty>`
|
||||
|
||||
### todoist
|
||||
|
||||
Default: `<empty>`
|
||||
|
||||
### trello
|
||||
|
||||
Default: `<empty>`
|
||||
|
||||
### microsofttodo
|
||||
|
||||
Default: `<empty>`
|
||||
|
||||
---
|
||||
|
||||
## avatar
|
||||
|
||||
|
||||
|
||||
### gravatarexpiration
|
||||
|
||||
When using gravatar, this is the duration in seconds until a cached gravatar user avatar expires
|
||||
|
||||
Default: `3600`
|
||||
|
||||
---
|
||||
|
||||
## backgrounds
|
||||
|
||||
|
||||
|
||||
### enabled
|
||||
|
||||
Whether to enable backgrounds for lists at all.
|
||||
|
||||
Default: `true`
|
||||
|
||||
### providers
|
||||
|
||||
Default: `<empty>`
|
||||
|
||||
---
|
||||
|
||||
## legal
|
||||
|
||||
Legal urls
|
||||
Will be shown in the frontend if configured here
|
||||
|
||||
|
||||
|
||||
### imprinturl
|
||||
|
||||
Default: `<empty>`
|
||||
|
||||
### privacyurl
|
||||
|
||||
Default: `<empty>`
|
||||
|
||||
---
|
||||
|
||||
## keyvalue
|
||||
|
||||
Key Value Storage settings
|
||||
The Key Value Storage is used for different kinds of things like metrics and a few cache systems.
|
||||
|
||||
|
||||
|
||||
### type
|
||||
|
||||
The type of the storage backend. Can be either "memory" or "redis". If "redis" is chosen it needs to be configured seperately.
|
||||
|
||||
Default: `memory`
|
||||
|
||||
---
|
||||
|
||||
## auth
|
||||
|
||||
|
||||
|
||||
### local
|
||||
|
||||
Local authentication will let users log in and register (if enabled) through the db.
|
||||
This is the default auth mechanism and does not require any additional configuration.
|
||||
|
||||
Default: `<empty>`
|
||||
|
||||
### openid
|
||||
|
||||
OpenID configuration will allow users to authenticate through a third-party OpenID Connect compatible provider.<br/>
|
||||
The provider needs to support the `openid`, `profile` and `email` scopes.<br/>
|
||||
**Note:** The frontend expects to be redirected after authentication by the third party
|
||||
to <frontend-url>/auth/openid/<auth key>. Please make sure to configure the redirect url with your third party
|
||||
auth service accordingy if you're using the default vikunja frontend.
|
||||
Take a look at the [default config file](https://kolaente.dev/vikunja/api/src/branch/master/config.yml.sample) for more information about how to configure openid authentication.
|
||||
|
||||
Default: `<empty>`
|
||||
|
||||
|
||||
231
docs/content/doc/setup/docker-start-to-finish.md
Normal file
231
docs/content/doc/setup/docker-start-to-finish.md
Normal file
@@ -0,0 +1,231 @@
|
||||
---
|
||||
date: "2020-05-24:00:00+02:00"
|
||||
title: "Docker Walkthrough"
|
||||
draft: false
|
||||
type: "doc"
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "setup"
|
||||
---
|
||||
|
||||
# Setup with docker from start to finish
|
||||
|
||||
This tutorial assumes basic knowledge of docker.
|
||||
It is aimed at beginners and should get you up and running quickly.
|
||||
|
||||
We'll use [docker compose](https://docs.docker.com/compose/) to make handling the bunch of containers easier.
|
||||
|
||||
> If you have any issues setting up vikunja, please don't hesitate to reach out to us via [matrix](https://riot.im/app/#/room/!dCRiCiLaCCFVNlDnYs:matrix.org?via=matrix.org), the [community forum](https://community.vikunja.io/) or even [email](mailto:hello@vikunja.io).
|
||||
|
||||
{{< table_of_contents >}}
|
||||
|
||||
## Preparations (optional)
|
||||
|
||||
Create a directory for the project where all data and the compose file will live in.
|
||||
|
||||
## Create all necessary files
|
||||
|
||||
Create a `docker-compose.yml` file with the following contents in your directory:
|
||||
|
||||
{{< highlight yaml >}}
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
db:
|
||||
image: mariadb:10
|
||||
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: supersecret
|
||||
MYSQL_DATABASE: vikunja
|
||||
volumes:
|
||||
- ./db:/var/lib/mysql
|
||||
restart: unless-stopped
|
||||
api:
|
||||
image: vikunja/api
|
||||
environment:
|
||||
VIKUNJA_DATABASE_HOST: db
|
||||
VIKUNJA_DATABASE_PASSWORD: supersecret
|
||||
VIKUNJA_DATABASE_TYPE: mysql
|
||||
VIKUNJA_DATABASE_USER: root
|
||||
VIKUNJA_DATABASE_DATABASE: vikunja
|
||||
volumes:
|
||||
- ./files:/app/vikunja/files
|
||||
depends_on:
|
||||
- db
|
||||
restart: unless-stopped
|
||||
frontend:
|
||||
image: vikunja/frontend
|
||||
restart: unless-stopped
|
||||
proxy:
|
||||
image: nginx
|
||||
ports:
|
||||
- 80:80
|
||||
volumes:
|
||||
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
|
||||
depends_on:
|
||||
- api
|
||||
- frontend
|
||||
restart: unless-stopped
|
||||
{{< /highlight >}}
|
||||
|
||||
This defines four services, each with their own container:
|
||||
|
||||
* An api service which runs the vikunja api. Most of the core logic lives here.
|
||||
* The frontend which will make vikunja actually usable for most people.
|
||||
* A database container which will store all lists, tasks, etc. We're using mariadb here, but you're free to use mysql or postgres if you want.
|
||||
* A proxy service which makes the frontend and api available on the same port, redirecting all requests to `/api` to the api container.
|
||||
If you already have a proxy on your host, you may want to check out the [reverse proxy examples]() to use that.
|
||||
By default, it uses port 80 on the host.
|
||||
To change to something different, you'll need to change the `ports` section in the service definition.
|
||||
The number before the colon is the host port - This is where you can reach vikunja from the outside once all is up and running.
|
||||
|
||||
For the proxy service we'll need another bit of configuration.
|
||||
Create an `nginx.conf` in your directory (next to the `docker-compose.yml` file) and add the following contents to it:
|
||||
|
||||
{{< highlight conf >}}
|
||||
server {
|
||||
listen 80;
|
||||
|
||||
location / {
|
||||
proxy_pass http://frontend:80;
|
||||
}
|
||||
|
||||
location ~* ^/(api|dav|\.well-known)/ {
|
||||
proxy_pass http://api:3456;
|
||||
client_max_body_size 20M;
|
||||
}
|
||||
}
|
||||
{{< /highlight >}}
|
||||
|
||||
This is a simple proxy configuration which will forward all requests to `/api/` to the api container and everything else to the frontend.
|
||||
|
||||
<div class="notification is-info">
|
||||
<b>NOTE:</b> Even if you want to make your installation available under a different port, you don't need to change anything in this configuration.
|
||||
</div>
|
||||
|
||||
<div class="notification is-warning">
|
||||
<b>NOTE:</b> If you change the max upload size in Vikunja's settings, you'll need to also change the <code>client_max_body_size</code> in the nginx proxy config.
|
||||
</div>
|
||||
|
||||
## Run it
|
||||
|
||||
Run `sudo docker-compose up` in your directory and take a look at the output you get.
|
||||
When first started, Vikunja will set up the database and run all migrations etc.
|
||||
Once it is ready, you should see a message like this one in your console:
|
||||
|
||||
```
|
||||
api_1 | 2020-05-24T11:15:37.560386009Z: INFO ▶ cmd/func1 025 Vikunja version 0.13.1+19-e9bc3246ce, built at Sun, 24 May 2020 11:10:36 +0000
|
||||
api_1 | ⇨ http server started on [::]:3456
|
||||
```
|
||||
|
||||
This indicates all setup has been successful.
|
||||
If you get any errors, see below:
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
Vikunja might not run on the first try.
|
||||
There are a few potential issues that could be causing this.
|
||||
|
||||
#### No connection to the database
|
||||
|
||||
Indicated by an error message like this one from the api container:
|
||||
|
||||
```
|
||||
2020/05/23 15:37:59 Config File "config" Not Found in "[/app/vikunja /etc/vikunja /app/vikunja/.config/vikunja]"
|
||||
2020/05/23 15:37:59 Using default config.
|
||||
2020-05-23T15:37:59.974435725Z: CRITICAL ▶ migration/Migrate 002 Migration failed: dial tcp 172.19.0.2:3306: connect: connection refused
|
||||
```
|
||||
|
||||
Especially when using mysql, this can happen on first start, because the mysql database container will take a few seconds to start.
|
||||
Vikunja does not know the container is not ready, therefore it will just try to connect to the db, fail since it is not ready and exit.
|
||||
|
||||
If you're using the docker compose example from above, you may notice the `restart: unless-stopped` option at the api service.
|
||||
This tells docker to restart the api container if it exits, unless you explicitly stop it.
|
||||
Therefore, it should "magically fix itself" by automatically restarting the container.
|
||||
|
||||
After a few seconds (or minutes) you should see a log message like this one from the mariadb container:
|
||||
|
||||
```
|
||||
2020-05-24 11:42:15 0 [Note] mysqld: ready for connections.
|
||||
Version: '10.4.12-MariaDB-1:10.4.12+maria~bionic' socket: '/var/run/mysqld/mysqld.sock' port: 3306 mariadb.org binary distribution
|
||||
```
|
||||
|
||||
The next restart of Vikunja should be successful.
|
||||
If not, there might be a different error or a bug with Vikunja, please reach out to us in that case.
|
||||
|
||||
(If you have an idea about how we could improve this, we'd like to hear it!)
|
||||
|
||||
#### "Not a directory"
|
||||
|
||||
If you get an error like this one:
|
||||
|
||||
```
|
||||
ERROR: for vikunja_proxy_1 Cannot start service proxy: OCI runtime create failed: container_linux.go:349: starting container process caused "process_linux.go:449: container init caused \"rootfs_linux.go:58: mounting \\\"vikunja/nginx.conf\\\" to rootfs \\\"/var/lib/docker/overlay2/9c8b8f9419c29dad0d1233fbb0a3c36cf403dabd7a55d6f0a47b0c1dd6029994/merged\\\" at \\\"/var/lib/docker/overlay2/9c8b8f9419c29dad0d1233fbb0a3c36cf403dabd7a55d6f0a47b0c1dd6029994/merged/etc/nginx/conf.d/default.conf\\\" caused \\\"not a directory\\\"\"": unknown: Are you trying to mount a directory onto a file (or vice-versa)? Check if the specified host path exists and is the expected type
|
||||
```
|
||||
|
||||
this means docker tried to mount a directory from the host to a file in the container.
|
||||
This can happen if you did not create the `nginx.conf` file.
|
||||
Because there is a volume mount for it in the `docker-compose.yml`, Docker will create a folder because non exists, assuming you want to mount a folder into the container.
|
||||
|
||||
To fix this, create the file and restart the containers again.
|
||||
|
||||
#### Migration failed: commands out of sync
|
||||
|
||||
If you get an error like this one:
|
||||
|
||||
```
|
||||
2020/05/23 15:53:38 Config File "config" Not Found in "[/app/vikunja /etc/vikunja /app/vikunja/.config/vikunja]"
|
||||
2020/05/23 15:53:38 Using default config.
|
||||
2020-05-23T15:53:38.762747276Z: CRITICAL ▶ migration/Migrate 002 Migration failed: commands out of sync. Did you run multiple statements at once?
|
||||
```
|
||||
|
||||
This is a mysql issue.
|
||||
Currently, we don't have a better solution than to completely wipe the database files and start over.
|
||||
To do this, first stop everything by running `sudo docker-compose down`, then remove the `db/` folder in your current folder with `sudo rm -rf db` and start the whole stack again with `sudo docker-compose up -d`.
|
||||
|
||||
## Try it
|
||||
|
||||
Head over to `http://<host-ip or url>/api/v1/info` in a browser.
|
||||
You should see something like this:
|
||||
|
||||
{{< highlight json >}}
|
||||
{
|
||||
"version": "0.13.1+19-e9bc3246ce",
|
||||
"frontend_url": "http://localhost:8080/",
|
||||
"motd": "test",
|
||||
"link_sharing_enabled": true,
|
||||
"max_file_size": "20MB",
|
||||
"registration_enabled": true,
|
||||
"available_migrators": [
|
||||
"wunderlist",
|
||||
"todoist"
|
||||
],
|
||||
"task_attachments_enabled": true
|
||||
}
|
||||
{{< /highlight >}}
|
||||
|
||||
This shows you can reach the api through the api proxy.
|
||||
|
||||
Now head over to `http://<host-ip or url>/` which should show the login mask.
|
||||
|
||||
## Make it persistent
|
||||
|
||||
Currently, Vikunja runs in foreground in your terminal.
|
||||
For a real-world scenario this is not the best way.
|
||||
|
||||
Back in your terminal, stop the stack by pressing `CTRL-C` on your keyboard.
|
||||
Then run `sudo docker-compose up -d` in your again.
|
||||
The `-d` flag at the end of the command will tell docker to run the containers in the background.
|
||||
If you need to check the logs after that, you can run `sudo docker-compose logs`.
|
||||
|
||||
Vikunja does not have any default users, you'll need to register and account.
|
||||
After that, you can use it.
|
||||
|
||||
## Tear it all down
|
||||
|
||||
If you want to completely stop all containers run `sudo docker-compose down` in your terminal.
|
||||
|
||||
## Improve this guide
|
||||
|
||||
We'll happily accept suggestions and improvements for this guide.
|
||||
Please [reach out to us](https://vikunja.io/contact/) if you have any.
|
||||
@@ -15,11 +15,44 @@ It uses an nginx container or traefik on the host to proxy backend and frontend
|
||||
|
||||
For all available configuration options, see [configuration]({{< ref "config.md">}}).
|
||||
|
||||
## Example with traefik
|
||||
<div class="notification is-warning">
|
||||
<b>NOTE:</b> If you intend to run Vikunja with mysql and/or to use non-latin characters
|
||||
<a href="{{< ref "utf-8.md">}}">make sure your db is utf-8 compatible</a>.<br/>
|
||||
All examples on this page already reflect this and do not require additional work.
|
||||
</div>
|
||||
|
||||
This example assumes [traefik](https://traefik.io) in version 1 installed and configured to [use docker as a configuration provider](https://docs.traefik.io/v1.7/configuration/backends/docker/).
|
||||
{{< table_of_contents >}}
|
||||
|
||||
### Without redis
|
||||
## Redis
|
||||
|
||||
To use redis, you'll need to add this to the config examples below:
|
||||
|
||||
{{< highlight yaml >}}
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
api:
|
||||
image: vikunja/api
|
||||
environment:
|
||||
VIKUNJA_REDIS_ENABLED: 1
|
||||
VIKUNJA_REDIS_HOST: 'redis:6379'
|
||||
VIKUNJA_CACHE_ENABLED: 1
|
||||
VIKUNJA_CACHE_TYPE: redis
|
||||
volumes:
|
||||
- ./files:/app/vikunja/files
|
||||
redis:
|
||||
image: redis
|
||||
{{< /highlight >}}
|
||||
|
||||
## Example with traefik 2
|
||||
|
||||
This example assumes [traefik](https://traefik.io) version 2 installed and configured to [use docker as a configuration provider](https://docs.traefik.io/providers/docker/).
|
||||
|
||||
We also make a few assumtions here which you'll most likely need to adjust for your traefik setup:
|
||||
|
||||
* Your domain is `vikunja.example.com`
|
||||
* The entrypoint you want to make vikunja available from is called `https`
|
||||
* The tls cert resolver is called `acme`
|
||||
|
||||
{{< highlight yaml >}}
|
||||
version: '3'
|
||||
@@ -42,25 +75,24 @@ services:
|
||||
- db
|
||||
restart: unless-stopped
|
||||
labels:
|
||||
- "traefik.docker.network=web"
|
||||
- "traefik.enable=true"
|
||||
- "traefik.frontend.rule=Host:vikunja.example.com;PathPrefix:/api/v1"
|
||||
- "traefik.port=3456"
|
||||
- "traefik.protocol=http"
|
||||
- "traefik.http.routers.vikunja-api.rule=Host(`vikunja.example.com`) && PathPrefix(`/api/v1`, `/dav/`, `/.well-known/`)"
|
||||
- "traefik.http.routers.vikunja-api.entrypoints=https"
|
||||
- "traefik.http.routers.vikunja-api.tls.certResolver=acme"
|
||||
frontend:
|
||||
image: vikunja/frontend
|
||||
labels:
|
||||
- "traefik.docker.network=web"
|
||||
- "traefik.enable=true"
|
||||
- "traefik.frontend.rule=Host:vikunja.example.com;PathPrefix:/"
|
||||
- "traefik.port=80"
|
||||
- "traefik.protocol=http"
|
||||
- "traefik.http.routers.vikunja-frontend.rule=Host(`vikunja.example.com`)"
|
||||
- "traefik.http.routers.vikunja-frontend.entrypoints=https"
|
||||
- "traefik.http.routers.vikunja-frontend.tls.certResolver=acme"
|
||||
networks:
|
||||
- web
|
||||
- default
|
||||
restart: unless-stopped
|
||||
db:
|
||||
image: mariadb:10
|
||||
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: supersupersecret
|
||||
MYSQL_USER: vikunja
|
||||
@@ -76,7 +108,9 @@ networks:
|
||||
external: true
|
||||
{{< /highlight >}}
|
||||
|
||||
### With redis
|
||||
## Example with traefik 1
|
||||
|
||||
This example assumes [traefik](https://traefik.io) in version 1 installed and configured to [use docker as a configuration provider](https://docs.traefik.io/v1.7/configuration/backends/docker/).
|
||||
|
||||
{{< highlight yaml >}}
|
||||
version: '3'
|
||||
@@ -90,10 +124,6 @@ services:
|
||||
VIKUNJA_DATABASE_TYPE: mysql
|
||||
VIKUNJA_DATABASE_USER: vikunja
|
||||
VIKUNJA_DATABASE_DATABASE: vikunja
|
||||
VIKUNJA_REDIS_ENABLED: 1
|
||||
VIKUNJA_REDIS_HOST: 'redis:6379'
|
||||
VIKUNJA_CACHE_ENABLED: 1
|
||||
VIKUNJA_CACHE_TYPE: redis
|
||||
volumes:
|
||||
- ./files:/app/vikunja/files
|
||||
networks:
|
||||
@@ -101,12 +131,11 @@ services:
|
||||
- default
|
||||
depends_on:
|
||||
- db
|
||||
- redis
|
||||
restart: unless-stopped
|
||||
labels:
|
||||
- "traefik.docker.network=web"
|
||||
- "traefik.enable=true"
|
||||
- "traefik.frontend.rule=Host:vikunja.example.com;PathPrefix:/api/v1"
|
||||
- "traefik.frontend.rule=Host:vikunja.example.com;PathPrefix:/api/v1,/dav/,/.well-known"
|
||||
- "traefik.port=3456"
|
||||
- "traefik.protocol=http"
|
||||
frontend:
|
||||
@@ -123,6 +152,7 @@ services:
|
||||
restart: unless-stopped
|
||||
db:
|
||||
image: mariadb:10
|
||||
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: supersupersecret
|
||||
MYSQL_USER: vikunja
|
||||
@@ -132,8 +162,6 @@ services:
|
||||
- ./db:/var/lib/mysql
|
||||
restart: unless-stopped
|
||||
command: --max-connections=1000
|
||||
redis:
|
||||
image: redis
|
||||
|
||||
networks:
|
||||
web:
|
||||
@@ -153,13 +181,18 @@ server {
|
||||
proxy_pass http://frontend:80;
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
location ~* ^/(api|dav|\.well-known)/ {
|
||||
proxy_pass http://api:3456;
|
||||
client_max_body_size 20M;
|
||||
}
|
||||
}
|
||||
{{< /highlight >}}
|
||||
|
||||
### Without redis
|
||||
<div class="notification is-warning">
|
||||
<b>NOTE:</b> If you change the max upload size in Vikunja's settings, you'll need to also change the <code>client_max_body_size</code> in the nginx proxy config.
|
||||
</div>
|
||||
|
||||
`docker-compose.yml` config:
|
||||
|
||||
{{< highlight yaml >}}
|
||||
version: '3'
|
||||
@@ -167,11 +200,13 @@ version: '3'
|
||||
services:
|
||||
db:
|
||||
image: mariadb:10
|
||||
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: supersecret
|
||||
MYSQL_DATABASE: vikunja
|
||||
volumes:
|
||||
- ./db:/var/lib/mysql
|
||||
restart: unless-stopped
|
||||
api:
|
||||
image: vikunja/api
|
||||
environment:
|
||||
@@ -184,8 +219,10 @@ services:
|
||||
- ./files:/app/vikunja/files
|
||||
depends_on:
|
||||
- db
|
||||
restart: unless-stopped
|
||||
frontend:
|
||||
image: vikunja/frontend
|
||||
restart: unless-stopped
|
||||
proxy:
|
||||
image: nginx
|
||||
ports:
|
||||
@@ -195,9 +232,23 @@ services:
|
||||
depends_on:
|
||||
- api
|
||||
- frontend
|
||||
restart: unless-stopped
|
||||
{{< /highlight >}}
|
||||
|
||||
### With redis
|
||||
## Example with Caddy v2 as proxy
|
||||
|
||||
You will need the following `Caddyfile` on your host (or elsewhere, but then you'd need to adjust the proxy mount at the bottom of the compose file):
|
||||
|
||||
{{< highlight conf >}}
|
||||
vikunja.example.com {
|
||||
reverse_proxy /api/* api:3456
|
||||
reverse_proxy /.well-known/* api:3456
|
||||
reverse_proxy /dav/* api:3456
|
||||
reverse_proxy frontend:80
|
||||
}
|
||||
{{< /highlight >}}
|
||||
|
||||
`docker-compose.yml` config:
|
||||
|
||||
{{< highlight yaml >}}
|
||||
version: '3'
|
||||
@@ -205,13 +256,13 @@ version: '3'
|
||||
services:
|
||||
db:
|
||||
image: mariadb:10
|
||||
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: supersecret
|
||||
MYSQL_DATABASE: vikunja
|
||||
volumes:
|
||||
- ./db:/var/lib/mysql
|
||||
redis:
|
||||
image: redis
|
||||
restart: unless-stopped
|
||||
api:
|
||||
image: vikunja/api
|
||||
environment:
|
||||
@@ -220,24 +271,23 @@ services:
|
||||
VIKUNJA_DATABASE_TYPE: mysql
|
||||
VIKUNJA_DATABASE_USER: root
|
||||
VIKUNJA_DATABASE_DATABASE: vikunja
|
||||
VIKUNJA_REDIS_ENABLED: 1
|
||||
VIKUNJA_REDIS_HOST: 'redis:6379'
|
||||
VIKUNJA_CACHE_ENABLED: 1
|
||||
VIKUNJA_CACHE_TYPE: redis
|
||||
volumes:
|
||||
- ./files:/app/vikunja/files
|
||||
depends_on:
|
||||
- db
|
||||
- redis
|
||||
restart: unless-stopped
|
||||
frontend:
|
||||
image: vikunja/frontend
|
||||
proxy:
|
||||
image: nginx
|
||||
restart: unless-stopped
|
||||
caddy:
|
||||
image: caddy
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 80:80
|
||||
volumes:
|
||||
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
depends_on:
|
||||
- api
|
||||
- frontend
|
||||
{{< /highlight >}}
|
||||
volumes:
|
||||
- ./Caddyfile:/etc/caddy/Caddyfile:ro
|
||||
{{< /highlight >}}
|
||||
|
||||
@@ -10,6 +10,13 @@ menu:
|
||||
|
||||
# Backend
|
||||
|
||||
<div class="notification is-warning">
|
||||
<b>NOTE:</b> If you intend to run Vikunja with mysql and/or to use non-latin characters
|
||||
<a href="{{< ref "utf-8.md">}}">make sure your db is utf-8 compatible</a>.
|
||||
</div>
|
||||
|
||||
{{< table_of_contents >}}
|
||||
|
||||
## Install from binary
|
||||
|
||||
Download a copy of Vikunja from the [download page](https://vikunja.io/en/download/) for your architecture.
|
||||
@@ -106,7 +113,7 @@ docker run -p 3456:3456 vikunja/api
|
||||
{{< /highlight >}}
|
||||
|
||||
to run with a standard configuration.
|
||||
This will expose
|
||||
This will expose vikunja on port `3456` on the host running the container.
|
||||
|
||||
You can mount a local configuration like so:
|
||||
|
||||
@@ -117,6 +124,18 @@ docker run -p 3456:3456 -v /path/to/config/on/host.yml:/app/vikunja/config.yml:r
|
||||
Though it is recommended to use eviroment variables or `.env` files to configure Vikunja in docker.
|
||||
See [config]({{< ref "config.md">}}) for a list of available configuration options.
|
||||
|
||||
### Files volume
|
||||
|
||||
By default the container stores all files uploaded and used through vikunja inside of `/app/vikunja/files` which is created as a docker volume.
|
||||
You should mount the volume somewhere to the host to permanently store the files and don't loose them if the container restarts.
|
||||
|
||||
### Setting user and group id of the user running vikunja
|
||||
|
||||
You can set the user and group id of the user running vikunja with the `PUID` and `PGID` evironment variables.
|
||||
This follows the pattern used by [the linuxserver.io](https://docs.linuxserver.io/general/understanding-puid-and-pgid) docker images.
|
||||
|
||||
This is useful to solve general permission problems when host-mounting volumes such as the volume used for task attachments.
|
||||
|
||||
### Docker compose
|
||||
|
||||
To run the backend with a mariadb database you can use this example [docker-compose](https://docs.docker.com/compose/) file:
|
||||
@@ -132,13 +151,16 @@ services:
|
||||
VIKUNJA_DATABASE_TYPE: mysql
|
||||
VIKUNJA_DATABASE_USER: root
|
||||
VIKUNJA_SERVICE_JWTSECRET: <generated secret>
|
||||
volumes:
|
||||
- ./files:/app/vikunja/files
|
||||
db:
|
||||
image: mariadb:10
|
||||
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: supersecret
|
||||
MYSQL_DATABASE: vikunja
|
||||
volumes:
|
||||
- ./db:/var/lib/mysql
|
||||
- ./db:/var/lib/mysql
|
||||
{{< /highlight >}}
|
||||
|
||||
See [full docker example]({{< ref "full-docker-example.md">}}) for more varations of this config.
|
||||
@@ -156,6 +178,100 @@ dpkg -i vikunja.deb
|
||||
This will install the backend to `/opt/vikunja`.
|
||||
To configure it, use the config file in `/etc/vikunja/config.yml`.
|
||||
|
||||
## FreeBSD / FreeNAS
|
||||
|
||||
Unfortunately, we currently can't provide pre-built binaries for FreeBSD.
|
||||
As a workaround, it is possible to compile vikunja for FreeBSD directly on a FreeBSD machine, a guide is available below:
|
||||
|
||||
*Thanks to HungrySkeleton who originally created this guide [in the forum](https://community.vikunja.io/t/freebsd-support/69/11).*
|
||||
|
||||
### Jail Setup
|
||||
|
||||
1. Create jail named ```vikunja```
|
||||
2. Set jail properties to 'auto start'
|
||||
3. Mount storage (```/mnt``` to ```jailData/vikunja```)
|
||||
4. Start jail & SSH into it
|
||||
|
||||
### Installing packages
|
||||
|
||||
{{< highlight bash >}}
|
||||
pkg update && pkg upgrade -y
|
||||
pkg install nano git go gmake
|
||||
go install github.com/magefile/mage
|
||||
{{< /highlight >}}
|
||||
|
||||
### Clone vikunja repo
|
||||
|
||||
{{< highlight bash >}}
|
||||
mkdir /mnt/GO/code.vikunja.io
|
||||
cd /mnt/GO/code.vikunja.io
|
||||
git clone https://code.vikunja.io/api
|
||||
cd /mnt/GO/code.vikunja.io/api
|
||||
{{< /highlight >}}
|
||||
|
||||
### Compile binaries
|
||||
|
||||
{{< highlight bash >}}
|
||||
go install
|
||||
mage build
|
||||
{{< /highlight >}}
|
||||
|
||||
### Create folder to install backend server into
|
||||
|
||||
{{< highlight bash >}}
|
||||
mkdir /mnt/backend
|
||||
cp /mnt/GO/code.vikunja.io/api/vikunja /mnt/backend/vikunja
|
||||
cd /mnt/backend
|
||||
chmod +x /mnt/backend/vikunja
|
||||
{{< /highlight >}}
|
||||
|
||||
### Set vikunja to boot on startup
|
||||
|
||||
{{< highlight bash >}}
|
||||
nano /etc/rc.d/vikunja
|
||||
{{< /highlight >}}
|
||||
|
||||
Then paste into the file:
|
||||
|
||||
{{< highlight bash >}}
|
||||
#!/bin/sh
|
||||
|
||||
. /etc/rc.subr
|
||||
|
||||
name=vikunja
|
||||
rcvar=vikunja_enable
|
||||
|
||||
command="/mnt/backend/${name}"
|
||||
|
||||
load_rc_config $name
|
||||
run_rc_command "$1"
|
||||
{{< /highlight >}}
|
||||
|
||||
Save and exit. Then execute:
|
||||
|
||||
{{< highlight bash >}}
|
||||
chmod +x /etc/rc.d/vikunja
|
||||
nano /etc/rc.conf
|
||||
{{< /highlight >}}
|
||||
|
||||
Then add line to bottom of file:
|
||||
|
||||
{{< highlight bash >}}
|
||||
vikunja_enable="YES"
|
||||
{{< /highlight >}}
|
||||
|
||||
Test vikunja now works with
|
||||
|
||||
{{< highlight bash >}}
|
||||
service vikunja start
|
||||
{{< /highlight >}}
|
||||
|
||||
The API is now available through IP:
|
||||
|
||||
```
|
||||
192.168.1.XXX:3456
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
See [available configuration options]({{< ref "config.md">}}).
|
||||
|
||||
@@ -17,6 +17,22 @@ Unzip them and store them somewhere your server can access them.
|
||||
|
||||
You also need to configure a rewrite condition to internally redirect all requests to `index.html` which handles all urls.
|
||||
|
||||
{{< table_of_contents >}}
|
||||
|
||||
## API URL configuration
|
||||
|
||||
By default, the frontend assumes it can reach the api at `/api/v1` relative to the frontend url.
|
||||
This means that if you make the frontend available at, say `https://vikunja.example.com`, it tries to reach the api
|
||||
at `https://vikunja.example.com/api/v1`.
|
||||
In this scenario it is not possible for the frontend and the api to live on seperate servers or even just seperate
|
||||
ports on the same server with [the use of a reverse proxy]({{< ref "reverse-proxies.md">}}).
|
||||
|
||||
To make configurations like this possible, the api url can be set in the `index.html` file of the frontend releases.
|
||||
Just open the file with a text editor - there are comments which will explain how to set the url.
|
||||
|
||||
**Note:** This needs to be done again after every update.
|
||||
(If you have a good idea for a better solution than this, we'd love to [hear it](https://vikunja.io/contact/))
|
||||
|
||||
## Docker
|
||||
|
||||
The docker image is based on nginx and just contains all nessecary files for the frontend.
|
||||
@@ -31,6 +47,16 @@ which will run the docker image and expose port 80 on the host.
|
||||
|
||||
See [full docker example]({{< ref "full-docker-example.md">}}) for more varations of this config.
|
||||
|
||||
### Setting user and group id of the user running vikunja
|
||||
|
||||
You can set the user and group id of the user running vikunja with the `PUID` and `PGID` evironment variables.
|
||||
This follows the pattern used by [the linuxserver.io](https://docs.linuxserver.io/general/understanding-puid-and-pgid) docker images.
|
||||
|
||||
### API URL configuration in docker
|
||||
|
||||
When running the frontend with docker, it is possible to set the environment variable `$VIKUNJA_API_URL` to the api url.
|
||||
It is therefore not needed to change the url manually inside the docker container.
|
||||
|
||||
## NGINX
|
||||
|
||||
Below are two example configurations which you can put in your `nginx.conf`:
|
||||
|
||||
@@ -30,6 +30,7 @@ This document provides an overview and instructions for the different methods.
|
||||
* [Docker]({{< ref "install-backend.md#docker">}})
|
||||
* [Debian packages]({{< ref "install-backend.md#debian-packages">}})
|
||||
* [Configuration]({{< ref "config.md">}})
|
||||
* [UTF-8 Settings]({{< ref "utf-8.md">}})
|
||||
* [Frontend]({{< ref "install-frontend.md">}})
|
||||
* [Docker]({{< ref "install-frontend.md#docker">}})
|
||||
* [NGINX]({{< ref "install-frontend.md#nginx">}})
|
||||
|
||||
@@ -13,6 +13,8 @@ menu:
|
||||
These examples assume you have an instance of the backend running on your server listening on port `3456`.
|
||||
If you've changed this setting, you need to update the server configurations accordingly.
|
||||
|
||||
{{< table_of_contents >}}
|
||||
|
||||
## NGINX
|
||||
|
||||
Below are two example configurations which you can put in your `nginx.conf`:
|
||||
@@ -43,12 +45,17 @@ server {
|
||||
index index.html index.htm;
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
location ~* ^/(api|dav|\.well-known)/ {
|
||||
proxy_pass http://localhost:3456;
|
||||
client_max_body_size 20M;
|
||||
}
|
||||
}
|
||||
{{< /highlight >}}
|
||||
|
||||
<div class="notification is-warning">
|
||||
<b>NOTE:</b> If you change the max upload size in Vikunja's settings, you'll need to also change the <code>client_max_body_size</code> in the nginx proxy config.
|
||||
</div>
|
||||
|
||||
### without gzip
|
||||
|
||||
{{< highlight conf >}}
|
||||
@@ -62,12 +69,17 @@ server {
|
||||
index index.html index.htm;
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
location ~* ^/(api|dav|\.well-known)/ {
|
||||
proxy_pass http://localhost:3456;
|
||||
client_max_body_size 20M;
|
||||
}
|
||||
}
|
||||
{{< /highlight >}}
|
||||
|
||||
<div class="notification is-warning">
|
||||
<b>NOTE:</b> If you change the max upload size in Vikunja's settings, you'll need to also change the <code>client_max_body_size</code> in the nginx proxy config.
|
||||
</div>
|
||||
|
||||
## Apache
|
||||
|
||||
Put the following config in `cat /etc/apache2/sites-available/vikunja.conf`:
|
||||
@@ -82,14 +94,18 @@ Put the following config in `cat /etc/apache2/sites-available/vikunja.conf`:
|
||||
</Proxy>
|
||||
ProxyPass /api http://localhost:3456/api
|
||||
ProxyPassReverse /api http://localhost:3456/api
|
||||
ProxyPass /dav http://localhost:3456/dav
|
||||
ProxyPassReverse /dav http://localhost:3456/dav
|
||||
ProxyPass /.well-known http://localhost:3456/.well-known
|
||||
ProxyPassReverse /.well-known http://localhost:3456/.well-known
|
||||
|
||||
DocumentRoot /var/www/html
|
||||
RewriteEngine On
|
||||
RewriteRule ^\/?(config\.json|favicon\.ico|css|fonts|images|img|js|api) - [L]
|
||||
RewriteRule ^\/?(config\.json|favicon\.ico|css|fonts|images|img|js|api|dav|\.well-known) - [L]
|
||||
RewriteRule ^(.*)$ /index.html [QSA,L]
|
||||
</VirtualHost>
|
||||
{{< /highlight >}}
|
||||
|
||||
**Note:** The apache modules `proxy` and `proxy_http` must be enabled for this.
|
||||
**Note:** The apache modules `proxy`, `proxy_http` and `rewrite` must be enabled for this.
|
||||
|
||||
For more details see the [frontend apache configuration]({{< ref "install-frontend.md#apache">}}).
|
||||
108
docs/content/doc/setup/utf-8.md
Normal file
108
docs/content/doc/setup/utf-8.md
Normal file
@@ -0,0 +1,108 @@
|
||||
---
|
||||
date: "2020-07-06:00:00+02:00"
|
||||
title: "UTF-8 Settings"
|
||||
draft: false
|
||||
type: "doc"
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "setup"
|
||||
---
|
||||
|
||||
# UTF-8 Settings
|
||||
|
||||
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.
|
||||
|
||||
On this page, you will find information about how to fully ensure non-latin characters like aüäß or emojis work
|
||||
with your installation.
|
||||
|
||||
{{< table_of_contents >}}
|
||||
|
||||
## Postgresql & SQLite
|
||||
|
||||
Postgresql and SQLite should handle utf-8 just fine - If you discover any issues nonetheless, please
|
||||
[drop us a message](https://vikunja.io/contact/).
|
||||
|
||||
## MySQL
|
||||
|
||||
MySQL is not able to handle utf-8 by default.
|
||||
To fix this, follow the steps below.
|
||||
|
||||
To find out if your db supports utf-8, run the following in a shell or similar, assuming the database
|
||||
you're using for vikunja is called `vikunja`:
|
||||
|
||||
{{< highlight sql >}}
|
||||
SELECT default_character_set_name FROM information_schema.SCHEMATA WHERE schema_name = 'vikunja';
|
||||
{{< /highlight >}}
|
||||
|
||||
This will get you a result like the following:
|
||||
|
||||
```
|
||||
+----------------------------+
|
||||
| default_character_set_name |
|
||||
+----------------------------+
|
||||
| latin1 |
|
||||
+----------------------------+
|
||||
1 row in set (0.001 sec)
|
||||
```
|
||||
|
||||
The charset `latin1` means the db is encoded in the `latin1` encoding which does not support utf-8 characters.
|
||||
|
||||
(The following guide is based on [this thread from stackoverflow](https://dba.stackexchange.com/a/104866))
|
||||
|
||||
### 0. Backup your database
|
||||
|
||||
Before attempting any conversion, please [back up your database]({{< ref "backups.md">}}).
|
||||
|
||||
### 1. Create a pre-conversion script
|
||||
|
||||
Copy the following sql statements in a file called `preAlterTables.sql` and replace all occurences of `vikunja` with
|
||||
the name of your database:
|
||||
|
||||
{{< highlight sql >}}
|
||||
use information_schema;
|
||||
SELECT concat("ALTER DATABASE `",table_schema,"` CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;") as _sql
|
||||
FROM `TABLES` where table_schema like 'vikunja' and TABLE_TYPE='BASE TABLE' group by table_schema;
|
||||
SELECT concat("ALTER TABLE `",table_schema,"`.`",table_name,"` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;") as _sql
|
||||
FROM `TABLES` where table_schema like 'vikunja' and TABLE_TYPE='BASE TABLE' group by table_schema, table_name;
|
||||
SELECT concat("ALTER TABLE `",table_schema,"`.`",table_name, "` CHANGE `",column_name,"` `",column_name,"` ",data_type,"(",character_maximum_length,") CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci",IF(is_nullable="YES"," NULL"," NOT NULL"),";") as _sql
|
||||
FROM `COLUMNS` where table_schema like 'vikunja' and data_type in ('varchar','char');
|
||||
SELECT concat("ALTER TABLE `",table_schema,"`.`",table_name, "` CHANGE `",column_name,"` `",column_name,"` ",data_type," CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci",IF(is_nullable="YES"," NULL"," NOT NULL"),";") as _sql
|
||||
FROM `COLUMNS` where table_schema like 'vikunja' and data_type in ('text','tinytext','mediumtext','longtext');
|
||||
{{< /highlight >}}
|
||||
|
||||
### 2. Run the pre-conversion script
|
||||
|
||||
Running this will create the actual migration script for your particular database structure and save it in a file called `alterTables.sql`:
|
||||
|
||||
{{< highlight bash >}}
|
||||
mysql -uroot < preAlterTables.sql | egrep '^ALTER' > alterTables.sql
|
||||
{{< /highlight >}}
|
||||
|
||||
### 3. Convert the database
|
||||
|
||||
At this point converting is just a matter of executing the previously generated sql script:
|
||||
|
||||
{{< highlight bash >}}
|
||||
mysql -uroot < alterTables.sql
|
||||
{{< /highlight >}}
|
||||
|
||||
### 4. Verify it was successfully converted
|
||||
|
||||
If everything worked as intended, your db collation should now look like this:
|
||||
|
||||
{{< highlight sql >}}
|
||||
SELECT default_character_set_name FROM information_schema.SCHEMATA WHERE schema_name = 'vikunja';
|
||||
{{< /highlight >}}
|
||||
|
||||
Should get you:
|
||||
|
||||
```
|
||||
+----------------------------+
|
||||
| default_character_set_name |
|
||||
+----------------------------+
|
||||
| utf8mb4 |
|
||||
+----------------------------+
|
||||
1 row in set (0.001 sec)
|
||||
```
|
||||
@@ -16,6 +16,8 @@ menu:
|
||||
|
||||
Vikunja supports managing tasks via the [caldav VTODO](https://tools.ietf.org/html/rfc5545#section-3.6.2) extension.
|
||||
|
||||
{{< table_of_contents >}}
|
||||
|
||||
## URLs
|
||||
|
||||
All urls are located under the `/dav` subspace.
|
||||
@@ -64,14 +66,16 @@ Vikunja **currently does not** support these properties:
|
||||
|
||||
## Tested Clients
|
||||
|
||||
#### Working
|
||||
### Working
|
||||
|
||||
* [Evolution](https://wiki.gnome.org/Apps/Evolution/)
|
||||
|
||||
#### Not working
|
||||
|
||||
* [OpenTasks](https://opentasks.app/) + [DAVx⁵](https://www.davx5.com/)
|
||||
* [Tasks (Android)](https://tasks.org/)
|
||||
|
||||
### Not working
|
||||
|
||||
* [Thunderbird (68)](https://www.thunderbird.net/)
|
||||
|
||||
## Dev logs
|
||||
|
||||
The whole thing is not optimized at all and probably pretty inefficent.
|
||||
|
||||
@@ -13,8 +13,12 @@ menu:
|
||||
You can interact with Vikunja using its `cli` interface.
|
||||
The following commands are available:
|
||||
|
||||
* [dump](#dump)
|
||||
* [help](#help)
|
||||
* [migrate](#migrate)
|
||||
* [restore](#restore)
|
||||
* [testmail](#testmail)
|
||||
* [user](#user)
|
||||
* [version](#version)
|
||||
* [web](#web)
|
||||
|
||||
@@ -22,6 +26,16 @@ 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">}}).
|
||||
|
||||
### `dump`
|
||||
|
||||
Creates a zip file with all vikunja-related files.
|
||||
This includes config, version, all files and the full database.
|
||||
|
||||
Usage:
|
||||
{{< highlight bash >}}
|
||||
$ vikunja dump
|
||||
{{< /highlight >}}
|
||||
|
||||
### `help`
|
||||
|
||||
Shows more detailed help about any command.
|
||||
@@ -63,6 +77,91 @@ $ vikunja migrate rollback [flags]
|
||||
Flags:
|
||||
* `-n`, `--name` string: The id of the migration you want to roll back until.
|
||||
|
||||
### `restore`
|
||||
|
||||
Restores a previously created dump from a zip file, see `dump`.
|
||||
|
||||
Usage:
|
||||
{{< highlight bash >}}
|
||||
$ vikunja restore <path to dump zip file>
|
||||
{{< /highlight >}}
|
||||
|
||||
### `testmail`
|
||||
|
||||
Sends a test mail using the configured smtp connection.
|
||||
|
||||
Usage:
|
||||
{{< highlight bash >}}
|
||||
$ vikunja testmail <email to send the test mail to>
|
||||
{{< /highlight >}}
|
||||
|
||||
### `user`
|
||||
|
||||
Bundles a few commands to manage users.
|
||||
|
||||
#### `user change-status`
|
||||
|
||||
Enable or disable a user. Will toggle the current status if no flag (`--enable` or `--disable`) is provided.
|
||||
|
||||
Usage:
|
||||
{{< highlight bash >}}
|
||||
$ vikunja user change-status <user id> <flags>
|
||||
{{< /highlight >}}
|
||||
|
||||
Flags:
|
||||
* `-d`, `--disable`: Disable the user.
|
||||
* `-e`, `--enable`: Enable the user.
|
||||
|
||||
#### `user create`
|
||||
|
||||
Create a new user.
|
||||
|
||||
Usage:
|
||||
{{< highlight bash >}}
|
||||
$ vikunja user create <flags>
|
||||
{{< /highlight >}}
|
||||
|
||||
Flags:
|
||||
* `-a`, `--avatar-provider`: The avatar provider of the new user. Optional.
|
||||
* `-e`, `--email`: The email address of the new user.
|
||||
* `-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 list`
|
||||
|
||||
Shows a list of all users.
|
||||
|
||||
Usage:
|
||||
{{< highlight bash >}}
|
||||
$ vikunja user list
|
||||
{{< /highlight >}}
|
||||
|
||||
#### `user reset-password`
|
||||
|
||||
Reset a users password, either through mailing them a reset link or directly.
|
||||
|
||||
Usage:
|
||||
{{< highlight bash >}}
|
||||
$ vikunja user reset-password <flags>
|
||||
{{< /highlight >}}
|
||||
|
||||
Flags:
|
||||
* `-d`, `--direct`: If provided, reset the password directly instead of sending the user a reset mail.
|
||||
* `-p`, `--password`: The new password of the user. Only used in combination with --direct. You will be asked to enter it if not provided through the flag.
|
||||
|
||||
#### `user update`
|
||||
|
||||
Update an existing user.
|
||||
|
||||
Usage:
|
||||
{{< highlight bash >}}
|
||||
$ vikunja user update <user id>
|
||||
{{< /highlight >}}
|
||||
|
||||
Flags:
|
||||
* `-a`, `--avatar-provider`: The new avatar provider of the new user.
|
||||
* `-e`, `--email`: The new email address of the user.
|
||||
* `-u`, `--username`: The new username of the user.
|
||||
|
||||
### `version`
|
||||
|
||||
@@ -81,4 +180,4 @@ Starts Vikunja's REST api server.
|
||||
Usage:
|
||||
{{< highlight bash >}}
|
||||
$ vikunja web
|
||||
{{< /highlight >}}
|
||||
{{< /highlight >}}
|
||||
|
||||
@@ -12,9 +12,18 @@ menu:
|
||||
|
||||
This document describes the different errors Vikunja can return.
|
||||
|
||||
{{< table_of_contents >}}
|
||||
|
||||
## Generic
|
||||
|
||||
| ErrorCode | HTTP Status Code | Description |
|
||||
|-----------|------------------|-------------|
|
||||
| 0001 | 403 | Generic forbidden error. |
|
||||
|
||||
## User
|
||||
|
||||
| ErrorCode | HTTP Status Code | Description |
|
||||
|-----------|------------------|-------------|
|
||||
| 1001 | 400 | A user with this username already exists. |
|
||||
| 1002 | 400 | A user with this email address already exists. |
|
||||
| 1004 | 400 | No username and password specified. |
|
||||
@@ -27,13 +36,33 @@ This document describes the different errors Vikunja can return.
|
||||
| 1012 | 412 | Email address of the user not confirmed. |
|
||||
| 1013 | 412 | New password is empty. |
|
||||
| 1014 | 412 | Old password is empty. |
|
||||
| 1015 | 412 | Totp is already enabled for this user. |
|
||||
| 1016 | 412 | Totp is not enabled for this user. |
|
||||
| 1017 | 412 | The provided Totp passcode is invalid. |
|
||||
| 1018 | 412 | The provided user avatar provider type setting is invalid. |
|
||||
|
||||
## Validation
|
||||
|
||||
| ErrorCode | HTTP Status Code | Description |
|
||||
|-----------|------------------|-------------|
|
||||
| 2001 | 400 | ID cannot be empty or 0. |
|
||||
| 2002 | 400 | Some of the request data was invalid. The response contains an aditional array with all invalid fields. |
|
||||
|
||||
## List
|
||||
|
||||
| ErrorCode | HTTP Status Code | Description |
|
||||
|-----------|------------------|-------------|
|
||||
| 3001 | 404 | The list does not exist. |
|
||||
| 3004 | 403 | The user needs to have read permissions on that list to perform that action. |
|
||||
| 3005 | 400 | The list title cannot be empty. |
|
||||
| 3006 | 404 | The list share does not exist. |
|
||||
| 3007 | 400 | A list with this identifier already exists. |
|
||||
| 3008 | 412 | The list is archived and can therefore only be accessed read only. This is also true for all tasks associated with this list. |
|
||||
|
||||
## Task
|
||||
|
||||
| ErrorCode | HTTP Status Code | Description |
|
||||
|-----------|------------------|-------------|
|
||||
| 4001 | 400 | The list task text cannot be empty. |
|
||||
| 4002 | 404 | The list task does not exist. |
|
||||
| 4003 | 403 | All bulk editing tasks must belong to the same list. |
|
||||
@@ -49,21 +78,67 @@ 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. |
|
||||
|
||||
## Namespace
|
||||
|
||||
| ErrorCode | HTTP Status Code | Description |
|
||||
|-----------|------------------|-------------|
|
||||
| 5001 | 404 | The namspace does not exist. |
|
||||
| 5003 | 403 | The user does not have access to the specified namespace. |
|
||||
| 5006 | 400 | The namespace name cannot be empty. |
|
||||
| 5009 | 403 | The user needs to have namespace read access to perform that action. |
|
||||
| 5010 | 403 | This team does not have access to that namespace. |
|
||||
| 5011 | 409 | This user has already access to that namespace. |
|
||||
| 5012 | 412 | The namespace is archived and can therefore only be accessed read only. |
|
||||
|
||||
## Team
|
||||
|
||||
| ErrorCode | HTTP Status Code | Description |
|
||||
|-----------|------------------|-------------|
|
||||
| 6001 | 400 | The team name cannot be emtpy. |
|
||||
| 6002 | 404 | The team does not exist. |
|
||||
| 6004 | 409 | The team already has access to that namespace or list. |
|
||||
| 6005 | 409 | The user is already a member of that team. |
|
||||
| 6006 | 400 | Cannot delete the last team member. |
|
||||
| 6007 | 403 | The team does not have access to the list to perform that action. |
|
||||
|
||||
## User List Access
|
||||
|
||||
| ErrorCode | HTTP Status Code | Description |
|
||||
|-----------|------------------|-------------|
|
||||
| 7002 | 409 | The user already has access to that list. |
|
||||
| 7003 | 403 | The user does not have access to that list. |
|
||||
|
||||
## Label
|
||||
|
||||
| ErrorCode | HTTP Status Code | Description |
|
||||
|-----------|------------------|-------------|
|
||||
| 8001 | 403 | This label already exists on that task. |
|
||||
| 8002 | 404 | The label does not exist. |
|
||||
| 8003 | 403 | The user does not have access to this label. |
|
||||
| 9001 | 403 | The right is invalid. |
|
||||
|
||||
## Right
|
||||
|
||||
| ErrorCode | HTTP Status Code | Description |
|
||||
|-----------|------------------|-------------|
|
||||
| 9001 | 403 | The right is invalid. |
|
||||
|
||||
## Kanban
|
||||
|
||||
| ErrorCode | HTTP Status Code | Description |
|
||||
|-----------|------------------|-------------|
|
||||
| 10001 | 404 | The bucket does not exist. |
|
||||
| 10002 | 400 | The bucket does not belong to that list. |
|
||||
| 10003 | 412 | You cannot remove the last bucket on a list. |
|
||||
| 10004 | 412 | You cannot add the task to this bucket as it already exceeded the limit of tasks it can hold. |
|
||||
|
||||
## Saved Filters
|
||||
|
||||
| ErrorCode | HTTP Status Code | Description |
|
||||
|-----------|------------------|-------------|
|
||||
| 11001 | 404 | The saved filter does not exist. |
|
||||
| 11002 | 412 | Saved filters are not available for link shares. |
|
||||
|
||||
@@ -23,7 +23,7 @@ The following values are possible:
|
||||
| 1 | Read and write. Namespaces or lists 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
|
||||
## Team admins
|
||||
|
||||
When adding or querying a team, every member has an additional boolean value stating if it is admin or not.
|
||||
A team admin can also add and remove team members and also change whether a user in the team is admin or not.
|
||||
2
docs/themes/vikunja
vendored
2
docs/themes/vikunja
vendored
Submodule docs/themes/vikunja updated: c0d3eccb16...958219fc84
124
go.mod
124
go.mod
@@ -17,72 +17,94 @@
|
||||
module code.vikunja.io/api
|
||||
|
||||
require (
|
||||
code.vikunja.io/web v0.0.0-20200208214421-c90649369427
|
||||
gitea.com/xorm/tests v0.5.6 // indirect
|
||||
gitea.com/xorm/xorm-redis-cache v0.0.0-20191113062523-5a6a9e2ab9f2
|
||||
4d63.com/tz v1.2.0
|
||||
code.vikunja.io/web v0.0.0-20201223143420-588abb73703a
|
||||
dmitri.shuralyov.com/go/generated v0.0.0-20170818220700-b1254a446363 // indirect
|
||||
gitea.com/xorm/xorm-redis-cache v0.2.0
|
||||
github.com/adlio/trello v1.8.0
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a
|
||||
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef
|
||||
github.com/beevik/etree v1.1.0 // indirect
|
||||
github.com/c2h5oh/datasize v0.0.0-20171227191756-4eba002a5eae
|
||||
github.com/c2h5oh/datasize v0.0.0-20200825124411-48ed595a09d2
|
||||
github.com/client9/misspell v0.3.4
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
|
||||
github.com/cweill/gotests v1.5.3
|
||||
github.com/d4l3k/messagediff v1.2.1 // indirect
|
||||
github.com/coreos/go-oidc v2.2.1+incompatible
|
||||
github.com/cweill/gotests v1.6.0
|
||||
github.com/d4l3k/messagediff v1.2.1
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
github.com/fzipp/gocyclo v0.0.0-20150627053110-6acd4345c835
|
||||
github.com/garyburd/redigo v1.6.0 // indirect
|
||||
github.com/go-openapi/jsonreference v0.19.3 // indirect
|
||||
github.com/go-openapi/spec v0.19.4 // indirect
|
||||
github.com/go-redis/redis v6.15.2+incompatible
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/dustinkirkland/golang-petname v0.0.0-20191129215211-8e5a1ed0cff0
|
||||
github.com/fzipp/gocyclo v0.3.1
|
||||
github.com/gabriel-vasile/mimetype v1.1.2
|
||||
github.com/getsentry/sentry-go v0.9.0
|
||||
github.com/go-errors/errors v1.1.1
|
||||
github.com/go-redis/redis/v8 v8.4.8
|
||||
github.com/go-sql-driver/mysql v1.5.0
|
||||
github.com/go-testfixtures/testfixtures/v3 v3.1.1
|
||||
github.com/go-xorm/core v0.6.2 // indirect
|
||||
github.com/go-xorm/xorm v0.7.9 // indirect
|
||||
github.com/golang/protobuf v1.3.2 // indirect
|
||||
github.com/gordonklaus/ineffassign v0.0.0-20180909121442-1003c8bd00dc
|
||||
github.com/imdario/mergo v0.3.7
|
||||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/jgautheron/goconst v0.0.0-20170703170152-9740945f5dcb
|
||||
github.com/labstack/echo/v4 v4.1.14
|
||||
github.com/go-testfixtures/testfixtures/v3 v3.4.1
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
|
||||
github.com/golang/snappy v0.0.2 // indirect
|
||||
github.com/gordonklaus/ineffassign v0.0.0-20210104184537-8eed68eb605f
|
||||
github.com/iancoleman/strcase v0.1.2
|
||||
github.com/imdario/mergo v0.3.11
|
||||
github.com/jgautheron/goconst v0.0.0-20201117150253-ccae5bf973f3
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/labstack/echo/v4 v4.1.17
|
||||
github.com/labstack/gommon v0.3.0
|
||||
github.com/laurent22/ical-go v0.1.1-0.20181107184520-7e5d6ade8eef
|
||||
github.com/lib/pq v1.3.0
|
||||
github.com/mailru/easyjson v0.7.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.12 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.4 // indirect
|
||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826
|
||||
github.com/olekukonko/tablewriter v0.0.1
|
||||
github.com/lib/pq v1.9.0
|
||||
github.com/magefile/mage v1.11.0
|
||||
github.com/mailru/easyjson v0.7.6 // indirect
|
||||
github.com/mattn/go-colorable v0.1.8 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.6
|
||||
github.com/mitchellh/mapstructure v1.3.2 // indirect
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.4
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||
github.com/pelletier/go-toml v1.4.0 // indirect
|
||||
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829
|
||||
github.com/pelletier/go-toml v1.8.0 // indirect
|
||||
github.com/pquerna/cachecontrol v0.0.0-20200921180117-858c6e7e6b7e // indirect
|
||||
github.com/pquerna/otp v1.3.0
|
||||
github.com/prometheus/client_golang v1.9.0
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/samedi/caldav-go v3.0.0+incompatible
|
||||
github.com/shurcooL/httpfs v0.0.0-20190527155220-6a4d4a70508b
|
||||
github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd
|
||||
github.com/spf13/afero v1.2.2
|
||||
github.com/spf13/cobra v0.0.3
|
||||
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749
|
||||
github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546
|
||||
github.com/spf13/afero v1.5.1
|
||||
github.com/spf13/cast v1.3.1 // indirect
|
||||
github.com/spf13/cobra v1.1.1
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/viper v1.3.2
|
||||
github.com/stretchr/testify v1.4.0
|
||||
github.com/swaggo/swag v1.6.3
|
||||
github.com/ulule/limiter/v3 v3.3.0
|
||||
github.com/urfave/cli v1.22.2 // indirect
|
||||
golang.org/x/crypto v0.0.0-20200208060501-ecb85df21340
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 // indirect
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 // indirect
|
||||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d // indirect
|
||||
github.com/spf13/viper v1.7.1
|
||||
github.com/stretchr/testify v1.6.1
|
||||
github.com/swaggo/swag v1.7.0
|
||||
github.com/ulule/limiter/v3 v3.8.0
|
||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
|
||||
golang.org/x/image v0.0.0-20201208152932-35266b937fa6
|
||||
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5
|
||||
golang.org/x/net v0.0.0-20201216054612-986b41b23924 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a
|
||||
golang.org/x/sys v0.0.0-20201223074533-0d417f636930 // indirect
|
||||
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
|
||||
gopkg.in/d4l3k/messagediff.v1 v1.2.1
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a
|
||||
src.techknowlogick.com/xgo v0.0.0-20190507142556-a5b29ecb0ff4
|
||||
src.techknowlogick.com/xormigrate v1.1.0
|
||||
xorm.io/builder v0.3.6
|
||||
gopkg.in/ini.v1 v1.57.0 // indirect
|
||||
gopkg.in/square/go-jose.v2 v2.5.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c
|
||||
honnef.co/go/tools v0.0.1-2020.1.5
|
||||
src.techknowlogick.com/xgo v1.2.1-0.20201205054505-b97762e7a76b
|
||||
src.techknowlogick.com/xormigrate v1.4.0
|
||||
xorm.io/builder v0.3.7
|
||||
xorm.io/core v0.7.3
|
||||
xorm.io/xorm v0.8.1
|
||||
xorm.io/xorm v1.0.5
|
||||
)
|
||||
|
||||
replace 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
|
||||
replace (
|
||||
github.com/adlio/trello => github.com/kolaente/trello v1.7.1-0.20201216234312-5c4ef79b531e
|
||||
github.com/coreos/bbolt => go.etcd.io/bbolt v1.3.4
|
||||
github.com/coreos/go-systemd => github.com/coreos/go-systemd/v22 v22.0.0
|
||||
github.com/hpcloud/tail => github.com/jeffbean/tail v1.0.1 // See https://github.com/hpcloud/tail/pull/159
|
||||
github.com/samedi/caldav-go => github.com/kolaente/caldav-go v3.0.1-0.20190524174923-9e5cd1688227+incompatible // Branch: feature/dynamic-supported-components, PR: https://github.com/samedi/caldav-go/pull/6 and https://github.com/samedi/caldav-go/pull/7
|
||||
gopkg.in/fsnotify.v1 => github.com/kolaente/fsnotify v1.4.10-0.20200411160148-1bc3c8ff4048 // See https://github.com/fsnotify/fsnotify/issues/328 and https://github.com/golang/go/issues/26904
|
||||
)
|
||||
|
||||
go 1.13
|
||||
|
||||
927
magefile.go
Normal file
927
magefile.go
Normal file
@@ -0,0 +1,927 @@
|
||||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU 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/>.
|
||||
|
||||
// +build mage
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/magefile/mage/mg"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
const (
|
||||
PACKAGE = `code.vikunja.io/api`
|
||||
DIST = `dist`
|
||||
)
|
||||
|
||||
var (
|
||||
Goflags = []string{
|
||||
"-v",
|
||||
}
|
||||
Executable = "vikunja"
|
||||
Ldflags = ""
|
||||
Tags = ""
|
||||
VersionNumber = "dev"
|
||||
Version = "master" // This holds the built version, master by default, when building from a tag or release branch, their name
|
||||
BinLocation = ""
|
||||
PkgVersion = "master"
|
||||
ApiPackages = []string{}
|
||||
RootPath = ""
|
||||
GoFiles = []string{}
|
||||
|
||||
// Aliases are mage aliases of targets
|
||||
Aliases = map[string]interface{}{
|
||||
"build": Build.Build,
|
||||
"do-the-swag": DoTheSwag,
|
||||
"check:got-swag": Check.GotSwag,
|
||||
"release:os-package": Release.OsPackage,
|
||||
"dev:create-migration": Dev.CreateMigration,
|
||||
"generate-docs": GenerateDocs,
|
||||
"check:golangci-fix": Check.GolangciFix,
|
||||
}
|
||||
)
|
||||
|
||||
func setVersion() {
|
||||
versionCmd := exec.Command("git", "describe", "--tags", "--always", "--abbrev=10")
|
||||
version, err := versionCmd.Output()
|
||||
if err != nil {
|
||||
fmt.Printf("Error getting version: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
VersionNumber = strings.Trim(string(version), "\n")
|
||||
VersionNumber = strings.Replace(VersionNumber, "-", "+", 1)
|
||||
VersionNumber = strings.Replace(VersionNumber, "-g", "-", 1)
|
||||
|
||||
if os.Getenv("DRONE_TAG") != "" {
|
||||
Version = os.Getenv("DRONE_TAG")
|
||||
} else if os.Getenv("DRONE_BRANCH") != "" {
|
||||
Version = strings.Replace(os.Getenv("DRONE_BRANCH"), "release/v", "", 1)
|
||||
}
|
||||
}
|
||||
|
||||
func setBinLocation() {
|
||||
if os.Getenv("DRONE_WORKSPACE") != "" {
|
||||
BinLocation = DIST + `/binaries/` + Executable + `-` + Version + `-linux-amd64`
|
||||
} else {
|
||||
BinLocation = Executable
|
||||
}
|
||||
}
|
||||
|
||||
func setPkgVersion() {
|
||||
if Version == "master" {
|
||||
PkgVersion = VersionNumber
|
||||
}
|
||||
}
|
||||
|
||||
func setExecutable() {
|
||||
if runtime.GOOS == "windows" {
|
||||
Executable += ".exe"
|
||||
}
|
||||
}
|
||||
|
||||
func setApiPackages() {
|
||||
cmd := exec.Command("go", "list", "all")
|
||||
pkgs, err := cmd.Output()
|
||||
if err != nil {
|
||||
fmt.Printf("Error getting packages: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
for _, p := range strings.Split(string(pkgs), "\n") {
|
||||
if strings.Contains(p, "code.vikunja.io/api") && !strings.Contains(p, "code.vikunja.io/api/pkg/integrations") {
|
||||
ApiPackages = append(ApiPackages, p)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func setRootPath() {
|
||||
pwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
fmt.Printf("Error getting pwd: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if err := os.Setenv("VIKUNJA_SERVICE_ROOTPATH", pwd); err != nil {
|
||||
fmt.Printf("Error setting root path: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
RootPath = pwd
|
||||
}
|
||||
|
||||
func setGoFiles() {
|
||||
// GOFILES := $(shell find . -name "*.go" -type f ! -path "*/bindata.go")
|
||||
cmd := exec.Command("find", ".", "-name", "*.go", "-type", "f", "!", "-path", "*/bindata.go")
|
||||
files, err := cmd.Output()
|
||||
if err != nil {
|
||||
fmt.Printf("Error getting go files: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
for _, f := range strings.Split(string(files), "\n") {
|
||||
if strings.HasSuffix(f, ".go") {
|
||||
GoFiles = append(GoFiles, RootPath+strings.TrimLeft(f, "."))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Some variables can always get initialized, so we do just that.
|
||||
func init() {
|
||||
setExecutable()
|
||||
setRootPath()
|
||||
}
|
||||
|
||||
// Some variables have external dependencies (like git) which may not always be available.
|
||||
func initVars() {
|
||||
Tags = os.Getenv("TAGS")
|
||||
setVersion()
|
||||
setBinLocation()
|
||||
setPkgVersion()
|
||||
setApiPackages()
|
||||
setGoFiles()
|
||||
Ldflags = `-X "` + PACKAGE + `/pkg/version.Version=` + VersionNumber + `" -X "main.Tags=` + Tags + `"`
|
||||
}
|
||||
|
||||
func runAndStreamOutput(cmd string, args ...string) {
|
||||
c := exec.Command(cmd, args...)
|
||||
|
||||
c.Env = os.Environ()
|
||||
c.Dir = RootPath
|
||||
|
||||
fmt.Printf("%s\n\n", c.String())
|
||||
|
||||
stdout, _ := c.StdoutPipe()
|
||||
errbuf := bytes.Buffer{}
|
||||
c.Stderr = &errbuf
|
||||
c.Start()
|
||||
|
||||
reader := bufio.NewReader(stdout)
|
||||
line, err := reader.ReadString('\n')
|
||||
for err == nil {
|
||||
fmt.Print(line)
|
||||
line, err = reader.ReadString('\n')
|
||||
}
|
||||
|
||||
if err := c.Wait(); err != nil {
|
||||
fmt.Printf(errbuf.String())
|
||||
fmt.Printf("Error: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// Will check if the tool exists and if not install it from the provided import path
|
||||
// If any errors occur, it will exit with a status code of 1.
|
||||
func checkAndInstallGoTool(tool, importPath string) {
|
||||
if err := exec.Command(tool).Run(); err != nil && strings.Contains(err.Error(), "executable file not found") {
|
||||
fmt.Printf("%s not installed, installing %s...\n", tool, importPath)
|
||||
if err := exec.Command("go", "install", Goflags[0], importPath).Run(); err != nil {
|
||||
fmt.Printf("Error installing %s\n", tool)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println("Installed.")
|
||||
}
|
||||
}
|
||||
|
||||
// Calculates a hash of a file
|
||||
func calculateSha256FileHash(path string) (hash string, err error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
h := sha256.New()
|
||||
if _, err := io.Copy(h, f); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%x", h.Sum(nil)), nil
|
||||
}
|
||||
|
||||
// Copy the src file to dst. Any existing file will be overwritten and will not
|
||||
// copy file attributes.
|
||||
func copyFile(src, dst string) error {
|
||||
in, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer in.Close()
|
||||
|
||||
out, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
_, err = io.Copy(out, in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
si, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.Chmod(dst, si.Mode()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return out.Close()
|
||||
}
|
||||
|
||||
// os.Rename has issues with moving files between docker volumes.
|
||||
// Because of this limitaion, it fails in drone.
|
||||
// Source: https://gist.github.com/var23rav/23ae5d0d4d830aff886c3c970b8f6c6b
|
||||
func moveFile(src, dst string) error {
|
||||
inputFile, err := os.Open(src)
|
||||
defer inputFile.Close()
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't open source file: %s", err)
|
||||
}
|
||||
|
||||
outputFile, err := os.Create(dst)
|
||||
defer outputFile.Close()
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't open dest file: %s", err)
|
||||
}
|
||||
|
||||
_, err = io.Copy(outputFile, inputFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("writing to output file failed: %s", err)
|
||||
}
|
||||
|
||||
// Make sure to copy copy the permissions of the original file as well
|
||||
si, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.Chmod(dst, si.Mode()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// The copy was successful, so now delete the original file
|
||||
err = os.Remove(src)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed removing original file: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Formats the code using go fmt
|
||||
func Fmt() {
|
||||
mg.Deps(initVars)
|
||||
args := append([]string{"-s", "-w"}, GoFiles...)
|
||||
runAndStreamOutput("gofmt", args...)
|
||||
}
|
||||
|
||||
// Generates the swagger docs from the code annotations
|
||||
func DoTheSwag() {
|
||||
mg.Deps(initVars)
|
||||
checkAndInstallGoTool("swag", "github.com/swaggo/swag/cmd/swag")
|
||||
runAndStreamOutput("swag", "init", "-g", "./pkg/routes/routes.go", "--parseDependency", "-d", RootPath, "-o", RootPath+"/pkg/swagger")
|
||||
}
|
||||
|
||||
type Test mg.Namespace
|
||||
|
||||
// Runs all tests except integration tests
|
||||
func (Test) Unit() {
|
||||
mg.Deps(initVars)
|
||||
// We run everything sequentially and not in parallel to prevent issues with real test databases
|
||||
args := append([]string{"test", Goflags[0], "-p", "1"}, ApiPackages...)
|
||||
runAndStreamOutput("go", args...)
|
||||
}
|
||||
|
||||
// Runs the tests and builds the coverage html file from coverage output
|
||||
func (Test) Coverage() {
|
||||
mg.Deps(initVars)
|
||||
mg.Deps(Test.Unit)
|
||||
runAndStreamOutput("go", "tool", "cover", "-html=cover.out", "-o", "cover.html")
|
||||
}
|
||||
|
||||
// Runs the integration tests
|
||||
func (Test) Integration() {
|
||||
mg.Deps(initVars)
|
||||
// We run everything sequentially and not in parallel to prevent issues with real test databases
|
||||
runAndStreamOutput("go", "test", Goflags[0], "-p", "1", PACKAGE+"/pkg/integrations")
|
||||
}
|
||||
|
||||
type Check mg.Namespace
|
||||
|
||||
// Checks if the swagger docs need to be re-generated from the code annotations
|
||||
func (Check) GotSwag() {
|
||||
mg.Deps(initVars)
|
||||
// The check is pretty cheaply done: We take the hash of the swagger.json file, generate the docs,
|
||||
// hash the file again and compare the two hashes to see if anything changed. If that's the case,
|
||||
// regenerating the docs is necessary.
|
||||
// swag is not capable of just outputting the generated docs to stdout, therefore we need to do it this way.
|
||||
// Another drawback of this is obviously it will only work once - we're not resetting the newly generated
|
||||
// docs after the check. This behaviour is good enough for ci though.
|
||||
oldHash, err := calculateSha256FileHash(RootPath + "/pkg/swagger/swagger.json")
|
||||
if err != nil {
|
||||
fmt.Printf("Error getting old hash of the swagger docs: %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
DoTheSwag()
|
||||
|
||||
newHash, err := calculateSha256FileHash(RootPath + "/pkg/swagger/swagger.json")
|
||||
if err != nil {
|
||||
fmt.Printf("Error getting new hash of the swagger docs: %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if oldHash != newHash {
|
||||
fmt.Println("Swagger docs are not up to date.")
|
||||
fmt.Println("Please run 'mage do-the-swag' and commit the result.")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func checkGolangCiLintInstalled() {
|
||||
mg.Deps(initVars)
|
||||
if err := exec.Command("golangci-lint").Run(); err != nil && strings.Contains(err.Error(), "executable file not found") {
|
||||
fmt.Println("Please manually install golangci-lint by running")
|
||||
fmt.Println("curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.31.0")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func (Check) Golangci() {
|
||||
checkGolangCiLintInstalled()
|
||||
runAndStreamOutput("golangci-lint", "run")
|
||||
}
|
||||
|
||||
func (Check) GolangciFix() {
|
||||
checkGolangCiLintInstalled()
|
||||
runAndStreamOutput("golangci-lint", "run", "--fix")
|
||||
}
|
||||
|
||||
// Runs fmt-check, lint, got-swag, misspell-check, ineffasign-check, gocyclo-check, static-check, gosec-check, goconst-check all in parallel
|
||||
func (Check) All() {
|
||||
mg.Deps(initVars)
|
||||
mg.Deps(
|
||||
Check.Golangci,
|
||||
Check.GotSwag,
|
||||
)
|
||||
}
|
||||
|
||||
type Build mg.Namespace
|
||||
|
||||
// Cleans all build, executable and bindata files
|
||||
func (Build) Clean() error {
|
||||
mg.Deps(initVars)
|
||||
if err := exec.Command("go", "clean", "./...").Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.Remove(Executable); err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
if err := os.RemoveAll(DIST); err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
if err := os.RemoveAll(BinLocation); err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Generates static content into the final binary
|
||||
func (Build) Generate() {
|
||||
mg.Deps(initVars)
|
||||
runAndStreamOutput("go", "generate", PACKAGE+"/pkg/static")
|
||||
}
|
||||
|
||||
// Builds a vikunja binary, ready to run
|
||||
func (Build) Build() {
|
||||
mg.Deps(initVars)
|
||||
mg.Deps(Build.Generate)
|
||||
runAndStreamOutput("go", "build", Goflags[0], "-tags", Tags, "-ldflags", "-s -w "+Ldflags, "-o", Executable)
|
||||
}
|
||||
|
||||
type Release mg.Namespace
|
||||
|
||||
// Runs all steps in the right order to create release packages for various platforms
|
||||
func (Release) Release(ctx context.Context) error {
|
||||
mg.Deps(initVars)
|
||||
mg.Deps(Build.Generate, Release.Dirs)
|
||||
mg.Deps(Release.Windows, Release.Linux, Release.Darwin)
|
||||
|
||||
// Run compiling in parallel to speed it up
|
||||
errs, _ := errgroup.WithContext(ctx)
|
||||
errs.Go((Release{}).Windows)
|
||||
errs.Go((Release{}).Linux)
|
||||
errs.Go((Release{}).Darwin)
|
||||
if err := errs.Wait(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := (Release{}).Compress(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := (Release{}).Copy(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := (Release{}).Check(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := (Release{}).OsPackage(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := (Release{}).Zip(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Creates all directories needed to release vikunja
|
||||
func (Release) Dirs() error {
|
||||
for _, d := range []string{"binaries", "release", "zip"} {
|
||||
if err := os.MkdirAll(RootPath+"/"+DIST+"/"+d, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func runXgo(targets string) error {
|
||||
mg.Deps(initVars)
|
||||
checkAndInstallGoTool("xgo", "src.techknowlogick.com/xgo")
|
||||
|
||||
extraLdflags := `-linkmode external -extldflags "-static" `
|
||||
|
||||
// See https://github.com/techknowlogick/xgo/issues/79
|
||||
if strings.HasPrefix(targets, "darwin") {
|
||||
extraLdflags = ""
|
||||
}
|
||||
|
||||
runAndStreamOutput("xgo",
|
||||
"-dest", RootPath+"/"+DIST+"/binaries",
|
||||
"-tags", "netgo "+Tags,
|
||||
"-ldflags", extraLdflags+Ldflags,
|
||||
"-targets", targets,
|
||||
"-out", Executable+"-"+Version,
|
||||
RootPath)
|
||||
if os.Getenv("DRONE_WORKSPACE") != "" {
|
||||
return filepath.Walk("/build/", func(path string, info os.FileInfo, err error) error {
|
||||
// Skip directories
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
return moveFile(path, RootPath+"/"+DIST+"/binaries/"+info.Name())
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Builds binaries for windows
|
||||
func (Release) Windows() error {
|
||||
return runXgo("windows/*")
|
||||
}
|
||||
|
||||
// Builds binaries for linux
|
||||
func (Release) Linux() error {
|
||||
return runXgo("linux/*")
|
||||
}
|
||||
|
||||
// Builds binaries for darwin
|
||||
func (Release) Darwin() error {
|
||||
return runXgo("darwin/*")
|
||||
}
|
||||
|
||||
// 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);)
|
||||
|
||||
errs, _ := errgroup.WithContext(ctx)
|
||||
|
||||
filepath.Walk(RootPath+"/"+DIST+"/binaries/", func(path string, info os.FileInfo, err error) error {
|
||||
// Only executable files
|
||||
if !strings.Contains(info.Name(), Executable) {
|
||||
return nil
|
||||
}
|
||||
// No mips or s390x for you today
|
||||
if strings.Contains(info.Name(), "mips") || strings.Contains(info.Name(), "s390x") {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Runs compressing in parallel since upx is single-threaded
|
||||
errs.Go(func() error {
|
||||
runAndStreamOutput("chmod", "+x", path) // Make sure all binaries are executable. Sometimes the CI does weired things and they're not.
|
||||
runAndStreamOutput("upx", "-9", path)
|
||||
return nil
|
||||
})
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return errs.Wait()
|
||||
}
|
||||
|
||||
// Copies all built binaries to dist/release/ in preparation for creating the os packages
|
||||
func (Release) Copy() error {
|
||||
return filepath.Walk(RootPath+"/"+DIST+"/binaries/", func(path string, info os.FileInfo, err error) error {
|
||||
// Only executable files
|
||||
if !strings.Contains(info.Name(), Executable) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return copyFile(path, RootPath+"/"+DIST+"/release/"+info.Name())
|
||||
})
|
||||
}
|
||||
|
||||
// Creates sha256 checksum files for each binary in dist/release/
|
||||
func (Release) Check() error {
|
||||
p := RootPath + "/" + DIST + "/release/"
|
||||
return filepath.Walk(p, func(path string, info os.FileInfo, err error) error {
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
f, err := os.Create(p + info.Name() + ".sha256")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hash, err := calculateSha256FileHash(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = f.WriteString(hash + " " + info.Name())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return f.Close()
|
||||
})
|
||||
}
|
||||
|
||||
// Creates a folder for each
|
||||
func (Release) OsPackage() error {
|
||||
p := RootPath + "/" + DIST + "/release/"
|
||||
|
||||
// We first put all files in a map to then iterate over it since the walk function would otherwise also iterate
|
||||
// over the newly created files, creating some kind of endless loop.
|
||||
bins := make(map[string]os.FileInfo)
|
||||
if err := filepath.Walk(p, func(path string, info os.FileInfo, err error) error {
|
||||
if strings.Contains(info.Name(), ".sha256") || info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
bins[path] = info
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for path, info := range bins {
|
||||
folder := p + info.Name() + "-full/"
|
||||
if err := os.Mkdir(folder, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := moveFile(p+info.Name()+".sha256", folder+info.Name()+".sha256"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := moveFile(path, folder+info.Name()); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := copyFile(RootPath+"/config.yml.sample", folder+"config.yml.sample"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := copyFile(RootPath+"/LICENSE", folder+"LICENSE"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Creates a zip file from all os-package folders in dist/release
|
||||
func (Release) Zip() error {
|
||||
p := RootPath + "/" + DIST + "/release/"
|
||||
if err := filepath.Walk(p, func(path string, info os.FileInfo, err error) error {
|
||||
if !info.IsDir() || info.Name() == "release" {
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Printf("Zipping %s...\n", info.Name())
|
||||
|
||||
c := exec.Command("zip", "-r", RootPath+"/"+DIST+"/zip/"+info.Name(), ".", "-i", "*")
|
||||
c.Dir = path
|
||||
out, err := c.Output()
|
||||
fmt.Print(string(out))
|
||||
return err
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Creates a debian repo structure
|
||||
func (Release) Reprepro() {
|
||||
mg.Deps(setVersion, setBinLocation)
|
||||
runAndStreamOutput("reprepro_expect", "debian", "includedeb", "buster", RootPath+"/"+DIST+"/os-packages/"+Executable+"_"+strings.ReplaceAll(VersionNumber, "v0", "0")+"_amd64.deb")
|
||||
}
|
||||
|
||||
// Creates deb, rpm and apk packages
|
||||
func (Release) Packages() error {
|
||||
mg.Deps(initVars)
|
||||
var err error
|
||||
binpath := "nfpm"
|
||||
err = exec.Command(binpath).Run()
|
||||
if err != nil && strings.Contains(err.Error(), "executable file not found") {
|
||||
binpath = "/nfpm"
|
||||
err = exec.Command(binpath).Run()
|
||||
}
|
||||
if err != nil && strings.Contains(err.Error(), "executable file not found") {
|
||||
fmt.Println("Please manually install nfpm by running")
|
||||
fmt.Println("curl -sfL https://install.goreleaser.com/github.com/goreleaser/nfpm.sh | sh -s -- -b $(go env GOPATH)/bin")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// 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)
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
||||
releasePath := RootPath + "/" + DIST + "/os-packages/"
|
||||
if err := os.MkdirAll(releasePath, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
runAndStreamOutput(binpath, "pkg", "--packager", "deb", "--target", releasePath)
|
||||
runAndStreamOutput(binpath, "pkg", "--packager", "rpm", "--target", releasePath)
|
||||
runAndStreamOutput(binpath, "pkg", "--packager", "apk", "--target", releasePath)
|
||||
|
||||
return ioutil.WriteFile(nfpmConfigPath, nfpmconfig, 0)
|
||||
}
|
||||
|
||||
type Dev mg.Namespace
|
||||
|
||||
// Creates a new bare db migration skeleton in pkg/migration with the current date
|
||||
func (Dev) CreateMigration() error {
|
||||
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
fmt.Print("Enter the name of the struct: ")
|
||||
str, _ := reader.ReadString('\n')
|
||||
str = strings.Trim(str, "\n")
|
||||
|
||||
date := time.Now().Format("20060102150405")
|
||||
|
||||
migration := `// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU 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 ` + str + date + ` struct {
|
||||
}
|
||||
|
||||
func (` + str + date + `) TableName() string {
|
||||
return "` + str + `"
|
||||
}
|
||||
|
||||
func init() {
|
||||
migrations = append(migrations, &xormigrate.Migration{
|
||||
ID: "` + date + `",
|
||||
Description: "",
|
||||
Migrate: func(tx *xorm.Engine) error {
|
||||
return tx.Sync2(` + str + date + `{})
|
||||
},
|
||||
Rollback: func(tx *xorm.Engine) error {
|
||||
return nil
|
||||
},
|
||||
})
|
||||
}
|
||||
`
|
||||
f, err := os.Create(RootPath + "/pkg/migration/" + date + ".go")
|
||||
defer f.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = f.WriteString(migration)
|
||||
return err
|
||||
}
|
||||
|
||||
type configOption struct {
|
||||
key string
|
||||
description string
|
||||
defaultValue string
|
||||
|
||||
children []*configOption
|
||||
}
|
||||
|
||||
func parseYamlConfigNode(node *yaml.Node) (config *configOption) {
|
||||
config = &configOption{
|
||||
key: node.Value,
|
||||
description: strings.ReplaceAll(node.HeadComment, "# ", ""),
|
||||
}
|
||||
|
||||
valMap := make(map[string]*configOption)
|
||||
|
||||
var lastOption *configOption
|
||||
|
||||
for i, n2 := range node.Content {
|
||||
coo := &configOption{
|
||||
key: n2.Value,
|
||||
description: strings.ReplaceAll(n2.HeadComment, "# ", ""),
|
||||
}
|
||||
|
||||
// If there's a key in valMap for the current key we should use that to append etc
|
||||
// Else we just create a new configobject
|
||||
co, exists := valMap[n2.Value]
|
||||
if exists {
|
||||
co.description = coo.description
|
||||
} else {
|
||||
valMap[n2.Value] = coo
|
||||
config.children = append(config.children, coo)
|
||||
}
|
||||
|
||||
// fmt.Println(i, coo.key, coo.description, n2.Value)
|
||||
|
||||
if i%2 == 0 {
|
||||
lastOption = coo
|
||||
continue
|
||||
} else {
|
||||
lastOption.defaultValue = n2.Value
|
||||
}
|
||||
|
||||
if i-1 >= 0 && i-1 <= len(node.Content) && node.Content[i-1].Value != "" {
|
||||
coo.defaultValue = n2.Value
|
||||
coo.key = node.Content[i-1].Value
|
||||
}
|
||||
|
||||
if len(n2.Content) > 0 {
|
||||
for _, n := range n2.Content {
|
||||
coo.children = append(coo.children, parseYamlConfigNode(n))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
func printConfig(config []*configOption, level int) (rendered string) {
|
||||
|
||||
// Keep track of what we already printed to prevent printing things twice
|
||||
printed := make(map[string]bool)
|
||||
|
||||
for _, option := range config {
|
||||
|
||||
if option.key != "" {
|
||||
|
||||
// Filter out all config objects where the default value == key
|
||||
// Yaml is weired: It gives you a slice with an entry each for the key and their value.
|
||||
if printed[option.key] {
|
||||
continue
|
||||
}
|
||||
|
||||
if level == 0 {
|
||||
rendered += "---\n\n"
|
||||
}
|
||||
|
||||
rendered += "#"
|
||||
for i := 0; i <= level; i++ {
|
||||
rendered += "#"
|
||||
}
|
||||
rendered += " " + option.key + "\n\n"
|
||||
|
||||
if option.description != "" {
|
||||
rendered += option.description + "\n\n"
|
||||
}
|
||||
|
||||
// Top level config values never have a default value
|
||||
if level > 0 {
|
||||
rendered += "Default: `" + option.defaultValue
|
||||
if option.defaultValue == "" {
|
||||
rendered += "<empty>"
|
||||
}
|
||||
rendered += "`\n"
|
||||
}
|
||||
}
|
||||
|
||||
printed[option.key] = true
|
||||
rendered += "\n" + printConfig(option.children, level+1)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const (
|
||||
configDocPath = `docs/content/doc/setup/config.md`
|
||||
configInjectComment = `<!-- Generated config will be injected here -->`
|
||||
)
|
||||
|
||||
// Generates the error docs from a commented config.yml.sample file in the repo root.
|
||||
func GenerateDocs() error {
|
||||
|
||||
config, err := ioutil.ReadFile("config.yml.sample")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var d yaml.Node
|
||||
err = yaml.Unmarshal(config, &d)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
conf := []*configOption{}
|
||||
|
||||
for _, node := range d.Content {
|
||||
for _, n := range node.Content {
|
||||
co := parseYamlConfigNode(n)
|
||||
conf = append(conf, co)
|
||||
}
|
||||
}
|
||||
|
||||
renderedConfig := printConfig(conf, 0)
|
||||
|
||||
// Rebuild the config
|
||||
file, err := os.OpenFile(configDocPath, os.O_RDWR, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// We read the config doc up until the marker, then stop and append our generated config
|
||||
fullConfig := ""
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
t := scanner.Text()
|
||||
fullConfig += t + "\n"
|
||||
|
||||
if t == configInjectComment {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fullConfig += "\n" + renderedConfig
|
||||
|
||||
// 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 {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
6
main.go
6
main.go
@@ -2,16 +2,16 @@
|
||||
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// 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 General Public License for more details.
|
||||
// GNU Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// 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 main
|
||||
|
||||
21
nfpm.yaml
Normal file
21
nfpm.yaml
Normal file
@@ -0,0 +1,21 @@
|
||||
name: "vikunja"
|
||||
arch: "amd64"
|
||||
platform: "linux"
|
||||
version: "<version>"
|
||||
description: "Vikunja is an open-source todo application, written in Go. It lets you create lists,tasks and share them via teams or directly between users."
|
||||
maintainer: "Vikunja Maintainers <maintainers@vikunja.io>"
|
||||
homepage: "https://vikunja.io"
|
||||
section: "default"
|
||||
priority: "extra"
|
||||
license: "AGPLv3"
|
||||
contents:
|
||||
- src: <binlocation>
|
||||
dst: /opt/vikunja/vikunja
|
||||
- src: ./config.yml.sample
|
||||
dst: /etc/vikunja/config.yml
|
||||
type: "config"
|
||||
- src: /opt/vikunja/vikunja
|
||||
dst: /usr/local/bin/vikunja
|
||||
type: "symlink"
|
||||
scripts:
|
||||
postinstall: ./build/after-install.sh
|
||||
@@ -2,30 +2,33 @@
|
||||
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// 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 General Public License for more details.
|
||||
// GNU Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// 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 caldav
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/timeutil"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"code.vikunja.io/api/pkg/utils"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// DateFormat ist the caldav date format
|
||||
// DateFormat is the caldav date format
|
||||
const DateFormat = `20060102T150405`
|
||||
|
||||
// Event holds a single caldav event
|
||||
@@ -34,38 +37,40 @@ type Event struct {
|
||||
Description string
|
||||
UID string
|
||||
Alarms []Alarm
|
||||
Color string
|
||||
|
||||
Timestamp timeutil.TimeStamp
|
||||
Start timeutil.TimeStamp
|
||||
End timeutil.TimeStamp
|
||||
Timestamp time.Time
|
||||
Start time.Time
|
||||
End time.Time
|
||||
}
|
||||
|
||||
// Todo holds a single VTODO
|
||||
type Todo struct {
|
||||
// Required
|
||||
Timestamp timeutil.TimeStamp
|
||||
Timestamp time.Time
|
||||
UID string
|
||||
|
||||
// Optional
|
||||
Summary string
|
||||
Description string
|
||||
Completed timeutil.TimeStamp
|
||||
Completed time.Time
|
||||
Organizer *user.User
|
||||
Priority int64 // 0-9, 1 is highest
|
||||
RelatedToUID string
|
||||
Color string
|
||||
|
||||
Start timeutil.TimeStamp
|
||||
End timeutil.TimeStamp
|
||||
DueDate timeutil.TimeStamp
|
||||
Start time.Time
|
||||
End time.Time
|
||||
DueDate time.Time
|
||||
Duration time.Duration
|
||||
|
||||
Created timeutil.TimeStamp
|
||||
Updated timeutil.TimeStamp // last-mod
|
||||
Created time.Time
|
||||
Updated time.Time // last-mod
|
||||
}
|
||||
|
||||
// Alarm holds infos about an alarm from a caldav event
|
||||
type Alarm struct {
|
||||
Time timeutil.TimeStamp
|
||||
Time time.Time
|
||||
Description string
|
||||
}
|
||||
|
||||
@@ -73,6 +78,24 @@ type Alarm struct {
|
||||
type Config struct {
|
||||
Name string
|
||||
ProdID string
|
||||
Color string
|
||||
}
|
||||
|
||||
func getCaldavColor(color string) (caldavcolor string) {
|
||||
if color == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(color, "#") {
|
||||
color = "#" + color
|
||||
}
|
||||
|
||||
color += "FF"
|
||||
|
||||
return `
|
||||
X-APPLE-CALENDAR-COLOR:` + color + `
|
||||
X-OUTLOOK-COLOR:` + color + `
|
||||
X-FUNAMBOL-COLOR:` + color
|
||||
}
|
||||
|
||||
// ParseEvents parses an array of caldav events and gives them back as string
|
||||
@@ -82,7 +105,7 @@ VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
X-PUBLISHED-TTL:PT4H
|
||||
X-WR-CALNAME:` + config.Name + `
|
||||
PRODID:-//` + config.ProdID + `//EN`
|
||||
PRODID:-//` + config.ProdID + `//EN` + getCaldavColor(config.Color)
|
||||
|
||||
for _, e := range events {
|
||||
|
||||
@@ -90,11 +113,17 @@ PRODID:-//` + config.ProdID + `//EN`
|
||||
e.UID = makeCalDavTimeFromTimeStamp(e.Timestamp) + utils.Sha256(e.Summary)
|
||||
}
|
||||
|
||||
formattedDescription := ""
|
||||
if e.Description != "" {
|
||||
re := regexp.MustCompile(`\r?\n`)
|
||||
formattedDescription = re.ReplaceAllString(e.Description, "\\n")
|
||||
}
|
||||
|
||||
caldavevents += `
|
||||
BEGIN:VEVENT
|
||||
UID:` + e.UID + `
|
||||
SUMMARY:` + e.Summary + `
|
||||
DESCRIPTION:` + e.Description + `
|
||||
SUMMARY:` + e.Summary + getCaldavColor(e.Color) + `
|
||||
DESCRIPTION:` + formattedDescription + `
|
||||
DTSTAMP:` + makeCalDavTimeFromTimeStamp(e.Timestamp) + `
|
||||
DTSTART:` + makeCalDavTimeFromTimeStamp(e.Start) + `
|
||||
DTEND:` + makeCalDavTimeFromTimeStamp(e.End)
|
||||
@@ -128,7 +157,7 @@ VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
X-PUBLISHED-TTL:PT4H
|
||||
X-WR-CALNAME:` + config.Name + `
|
||||
PRODID:-//` + config.ProdID + `//EN`
|
||||
PRODID:-//` + config.ProdID + `//EN` + getCaldavColor(config.Color)
|
||||
|
||||
for _, t := range todos {
|
||||
if t.UID == "" {
|
||||
@@ -139,23 +168,26 @@ PRODID:-//` + config.ProdID + `//EN`
|
||||
BEGIN:VTODO
|
||||
UID:` + t.UID + `
|
||||
DTSTAMP:` + makeCalDavTimeFromTimeStamp(t.Timestamp) + `
|
||||
SUMMARY:` + t.Summary
|
||||
SUMMARY:` + t.Summary + getCaldavColor(t.Color)
|
||||
|
||||
if t.Start != 0 {
|
||||
if t.Start.Unix() > 0 {
|
||||
caldavtodos += `
|
||||
DTSTART: ` + makeCalDavTimeFromTimeStamp(t.Start)
|
||||
}
|
||||
if t.End != 0 {
|
||||
if t.End.Unix() > 0 {
|
||||
caldavtodos += `
|
||||
DTEND: ` + makeCalDavTimeFromTimeStamp(t.End)
|
||||
}
|
||||
if t.Description != "" {
|
||||
re := regexp.MustCompile(`\r?\n`)
|
||||
formattedDescription := re.ReplaceAllString(t.Description, "\\n")
|
||||
caldavtodos += `
|
||||
DESCRIPTION:` + t.Description
|
||||
DESCRIPTION:` + formattedDescription
|
||||
}
|
||||
if t.Completed != 0 {
|
||||
if t.Completed.Unix() > 0 {
|
||||
caldavtodos += `
|
||||
COMPLETED: ` + makeCalDavTimeFromTimeStamp(t.Completed)
|
||||
COMPLETED:` + makeCalDavTimeFromTimeStamp(t.Completed) + `
|
||||
STATUS:COMPLETED`
|
||||
}
|
||||
if t.Organizer != nil {
|
||||
caldavtodos += `
|
||||
@@ -167,12 +199,12 @@ ORGANIZER;CN=:` + t.Organizer.Username
|
||||
RELATED-TO:` + t.RelatedToUID
|
||||
}
|
||||
|
||||
if t.DueDate != 0 {
|
||||
if t.DueDate.Unix() > 0 {
|
||||
caldavtodos += `
|
||||
DUE:` + makeCalDavTimeFromTimeStamp(t.DueDate)
|
||||
}
|
||||
|
||||
if t.Created != 0 {
|
||||
if t.Created.Unix() > 0 {
|
||||
caldavtodos += `
|
||||
CREATED:` + makeCalDavTimeFromTimeStamp(t.Created)
|
||||
}
|
||||
@@ -200,20 +232,19 @@ END:VCALENDAR` // Need a line break
|
||||
return
|
||||
}
|
||||
|
||||
func makeCalDavTimeFromTimeStamp(ts timeutil.TimeStamp) (caldavtime string) {
|
||||
tz, _ := time.LoadLocation("UTC")
|
||||
return ts.ToTime().In(tz).Format(DateFormat)
|
||||
func makeCalDavTimeFromTimeStamp(ts time.Time) (caldavtime string) {
|
||||
return ts.In(config.GetTimeZone()).Format(DateFormat)
|
||||
}
|
||||
|
||||
func calcAlarmDateFromReminder(eventStartUnix, reminderUnix timeutil.TimeStamp) (alarmTime string) {
|
||||
if eventStartUnix > reminderUnix {
|
||||
func calcAlarmDateFromReminder(eventStart, reminder time.Time) (alarmTime string) {
|
||||
diff := reminder.Sub(eventStart)
|
||||
diffStr := strings.ToUpper(diff.String())
|
||||
if diff < 0 {
|
||||
alarmTime += `-`
|
||||
// We append the - at the beginning of the caldav flag, that would get in the way if the minutes
|
||||
// themselves are also containing it
|
||||
diffStr = diffStr[1:]
|
||||
}
|
||||
alarmTime += `PT`
|
||||
diff := eventStartUnix - reminderUnix
|
||||
if diff < 0 { // Make it positive
|
||||
diff = diff * -1
|
||||
}
|
||||
alarmTime += strconv.Itoa(int(diff/60)) + "M"
|
||||
alarmTime += `PT` + diffStr
|
||||
return
|
||||
}
|
||||
|
||||
@@ -2,21 +2,27 @@
|
||||
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// 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 General Public License for more details.
|
||||
// GNU Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// 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 caldav
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestParseEvents(t *testing.T) {
|
||||
type args struct {
|
||||
@@ -34,29 +40,31 @@ func TestParseEvents(t *testing.T) {
|
||||
config: &Config{
|
||||
Name: "test",
|
||||
ProdID: "RandomProdID which is not random",
|
||||
Color: "ffffff",
|
||||
},
|
||||
events: []*Event{
|
||||
{
|
||||
Summary: "Event #1",
|
||||
Description: "Lorem Ipsum",
|
||||
UID: "randommduid",
|
||||
Timestamp: 1543626724,
|
||||
Start: 1543626724,
|
||||
End: 1543627824,
|
||||
Timestamp: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||
Start: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||
End: time.Unix(1543627824, 0).In(config.GetTimeZone()),
|
||||
Color: "affffe",
|
||||
},
|
||||
{
|
||||
Summary: "Event #2",
|
||||
UID: "randommduidd",
|
||||
Timestamp: 1543726724,
|
||||
Start: 1543726724,
|
||||
End: 1543738724,
|
||||
Timestamp: time.Unix(1543726724, 0).In(config.GetTimeZone()),
|
||||
Start: time.Unix(1543726724, 0).In(config.GetTimeZone()),
|
||||
End: time.Unix(1543738724, 0).In(config.GetTimeZone()),
|
||||
},
|
||||
{
|
||||
Summary: "Event #3 with empty uid",
|
||||
UID: "20181202T0600242aaef4a81d770c1e775e26bc5abebc87f1d3d7bffaa83",
|
||||
Timestamp: 1543726824,
|
||||
Start: 1543726824,
|
||||
End: 1543727000,
|
||||
Timestamp: time.Unix(1543726824, 0).In(config.GetTimeZone()),
|
||||
Start: time.Unix(1543726824, 0).In(config.GetTimeZone()),
|
||||
End: time.Unix(1543727000, 0).In(config.GetTimeZone()),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -66,9 +74,15 @@ 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:VEVENT
|
||||
UID:randommduid
|
||||
SUMMARY:Event #1
|
||||
X-APPLE-CALENDAR-COLOR:#affffeFF
|
||||
X-OUTLOOK-COLOR:#affffeFF
|
||||
X-FUNAMBOL-COLOR:#affffeFF
|
||||
DESCRIPTION:Lorem Ipsum
|
||||
DTSTAMP:20181201T011204
|
||||
DTSTART:20181201T011204
|
||||
@@ -104,44 +118,44 @@ END:VCALENDAR`,
|
||||
Summary: "Event #1",
|
||||
Description: "Lorem Ipsum",
|
||||
UID: "randommduid",
|
||||
Timestamp: 1543626724,
|
||||
Start: 1543626724,
|
||||
End: 1543627824,
|
||||
Timestamp: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||
Start: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||
End: time.Unix(1543627824, 0).In(config.GetTimeZone()),
|
||||
Alarms: []Alarm{
|
||||
{Time: 1543626524},
|
||||
{Time: 1543626224},
|
||||
{Time: 1543626024},
|
||||
{Time: time.Unix(1543626524, 0).In(config.GetTimeZone())},
|
||||
{Time: time.Unix(1543626224, 0).In(config.GetTimeZone())},
|
||||
{Time: time.Unix(1543626024, 0)},
|
||||
},
|
||||
},
|
||||
{
|
||||
Summary: "Event #2",
|
||||
UID: "randommduidd",
|
||||
Timestamp: 1543726724,
|
||||
Start: 1543726724,
|
||||
End: 1543738724,
|
||||
Timestamp: time.Unix(1543726724, 0).In(config.GetTimeZone()),
|
||||
Start: time.Unix(1543726724, 0).In(config.GetTimeZone()),
|
||||
End: time.Unix(1543738724, 0).In(config.GetTimeZone()),
|
||||
Alarms: []Alarm{
|
||||
{Time: 1543626524},
|
||||
{Time: 1543626224},
|
||||
{Time: 1543626024},
|
||||
{Time: time.Unix(1543626524, 0).In(config.GetTimeZone())},
|
||||
{Time: time.Unix(1543626224, 0).In(config.GetTimeZone())},
|
||||
{Time: time.Unix(1543626024, 0).In(config.GetTimeZone())},
|
||||
},
|
||||
},
|
||||
{
|
||||
Summary: "Event #3 with empty uid",
|
||||
Timestamp: 1543726824,
|
||||
Start: 1543726824,
|
||||
End: 1543727000,
|
||||
Timestamp: time.Unix(1543726824, 0).In(config.GetTimeZone()),
|
||||
Start: time.Unix(1543726824, 0).In(config.GetTimeZone()),
|
||||
End: time.Unix(1543727000, 0).In(config.GetTimeZone()),
|
||||
Alarms: []Alarm{
|
||||
{Time: 1543626524},
|
||||
{Time: 1543626224},
|
||||
{Time: 1543626024},
|
||||
{Time: 1543826824},
|
||||
{Time: time.Unix(1543626524, 0).In(config.GetTimeZone())},
|
||||
{Time: time.Unix(1543626224, 0).In(config.GetTimeZone())},
|
||||
{Time: time.Unix(1543626024, 0).In(config.GetTimeZone())},
|
||||
{Time: time.Unix(1543826824, 0).In(config.GetTimeZone())},
|
||||
},
|
||||
},
|
||||
{
|
||||
Summary: "Event #4 without any",
|
||||
Timestamp: 1543726824,
|
||||
Start: 1543726824,
|
||||
End: 1543727000,
|
||||
Timestamp: time.Unix(1543726824, 0),
|
||||
Start: time.Unix(1543726824, 0),
|
||||
End: time.Unix(1543727000, 0),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -159,17 +173,17 @@ DTSTAMP:20181201T011204
|
||||
DTSTART:20181201T011204
|
||||
DTEND:20181201T013024
|
||||
BEGIN:VALARM
|
||||
TRIGGER:-PT3M
|
||||
TRIGGER:-PT3M20S
|
||||
ACTION:DISPLAY
|
||||
DESCRIPTION:Event #1
|
||||
END:VALARM
|
||||
BEGIN:VALARM
|
||||
TRIGGER:-PT8M
|
||||
TRIGGER:-PT8M20S
|
||||
ACTION:DISPLAY
|
||||
DESCRIPTION:Event #1
|
||||
END:VALARM
|
||||
BEGIN:VALARM
|
||||
TRIGGER:-PT11M
|
||||
TRIGGER:-PT11M40S
|
||||
ACTION:DISPLAY
|
||||
DESCRIPTION:Event #1
|
||||
END:VALARM
|
||||
@@ -182,17 +196,17 @@ DTSTAMP:20181202T045844
|
||||
DTSTART:20181202T045844
|
||||
DTEND:20181202T081844
|
||||
BEGIN:VALARM
|
||||
TRIGGER:-PT1670M
|
||||
TRIGGER:-PT27H50M0S
|
||||
ACTION:DISPLAY
|
||||
DESCRIPTION:Event #2
|
||||
END:VALARM
|
||||
BEGIN:VALARM
|
||||
TRIGGER:-PT1675M
|
||||
TRIGGER:-PT27H55M0S
|
||||
ACTION:DISPLAY
|
||||
DESCRIPTION:Event #2
|
||||
END:VALARM
|
||||
BEGIN:VALARM
|
||||
TRIGGER:-PT1678M
|
||||
TRIGGER:-PT27H58M20S
|
||||
ACTION:DISPLAY
|
||||
DESCRIPTION:Event #2
|
||||
END:VALARM
|
||||
@@ -205,22 +219,22 @@ DTSTAMP:20181202T050024
|
||||
DTSTART:20181202T050024
|
||||
DTEND:20181202T050320
|
||||
BEGIN:VALARM
|
||||
TRIGGER:-PT1671M
|
||||
TRIGGER:-PT27H51M40S
|
||||
ACTION:DISPLAY
|
||||
DESCRIPTION:Event #3 with empty uid
|
||||
END:VALARM
|
||||
BEGIN:VALARM
|
||||
TRIGGER:-PT1676M
|
||||
TRIGGER:-PT27H56M40S
|
||||
ACTION:DISPLAY
|
||||
DESCRIPTION:Event #3 with empty uid
|
||||
END:VALARM
|
||||
BEGIN:VALARM
|
||||
TRIGGER:-PT1680M
|
||||
TRIGGER:-PT28H0M0S
|
||||
ACTION:DISPLAY
|
||||
DESCRIPTION:Event #3 with empty uid
|
||||
END:VALARM
|
||||
BEGIN:VALARM
|
||||
TRIGGER:PT1666M
|
||||
TRIGGER:PT27H46M40S
|
||||
ACTION:DISPLAY
|
||||
DESCRIPTION:Event #3 with empty uid
|
||||
END:VALARM
|
||||
@@ -233,14 +247,141 @@ DTSTAMP:20181202T050024
|
||||
DTSTART:20181202T050024
|
||||
DTEND:20181202T050320
|
||||
END:VEVENT
|
||||
END:VCALENDAR`,
|
||||
},
|
||||
{
|
||||
name: "Test caldavparsing with multiline description",
|
||||
args: args{
|
||||
config: &Config{
|
||||
Name: "test",
|
||||
ProdID: "RandomProdID which is not random",
|
||||
},
|
||||
events: []*Event{
|
||||
{
|
||||
Summary: "Event #1",
|
||||
Description: `Lorem Ipsum
|
||||
Dolor sit amet`,
|
||||
UID: "randommduid",
|
||||
Timestamp: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||
Start: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||
End: time.Unix(1543627824, 0).In(config.GetTimeZone()),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantCaldavevents: `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
X-PUBLISHED-TTL:PT4H
|
||||
X-WR-CALNAME:test
|
||||
PRODID:-//RandomProdID which is not random//EN
|
||||
BEGIN:VEVENT
|
||||
UID:randommduid
|
||||
SUMMARY:Event #1
|
||||
DESCRIPTION:Lorem Ipsum\nDolor sit amet
|
||||
DTSTAMP:20181201T011204
|
||||
DTSTART:20181201T011204
|
||||
DTEND:20181201T013024
|
||||
END:VEVENT
|
||||
END:VCALENDAR`,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if gotCaldavevents := ParseEvents(tt.args.config, tt.args.events); gotCaldavevents != tt.wantCaldavevents {
|
||||
t.Errorf("ParseEvents() = %v, want %v", gotCaldavevents, tt.wantCaldavevents)
|
||||
}
|
||||
gotCaldavevents := ParseEvents(tt.args.config, tt.args.events)
|
||||
assert.Equal(t, gotCaldavevents, tt.wantCaldavevents)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseTodos(t *testing.T) {
|
||||
type args struct {
|
||||
config *Config
|
||||
todos []*Todo
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantCaldavtasks string
|
||||
}{
|
||||
{
|
||||
name: "Test caldavparsing with multiline description",
|
||||
args: args{
|
||||
config: &Config{
|
||||
Name: "test",
|
||||
ProdID: "RandomProdID which is not random",
|
||||
Color: "ffffff",
|
||||
},
|
||||
todos: []*Todo{
|
||||
{
|
||||
Summary: "Todo #1",
|
||||
Description: `Lorem Ipsum
|
||||
Dolor sit amet`,
|
||||
UID: "randommduid",
|
||||
Timestamp: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||
Color: "affffe",
|
||||
},
|
||||
},
|
||||
},
|
||||
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:20181201T011204
|
||||
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
|
||||
END:VTODO
|
||||
END:VCALENDAR`,
|
||||
},
|
||||
{
|
||||
name: "Test caldavparsing with completed task",
|
||||
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()),
|
||||
Completed: time.Unix(1543627824, 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:20181201T011204
|
||||
SUMMARY:Todo #1
|
||||
DESCRIPTION:Lorem Ipsum
|
||||
COMPLETED:20181201T013024
|
||||
STATUS:COMPLETED
|
||||
LAST-MODIFIED:00010101T000000
|
||||
END:VTODO
|
||||
END:VCALENDAR`,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gotCaldavtasks := ParseTodos(tt.args.config, tt.args.todos)
|
||||
assert.Equal(t, gotCaldavtasks, tt.wantCaldavtasks)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,38 +2,26 @@
|
||||
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// 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 General Public License for more details.
|
||||
// GNU Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// You should have received a copy of the GNU Affero General Public Licensee
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/files"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/mail"
|
||||
"code.vikunja.io/api/pkg/migration"
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
migrator "code.vikunja.io/api/pkg/modules/migration"
|
||||
"code.vikunja.io/api/pkg/red"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"fmt"
|
||||
"github.com/spf13/cobra"
|
||||
"os"
|
||||
)
|
||||
|
||||
func init() {
|
||||
cobra.OnInitialize(initialize)
|
||||
}
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "vikunja",
|
||||
@@ -47,7 +35,8 @@ alpine areas of the Andes and a relative of the llama.
|
||||
Vikunja is a self-hosted To-Do list application with a web app and mobile apps for all platforms. It is licensed under the GPLv3.
|
||||
|
||||
Find more info at vikunja.io.`,
|
||||
Run: webCmd.Run,
|
||||
PreRun: webCmd.PreRun,
|
||||
Run: webCmd.Run,
|
||||
}
|
||||
|
||||
// Execute starts the application
|
||||
@@ -57,42 +46,3 @@ func Execute() {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// Initializes all kinds of things in the right order
|
||||
func initialize() {
|
||||
// Init the config
|
||||
config.InitConfig()
|
||||
|
||||
// Init redis
|
||||
red.InitRedis()
|
||||
|
||||
// Set logger
|
||||
log.InitLogger()
|
||||
|
||||
// Run the migrations
|
||||
migration.Migrate(nil)
|
||||
|
||||
// Set Engine
|
||||
err := models.SetEngine()
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
err = user.InitDB()
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
err = files.SetEngine()
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
err = migrator.InitDB()
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
|
||||
// Initialize the files handler
|
||||
files.InitFileHandler()
|
||||
|
||||
// Start the mail daemon
|
||||
mail.StartMailDaemon()
|
||||
}
|
||||
|
||||
44
pkg/cmd/dump.go
Normal file
44
pkg/cmd/dump.go
Normal file
@@ -0,0 +1,44 @@
|
||||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public Licensee as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public Licensee
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"code.vikunja.io/api/pkg/initialize"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/modules/dump"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(dumpCmd)
|
||||
}
|
||||
|
||||
var dumpCmd = &cobra.Command{
|
||||
Use: "dump",
|
||||
Short: "Dump all vikunja data into a zip file. Includes config, files and db.",
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
initialize.FullInit()
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
filename := "vikunja-dump_" + time.Now().Format("2006-01-02_15-03-05") + ".zip"
|
||||
if err := dump.Dump(filename); err != nil {
|
||||
log.Critical(err.Error())
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -2,21 +2,22 @@
|
||||
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// 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 General Public License for more details.
|
||||
// GNU Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// You should have received a copy of the GNU Affero General Public Licensee
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/initialize"
|
||||
"code.vikunja.io/api/pkg/migration"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -24,7 +25,7 @@ import (
|
||||
func init() {
|
||||
migrateCmd.AddCommand(migrateListCmd)
|
||||
migrationRollbackCmd.Flags().StringVarP(&rollbackUntilFlag, "name", "n", "", "The id of the migration you want to roll back until.")
|
||||
migrationRollbackCmd.MarkFlagRequired("name")
|
||||
_ = migrationRollbackCmd.MarkFlagRequired("name")
|
||||
migrateCmd.AddCommand(migrationRollbackCmd)
|
||||
rootCmd.AddCommand(migrateCmd)
|
||||
}
|
||||
@@ -35,6 +36,9 @@ func init() {
|
||||
var migrateCmd = &cobra.Command{
|
||||
Use: "migrate",
|
||||
Short: "Run all database migrations which didn't already run.",
|
||||
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
||||
initialize.LightInit()
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
migration.Migrate(nil)
|
||||
},
|
||||
|
||||
42
pkg/cmd/restore.go
Normal file
42
pkg/cmd/restore.go
Normal file
@@ -0,0 +1,42 @@
|
||||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public Licensee as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public Licensee
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/initialize"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/modules/dump"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(restoreCmd)
|
||||
}
|
||||
|
||||
var restoreCmd = &cobra.Command{
|
||||
Use: "restore [filename]",
|
||||
Short: "Restores all vikunja data from a vikunja dump.",
|
||||
Args: cobra.ExactArgs(1),
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
initialize.FullInit()
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if err := dump.Restore(args[0]); err != nil {
|
||||
log.Critical(err.Error())
|
||||
}
|
||||
},
|
||||
}
|
||||
49
pkg/cmd/testmail.go
Normal file
49
pkg/cmd/testmail.go
Normal file
@@ -0,0 +1,49 @@
|
||||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public Licensee as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public Licensee
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/initialize"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/mail"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(testmailCmd)
|
||||
}
|
||||
|
||||
var testmailCmd = &cobra.Command{
|
||||
Use: "testmail [email]",
|
||||
Short: "Send a test mail using the configured smtp connection",
|
||||
Args: cobra.ExactArgs(1),
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
initialize.LightInit()
|
||||
|
||||
// Start the mail daemon
|
||||
mail.StartMailDaemon()
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
log.Info("Sending testmail...")
|
||||
email := args[0]
|
||||
if err := mail.SendTestMail(email); err != nil {
|
||||
log.Errorf("Error sending test mail: %s", err.Error())
|
||||
return
|
||||
}
|
||||
log.Info("Testmail successfully sent.")
|
||||
},
|
||||
}
|
||||
298
pkg/cmd/user.go
Normal file
298
pkg/cmd/user.go
Normal file
@@ -0,0 +1,298 @@
|
||||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public Licensee as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public Licensee
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.vikunja.io/api/pkg/db"
|
||||
"code.vikunja.io/api/pkg/initialize"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"github.com/olekukonko/tablewriter"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/term"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
var (
|
||||
userFlagUsername string
|
||||
userFlagEmail string
|
||||
userFlagPassword string
|
||||
userFlagAvatar = "default"
|
||||
userFlagResetPasswordDirectly bool
|
||||
userFlagEnableUser bool
|
||||
userFlagDisableUser bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
// User create flags
|
||||
userCreateCmd.Flags().StringVarP(&userFlagUsername, "username", "u", "", "The username of the new user.")
|
||||
_ = userCreateCmd.MarkFlagRequired("username")
|
||||
userCreateCmd.Flags().StringVarP(&userFlagEmail, "email", "e", "", "The email address of the new user.")
|
||||
_ = userCreateCmd.MarkFlagRequired("email")
|
||||
userCreateCmd.Flags().StringVarP(&userFlagPassword, "password", "p", "", "The password of the new user. You will be asked to enter it if not provided through the flag.")
|
||||
userCreateCmd.Flags().StringVarP(&userFlagAvatar, "avatar-provider", "a", "", "The avatar provider of the new user. Optional.")
|
||||
|
||||
// User update flags
|
||||
userUpdateCmd.Flags().StringVarP(&userFlagUsername, "username", "u", "", "The new username of the user.")
|
||||
userUpdateCmd.Flags().StringVarP(&userFlagEmail, "email", "e", "", "The new email address of the user.")
|
||||
userUpdateCmd.Flags().StringVarP(&userFlagAvatar, "avatar-provider", "a", "", "The new avatar provider of the new user.")
|
||||
|
||||
// Reset PW flags
|
||||
userResetPasswordCmd.Flags().BoolVarP(&userFlagResetPasswordDirectly, "direct", "d", false, "If provided, reset the password directly instead of sending the user a reset mail.")
|
||||
userResetPasswordCmd.Flags().StringVarP(&userFlagPassword, "password", "p", "", "The new password of the user. Only used in combination with --direct. You will be asked to enter it if not provided through the flag.")
|
||||
|
||||
// Change status flags
|
||||
userChangeEnabledCmd.Flags().BoolVarP(&userFlagDisableUser, "disable", "d", false, "Disable the user.")
|
||||
userChangeEnabledCmd.Flags().BoolVarP(&userFlagEnableUser, "enable", "e", false, "Enable the user.")
|
||||
|
||||
userCmd.AddCommand(userListCmd, userCreateCmd, userUpdateCmd, userResetPasswordCmd, userChangeEnabledCmd)
|
||||
rootCmd.AddCommand(userCmd)
|
||||
}
|
||||
|
||||
func getPasswordFromFlagOrInput() (pw string) {
|
||||
pw = userFlagPassword
|
||||
if userFlagPassword == "" {
|
||||
fmt.Print("Enter Password: ")
|
||||
bytePW, err := term.ReadPassword(int(os.Stdin.Fd()))
|
||||
if err != nil {
|
||||
log.Fatalf("Error reading password: %s", err)
|
||||
}
|
||||
fmt.Printf("\nConfirm Password: ")
|
||||
byteConfirmPW, err := term.ReadPassword(int(os.Stdin.Fd()))
|
||||
if err != nil {
|
||||
log.Fatalf("Error reading password: %s", err)
|
||||
}
|
||||
if string(bytePW) != string(byteConfirmPW) {
|
||||
log.Critical("Passwords don't match!")
|
||||
}
|
||||
fmt.Printf("\n")
|
||||
pw = strings.TrimSpace(string(bytePW))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func getUserFromArg(s *xorm.Session, arg string) *user.User {
|
||||
id, err := strconv.ParseInt(arg, 10, 64)
|
||||
if err != nil {
|
||||
log.Fatalf("Invalid user id: %s", err)
|
||||
}
|
||||
|
||||
u, err := user.GetUserByID(s, id)
|
||||
if err != nil {
|
||||
log.Fatalf("Could not get user: %s", err)
|
||||
}
|
||||
return u
|
||||
}
|
||||
|
||||
var userCmd = &cobra.Command{
|
||||
Use: "user",
|
||||
Short: "Manage users locally through the cli.",
|
||||
}
|
||||
|
||||
var userListCmd = &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "Shows a list of all users.",
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
initialize.FullInit()
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
users, err := user.ListUsers(s, "")
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
log.Fatalf("Error getting users: %s", err)
|
||||
}
|
||||
|
||||
if err := s.Commit(); err != nil {
|
||||
log.Fatalf("Error getting users: %s", err)
|
||||
}
|
||||
|
||||
table := tablewriter.NewWriter(os.Stdout)
|
||||
table.SetHeader([]string{
|
||||
"ID",
|
||||
"Username",
|
||||
"Email",
|
||||
"Active",
|
||||
"Created",
|
||||
"Updated",
|
||||
})
|
||||
|
||||
for _, u := range users {
|
||||
table.Append([]string{
|
||||
strconv.FormatInt(u.ID, 10),
|
||||
u.Username,
|
||||
u.Email,
|
||||
strconv.FormatBool(u.IsActive),
|
||||
u.Created.Format(time.RFC3339),
|
||||
u.Updated.Format(time.RFC3339),
|
||||
})
|
||||
}
|
||||
|
||||
table.Render()
|
||||
},
|
||||
}
|
||||
|
||||
var userCreateCmd = &cobra.Command{
|
||||
Use: "create",
|
||||
Short: "Create a new user.",
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
initialize.FullInit()
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
u := &user.User{
|
||||
Username: userFlagUsername,
|
||||
Email: userFlagEmail,
|
||||
Password: getPasswordFromFlagOrInput(),
|
||||
}
|
||||
newUser, err := user.CreateUser(s, u)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
log.Fatalf("Error creating new user: %s", err)
|
||||
}
|
||||
|
||||
err = models.CreateNewNamespaceForUser(s, newUser)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
log.Fatalf("Error creating new namespace for user: %s", err)
|
||||
}
|
||||
|
||||
if err := s.Commit(); err != nil {
|
||||
log.Fatalf("Error saving everything: %s", err)
|
||||
}
|
||||
|
||||
fmt.Printf("\nUser was created successfully.\n")
|
||||
},
|
||||
}
|
||||
|
||||
var userUpdateCmd = &cobra.Command{
|
||||
Use: "update [user id]",
|
||||
Short: "Update an existing user.",
|
||||
Args: cobra.ExactArgs(1),
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
initialize.FullInit()
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
u := getUserFromArg(s, args[0])
|
||||
|
||||
if userFlagUsername != "" {
|
||||
u.Username = userFlagUsername
|
||||
}
|
||||
if userFlagEmail != "" {
|
||||
u.Email = userFlagEmail
|
||||
}
|
||||
if userFlagAvatar != "default" {
|
||||
u.AvatarProvider = userFlagAvatar
|
||||
}
|
||||
|
||||
_, err := user.UpdateUser(s, u)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
log.Fatalf("Error updating the user: %s", err)
|
||||
}
|
||||
|
||||
if err := s.Commit(); err != nil {
|
||||
log.Fatalf("Error saving everything: %s", err)
|
||||
}
|
||||
|
||||
fmt.Println("User updated successfully.")
|
||||
},
|
||||
}
|
||||
|
||||
var userResetPasswordCmd = &cobra.Command{
|
||||
Use: "reset-password [user id]",
|
||||
Short: "Reset a users password, either through mailing them a reset link or directly.",
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
initialize.FullInit()
|
||||
},
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
u := getUserFromArg(s, args[0])
|
||||
|
||||
// By default we reset as usual, only with specific flag directly.
|
||||
if userFlagResetPasswordDirectly {
|
||||
err := user.UpdateUserPassword(s, u, getPasswordFromFlagOrInput())
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
log.Fatalf("Could not update user password: %s", err)
|
||||
}
|
||||
fmt.Println("Password updated successfully.")
|
||||
} else {
|
||||
err := user.RequestUserPasswordResetToken(s, u)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
log.Fatalf("Could not send password reset email: %s", err)
|
||||
}
|
||||
fmt.Println("Password reset email sent successfully.")
|
||||
}
|
||||
|
||||
if err := s.Commit(); err != nil {
|
||||
log.Fatalf("Could not send password reset email: %s", err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
var userChangeEnabledCmd = &cobra.Command{
|
||||
Use: "change-status [user id]",
|
||||
Short: "Enable or disable a user. Will toggle the current status if no flag (--enable or --disable) is provided.",
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
initialize.FullInit()
|
||||
},
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
u := getUserFromArg(s, args[0])
|
||||
|
||||
if userFlagEnableUser {
|
||||
u.IsActive = true
|
||||
} else if userFlagDisableUser {
|
||||
u.IsActive = false
|
||||
} else {
|
||||
u.IsActive = !u.IsActive
|
||||
}
|
||||
_, err := user.UpdateUser(s, u)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
log.Fatalf("Could not enable the user")
|
||||
}
|
||||
|
||||
if err := s.Commit(); err != nil {
|
||||
log.Fatalf("Error saving everything: %s", err)
|
||||
}
|
||||
|
||||
fmt.Printf("User status successfully changed, user is now active: %t.\n", u.IsActive)
|
||||
},
|
||||
}
|
||||
@@ -2,23 +2,25 @@
|
||||
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// 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 General Public License for more details.
|
||||
// GNU Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// You should have received a copy of the GNU Affero General Public Licensee
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/version"
|
||||
"fmt"
|
||||
"runtime"
|
||||
|
||||
"code.vikunja.io/api/pkg/version"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -30,6 +32,7 @@ var versionCmd = &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Print the version number of Vikunja",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Println("Vikunja api version " + version.Version)
|
||||
fmt.Printf("Vikunja api version %s\n", version.Version)
|
||||
fmt.Printf("Built with %s\n", runtime.Version())
|
||||
},
|
||||
}
|
||||
|
||||
@@ -2,31 +2,35 @@
|
||||
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// 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 General Public License for more details.
|
||||
// GNU Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// You should have received a copy of the GNU Affero General Public Licensee
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"os/signal"
|
||||
"time"
|
||||
|
||||
"code.vikunja.io/api/pkg/cron"
|
||||
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/initialize"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/routes"
|
||||
"code.vikunja.io/api/pkg/swagger"
|
||||
"code.vikunja.io/api/pkg/version"
|
||||
"context"
|
||||
"github.com/spf13/cobra"
|
||||
"os"
|
||||
"os/signal"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -36,6 +40,9 @@ func init() {
|
||||
var webCmd = &cobra.Command{
|
||||
Use: "web",
|
||||
Short: "Starts the rest api web server",
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
initialize.FullInit()
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
|
||||
// Version notification
|
||||
@@ -65,5 +72,6 @@ var webCmd = &cobra.Command{
|
||||
if err := e.Shutdown(ctx); err != nil {
|
||||
e.Logger.Fatal(err)
|
||||
}
|
||||
cron.Stop()
|
||||
},
|
||||
}
|
||||
|
||||
@@ -2,16 +2,16 @@
|
||||
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// 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 General Public License for more details.
|
||||
// GNU Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// 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 config
|
||||
@@ -21,10 +21,12 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"4d63.com/tz"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
@@ -33,6 +35,7 @@ type Key string
|
||||
|
||||
// These constants hold all config value keys
|
||||
const (
|
||||
// #nosec
|
||||
ServiceJWTSecret Key = `service.JWTSecret`
|
||||
ServiceInterface Key = `service.interface`
|
||||
ServiceFrontendurl Key = `service.frontendurl`
|
||||
@@ -46,6 +49,18 @@ const (
|
||||
ServiceEnableTaskAttachments Key = `service.enabletaskattachments`
|
||||
ServiceTimeZone Key = `service.timezone`
|
||||
ServiceEnableTaskComments Key = `service.enabletaskcomments`
|
||||
ServiceEnableTotp Key = `service.enabletotp`
|
||||
ServiceSentryDsn Key = `service.sentrydsn`
|
||||
ServiceTestingtoken Key = `service.testingtoken`
|
||||
ServiceEnableEmailReminders Key = `service.enableemailreminders`
|
||||
|
||||
AuthLocalEnabled Key = `auth.local.enabled`
|
||||
AuthOpenIDEnabled Key = `auth.openid.enabled`
|
||||
AuthOpenIDRedirectURL Key = `auth.openid.redirecturl`
|
||||
AuthOpenIDProviders Key = `auth.openid.providers`
|
||||
|
||||
LegalImprintURL Key = `legal.imprinturl`
|
||||
LegalPrivacyURL Key = `legal.privacyurl`
|
||||
|
||||
DatabaseType Key = `database.type`
|
||||
DatabaseHost Key = `database.host`
|
||||
@@ -71,19 +86,21 @@ const (
|
||||
MailerFromEmail Key = `mailer.fromemail`
|
||||
MailerQueuelength Key = `mailer.queuelength`
|
||||
MailerQueueTimeout Key = `mailer.queuetimeout`
|
||||
MailerForceSSL Key = `mailer.forcessl`
|
||||
|
||||
RedisEnabled Key = `redis.enabled`
|
||||
RedisHost Key = `redis.host`
|
||||
RedisPassword Key = `redis.password`
|
||||
RedisDB Key = `redis.db`
|
||||
|
||||
LogEnabled Key = `log.enabled`
|
||||
LogErrors Key = `log.errors`
|
||||
LogStandard Key = `log.standard`
|
||||
LogDatabase Key = `log.database`
|
||||
LogHTTP Key = `log.http`
|
||||
LogEcho Key = `log.echo`
|
||||
LogPath Key = `log.path`
|
||||
LogEnabled Key = `log.enabled`
|
||||
LogStandard Key = `log.standard`
|
||||
LogLevel Key = `log.level`
|
||||
LogDatabase Key = `log.database`
|
||||
LogDatabaseLevel Key = `log.databaselevel`
|
||||
LogHTTP Key = `log.http`
|
||||
LogEcho Key = `log.echo`
|
||||
LogPath Key = `log.path`
|
||||
|
||||
RateLimitEnabled Key = `ratelimit.enabled`
|
||||
RateLimitKind Key = `ratelimit.kind`
|
||||
@@ -94,14 +111,35 @@ 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`
|
||||
MigrationWunderlistEnable Key = `migration.wunderlist.enable`
|
||||
MigrationWunderlistClientID Key = `migration.wunderlist.clientid`
|
||||
MigrationWunderlistClientSecret Key = `migration.wunderlist.clientsecret`
|
||||
MigrationWunderlistRedirectURL Key = `migration.wunderlist.redirecturl`
|
||||
MigrationTodoistEnable Key = `migration.todoist.enable`
|
||||
MigrationTodoistClientID Key = `migration.todoist.clientid`
|
||||
MigrationTodoistClientSecret Key = `migration.todoist.clientsecret`
|
||||
MigrationTodoistRedirectURL Key = `migration.todoist.redirecturl`
|
||||
MigrationTrelloEnable Key = `migration.trello.enable`
|
||||
MigrationTrelloKey Key = `migration.trello.key`
|
||||
MigrationTrelloRedirectURL Key = `migration.trello.redirecturl`
|
||||
MigrationMicrosoftTodoEnable Key = `migration.microsofttodo.enable`
|
||||
MigrationMicrosoftTodoClientID Key = `migration.microsofttodo.clientid`
|
||||
MigrationMicrosoftTodoClientSecret Key = `migration.microsofttodo.clientsecret`
|
||||
MigrationMicrosoftTodoRedirectURL Key = `migration.microsofttodo.redirecturl`
|
||||
|
||||
CorsEnable Key = `cors.enable`
|
||||
CorsOrigins Key = `cors.origins`
|
||||
CorsMaxAge Key = `cors.maxage`
|
||||
|
||||
AvatarGravaterExpiration Key = `avatar.gravatarexpiration`
|
||||
|
||||
BackgroundsEnabled Key = `backgrounds.enabled`
|
||||
BackgroundsUploadEnabled Key = `backgrounds.providers.upload.enabled`
|
||||
BackgroundsUnsplashEnabled Key = `backgrounds.providers.unsplash.enabled`
|
||||
BackgroundsUnsplashAccessToken Key = `backgrounds.providers.unsplash.accesstoken`
|
||||
BackgroundsUnsplashApplicationID Key = `backgrounds.providers.unsplash.applicationid`
|
||||
|
||||
KeyvalueType Key = `keyvalue.type`
|
||||
)
|
||||
|
||||
// GetString returns a string config value
|
||||
@@ -134,6 +172,28 @@ func (k Key) GetStringSlice() []string {
|
||||
return viper.GetStringSlice(string(k))
|
||||
}
|
||||
|
||||
// Get returns the raw value from a config option
|
||||
func (k Key) Get() interface{} {
|
||||
return viper.Get(string(k))
|
||||
}
|
||||
|
||||
var timezone *time.Location
|
||||
|
||||
// GetTimeZone returns the time zone configured for vikunja
|
||||
// It is a separate function and not done through viper because that makes handling
|
||||
// it way easier, especially when testing.
|
||||
func GetTimeZone() *time.Location {
|
||||
if timezone == nil {
|
||||
loc, err := tz.LoadLocation(ServiceTimeZone.GetString())
|
||||
if err != nil {
|
||||
fmt.Printf("Error parsing time zone: %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
timezone = loc
|
||||
}
|
||||
return timezone
|
||||
}
|
||||
|
||||
// Set sets a value
|
||||
func (k Key) Set(i interface{}) {
|
||||
viper.Set(string(k), i)
|
||||
@@ -173,6 +233,12 @@ func InitDefaultConfig() {
|
||||
ServiceEnableTaskAttachments.setDefault(true)
|
||||
ServiceTimeZone.setDefault("GMT")
|
||||
ServiceEnableTaskComments.setDefault(true)
|
||||
ServiceEnableTotp.setDefault(true)
|
||||
ServiceEnableEmailReminders.setDefault(true)
|
||||
|
||||
// Auth
|
||||
AuthLocalEnabled.setDefault(true)
|
||||
AuthOpenIDEnabled.setDefault(false)
|
||||
|
||||
// Database
|
||||
DatabaseType.setDefault("sqlite")
|
||||
@@ -200,6 +266,7 @@ func InitDefaultConfig() {
|
||||
MailerFromEmail.setDefault("mail@vikunja")
|
||||
MailerQueuelength.setDefault(100)
|
||||
MailerQueueTimeout.setDefault(30)
|
||||
MailerForceSSL.setDefault(false)
|
||||
// Redis
|
||||
RedisEnabled.setDefault(false)
|
||||
RedisHost.setDefault("localhost:6379")
|
||||
@@ -207,9 +274,10 @@ func InitDefaultConfig() {
|
||||
RedisDB.setDefault(0)
|
||||
// Logger
|
||||
LogEnabled.setDefault(true)
|
||||
LogErrors.setDefault("stdout")
|
||||
LogStandard.setDefault("stdout")
|
||||
LogLevel.setDefault("INFO")
|
||||
LogDatabase.setDefault("off")
|
||||
LogDatabaseLevel.setDefault("WARNING")
|
||||
LogHTTP.setDefault("stdout")
|
||||
LogEcho.setDefault("off")
|
||||
LogPath.setDefault(ServiceRootpath.GetString() + "/logs")
|
||||
@@ -228,6 +296,17 @@ func InitDefaultConfig() {
|
||||
CorsMaxAge.setDefault(0)
|
||||
// Migration
|
||||
MigrationWunderlistEnable.setDefault(false)
|
||||
MigrationTodoistEnable.setDefault(false)
|
||||
MigrationTrelloEnable.setDefault(false)
|
||||
MigrationMicrosoftTodoEnable.setDefault(false)
|
||||
// Avatar
|
||||
AvatarGravaterExpiration.setDefault(3600)
|
||||
// List Backgrounds
|
||||
BackgroundsEnabled.setDefault(true)
|
||||
BackgroundsUploadEnabled.setDefault(true)
|
||||
BackgroundsUnsplashEnabled.setDefault(false)
|
||||
// Key Value
|
||||
KeyvalueType.setDefault("memory")
|
||||
}
|
||||
|
||||
// InitConfig initializes the config, sets defaults etc.
|
||||
@@ -244,14 +323,44 @@ func InitConfig() {
|
||||
// Load the config file
|
||||
viper.AddConfigPath(ServiceRootpath.GetString())
|
||||
viper.AddConfigPath("/etc/vikunja/")
|
||||
viper.AddConfigPath("$HOME/.config/vikunja")
|
||||
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
log.Printf("No home directory found, not using config from ~/.config/vikunja/. Error was: %s\n", err.Error())
|
||||
} else {
|
||||
viper.AddConfigPath(path.Join(homeDir, ".config", "vikunja"))
|
||||
}
|
||||
|
||||
viper.AddConfigPath(".")
|
||||
viper.SetConfigName("config")
|
||||
err := viper.ReadInConfig()
|
||||
err = viper.ReadInConfig()
|
||||
if err != nil {
|
||||
log.Println(err.Error())
|
||||
log.Println("Using defaults.")
|
||||
log.Println("Using default config.")
|
||||
return
|
||||
}
|
||||
|
||||
if CacheType.GetString() == "keyvalue" {
|
||||
CacheType.Set(KeyvalueType.GetString())
|
||||
}
|
||||
|
||||
if RateLimitStore.GetString() == "keyvalue" {
|
||||
RateLimitStore.Set(KeyvalueType.GetString())
|
||||
}
|
||||
|
||||
if AuthOpenIDRedirectURL.GetString() == "" {
|
||||
AuthOpenIDRedirectURL.Set(ServiceFrontendurl.GetString() + "auth/openid/")
|
||||
}
|
||||
|
||||
if MigrationTrelloRedirectURL.GetString() == "" {
|
||||
MigrationTrelloRedirectURL.Set(ServiceFrontendurl.GetString() + "migrate/trello")
|
||||
}
|
||||
|
||||
if MigrationMicrosoftTodoRedirectURL.GetString() == "" {
|
||||
MigrationMicrosoftTodoRedirectURL.Set(ServiceFrontendurl.GetString() + "migrate/microsoft-todo")
|
||||
}
|
||||
|
||||
log.Printf("Using config file: %s", viper.ConfigFileUsed())
|
||||
}
|
||||
|
||||
func random(length int) (string, error) {
|
||||
|
||||
40
pkg/cron/cron.go
Normal file
40
pkg/cron/cron.go
Normal file
@@ -0,0 +1,40 @@
|
||||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU 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 cron
|
||||
|
||||
import (
|
||||
"github.com/robfig/cron/v3"
|
||||
)
|
||||
|
||||
var c *cron.Cron
|
||||
|
||||
// Init starts the cron
|
||||
func Init() {
|
||||
c = cron.New()
|
||||
c.Start()
|
||||
}
|
||||
|
||||
// Schedule schedules a job as a cron job
|
||||
func Schedule(schedule string, f func()) (err error) {
|
||||
_, err = c.AddFunc(schedule, f)
|
||||
return
|
||||
}
|
||||
|
||||
// Stop stops the cron scheduler
|
||||
func Stop() {
|
||||
c.Stop()
|
||||
}
|
||||
81
pkg/db/db.go
81
pkg/db/db.go
@@ -2,33 +2,36 @@
|
||||
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// 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 General Public License for more details.
|
||||
// GNU Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// 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 db
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"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"
|
||||
|
||||
xrc "gitea.com/xorm/xorm-redis-cache"
|
||||
"xorm.io/xorm/caches"
|
||||
"xorm.io/xorm/schemas"
|
||||
|
||||
_ "github.com/go-sql-driver/mysql" // Because.
|
||||
_ "github.com/lib/pq" // Because.
|
||||
@@ -51,27 +54,35 @@ func CreateDBEngine() (engine *xorm.Engine, err error) {
|
||||
}
|
||||
|
||||
// Use Mysql if set
|
||||
if config.DatabaseType.GetString() == "mysql" {
|
||||
switch config.DatabaseType.GetString() {
|
||||
case "mysql":
|
||||
engine, err = initMysqlEngine()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else if config.DatabaseType.GetString() == "postgres" {
|
||||
case "postgres":
|
||||
engine, err = initPostgresEngine()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
case "sqlite":
|
||||
// Otherwise use sqlite
|
||||
engine, err = initSqliteEngine()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
default:
|
||||
log.Fatalf("Unknown database type %s", config.DatabaseType.GetString())
|
||||
}
|
||||
|
||||
engine.SetTZLocation(config.GetTimeZone()) // Vikunja's timezone
|
||||
loc, err := time.LoadLocation("GMT") // The db data timezone
|
||||
if err != nil {
|
||||
log.Fatalf("Error parsing time zone: %s", err)
|
||||
}
|
||||
engine.SetTZDatabase(loc)
|
||||
engine.SetMapper(core.GonicMapper{})
|
||||
logger := xorm.NewSimpleLogger(log.GetLogWriter("database"))
|
||||
logger.ShowSQL(config.LogDatabase.GetString() != "off")
|
||||
logger := log.NewXormLogger("")
|
||||
engine.SetLogger(logger)
|
||||
|
||||
// Cache
|
||||
@@ -79,10 +90,10 @@ func CreateDBEngine() (engine *xorm.Engine, err error) {
|
||||
if config.CacheEnabled.GetBool() {
|
||||
switch config.CacheType.GetString() {
|
||||
case "memory":
|
||||
cacher := xorm.NewLRUCacher(xorm.NewMemoryStore(), config.CacheMaxElementSize.GetInt())
|
||||
cacher := caches.NewLRUCacher(caches.NewMemoryStore(), config.CacheMaxElementSize.GetInt())
|
||||
engine.SetDefaultCacher(cacher)
|
||||
case "redis":
|
||||
cacher := xrc.NewRedisCacher(config.RedisEnabled.GetString(), config.RedisPassword.GetString(), xrc.DEFAULT_EXPIRATION, engine.Logger())
|
||||
cacher := xrc.NewRedisCacher(config.RedisHost.GetString(), config.RedisPassword.GetString(), xrc.DEFAULT_EXPIRATION, engine.Logger())
|
||||
engine.SetDefaultCacher(cacher)
|
||||
default:
|
||||
log.Info("Did not find a valid cache type. Caching disabled. Please refer to the docs for poosible cache types.")
|
||||
@@ -167,5 +178,47 @@ func initSqliteEngine() (engine *xorm.Engine, err error) {
|
||||
path = "./db.db"
|
||||
}
|
||||
|
||||
// Try opening the db file to return a better error message if that does not work
|
||||
var exists = true
|
||||
if _, err := os.Stat(path); err != nil {
|
||||
exists = !os.IsNotExist(err)
|
||||
}
|
||||
file, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not open database file [uid=%d, gid=%d]: %s", os.Getuid(), os.Getgid(), err)
|
||||
}
|
||||
_ = file.Close() // We directly close the file because we only want to check if it is writable. It will be reopened lazily later by xorm.
|
||||
|
||||
if !exists {
|
||||
_ = os.Remove(path) // Remove the file to not prevent the db from creating another one
|
||||
}
|
||||
|
||||
return xorm.NewEngine("sqlite3", path)
|
||||
}
|
||||
|
||||
// WipeEverything wipes all tables and their data. Use with caution...
|
||||
func WipeEverything() error {
|
||||
|
||||
tables, err := x.DBMetas()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, t := range tables {
|
||||
if err := x.DropTables(t.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewSession creates a new xorm session
|
||||
func NewSession() *xorm.Session {
|
||||
return x.NewSession()
|
||||
}
|
||||
|
||||
// Type returns the db type of the currently configured db
|
||||
func Type() schemas.DBType {
|
||||
return x.Dialect().URI().DBType
|
||||
}
|
||||
|
||||
77
pkg/db/dump.go
Normal file
77
pkg/db/dump.go
Normal file
@@ -0,0 +1,77 @@
|
||||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU 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 db
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"xorm.io/xorm/schemas"
|
||||
)
|
||||
|
||||
// Dump dumps all database tables
|
||||
func Dump() (data map[string][]byte, err error) {
|
||||
tables, err := x.DBMetas()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
data = make(map[string][]byte, len(tables))
|
||||
for _, table := range tables {
|
||||
entries := []map[string]interface{}{}
|
||||
err := x.Table(table.Name).Find(&entries)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data[table.Name], err = json.Marshal(entries)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Restore restores a table with all its entries
|
||||
func Restore(table string, contents []map[string]interface{}) (err error) {
|
||||
|
||||
for _, content := range contents {
|
||||
if _, err := x.Table(table).Insert(content); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// RestoreAndTruncate removes all content from the table before restoring it from the contents map
|
||||
func RestoreAndTruncate(table string, contents []map[string]interface{}) (err error) {
|
||||
if _, err := x.IsTableExist(table); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if x.Dialect().URI().DBType == schemas.SQLITE {
|
||||
if _, err := x.Query("DELETE FROM " + table); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if _, err := x.Query("TRUNCATE TABLE ?", table); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return Restore(table, contents)
|
||||
}
|
||||
209
pkg/db/fixtures/buckets.yml
Normal file
209
pkg/db/fixtures/buckets.yml
Normal file
@@ -0,0 +1,209 @@
|
||||
- id: 1
|
||||
title: testbucket1
|
||||
list_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
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 2
|
||||
title: testbucket2
|
||||
list_id: 1
|
||||
created_by_id: 1
|
||||
limit: 3
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 3
|
||||
title: testbucket3
|
||||
list_id: 1
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 4
|
||||
title: testbucket4 - other list
|
||||
list_id: 2
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
# The following are not or only partly owned by user 1
|
||||
- id: 5
|
||||
title: testbucket5
|
||||
list_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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
- id: 34
|
||||
title: testbucket34
|
||||
list_id: 18
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
@@ -1,5 +1,5 @@
|
||||
- id: 1
|
||||
name: test
|
||||
size: 100
|
||||
created_unix: 1570998791
|
||||
created: 2019-10-13 20:33:11
|
||||
created_by_id: 1
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
- id: 1
|
||||
task_id: 1
|
||||
label_id: 4
|
||||
created: 0
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 2
|
||||
task_id: 2
|
||||
label_id: 4
|
||||
created: 0
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 3
|
||||
task_id: 35
|
||||
label_id: 4
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 4
|
||||
task_id: 36
|
||||
label_id: 4
|
||||
created: 2018-12-01 15:13:12
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
- id: 1
|
||||
title: 'Label #1'
|
||||
created_by_id: 1
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 2
|
||||
title: 'Label #2'
|
||||
created_by_id: 1
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 3
|
||||
title: 'Label #3 - other user'
|
||||
created_by_id: 2
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 4
|
||||
title: 'Label #4 - visible via other task'
|
||||
created_by_id: 2
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
@@ -4,21 +4,21 @@
|
||||
right: 0
|
||||
sharing_type: 1
|
||||
shared_by_id: 1
|
||||
created: 0
|
||||
updated: 0
|
||||
created: 2018-12-01 15:13:12
|
||||
updated: 2018-12-02 15:13:12
|
||||
- id: 2
|
||||
hash: test2
|
||||
list_id: 2
|
||||
right: 1
|
||||
sharing_type: 1
|
||||
shared_by_id: 1
|
||||
created: 0
|
||||
updated: 0
|
||||
created: 2018-12-01 15:13:12
|
||||
updated: 2018-12-02 15:13:12
|
||||
- id: 3
|
||||
hash: test3
|
||||
list_id: 3
|
||||
right: 2
|
||||
sharing_type: 1
|
||||
shared_by_id: 1
|
||||
created: 0
|
||||
updated: 0
|
||||
created: 2018-12-01 15:13:12
|
||||
updated: 2018-12-02 15:13:12
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
identifier: test1
|
||||
owner_id: 1
|
||||
namespace_id: 1
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
-
|
||||
id: 2
|
||||
title: Test2
|
||||
@@ -14,8 +14,8 @@
|
||||
identifier: test2
|
||||
owner_id: 3
|
||||
namespace_id: 1
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
-
|
||||
id: 3
|
||||
title: Test3
|
||||
@@ -23,8 +23,8 @@
|
||||
identifier: test3
|
||||
owner_id: 3
|
||||
namespace_id: 2
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
-
|
||||
id: 4
|
||||
title: Test4
|
||||
@@ -32,8 +32,8 @@
|
||||
identifier: test4
|
||||
owner_id: 3
|
||||
namespace_id: 3
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
-
|
||||
id: 5
|
||||
title: Test5
|
||||
@@ -41,8 +41,8 @@
|
||||
identifier: test5
|
||||
owner_id: 5
|
||||
namespace_id: 5
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
-
|
||||
id: 6
|
||||
title: Test6
|
||||
@@ -50,8 +50,8 @@
|
||||
identifier: test6
|
||||
owner_id: 6
|
||||
namespace_id: 6
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
-
|
||||
id: 7
|
||||
title: Test7
|
||||
@@ -59,8 +59,8 @@
|
||||
identifier: test7
|
||||
owner_id: 6
|
||||
namespace_id: 6
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
-
|
||||
id: 8
|
||||
title: Test8
|
||||
@@ -68,8 +68,8 @@
|
||||
identifier: test8
|
||||
owner_id: 6
|
||||
namespace_id: 6
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
-
|
||||
id: 9
|
||||
title: Test9
|
||||
@@ -77,8 +77,8 @@
|
||||
identifier: test9
|
||||
owner_id: 6
|
||||
namespace_id: 6
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
-
|
||||
id: 10
|
||||
title: Test10
|
||||
@@ -86,8 +86,8 @@
|
||||
identifier: test10
|
||||
owner_id: 6
|
||||
namespace_id: 6
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
-
|
||||
id: 11
|
||||
title: Test11
|
||||
@@ -95,8 +95,8 @@
|
||||
identifier: test11
|
||||
owner_id: 6
|
||||
namespace_id: 6
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
-
|
||||
id: 12
|
||||
title: Test12
|
||||
@@ -104,8 +104,8 @@
|
||||
identifier: test12
|
||||
owner_id: 6
|
||||
namespace_id: 7
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
-
|
||||
id: 13
|
||||
title: Test13
|
||||
@@ -113,8 +113,8 @@
|
||||
identifier: test13
|
||||
owner_id: 6
|
||||
namespace_id: 8
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
-
|
||||
id: 14
|
||||
title: Test14
|
||||
@@ -122,8 +122,8 @@
|
||||
identifier: test14
|
||||
owner_id: 6
|
||||
namespace_id: 9
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
-
|
||||
id: 15
|
||||
title: Test15
|
||||
@@ -131,8 +131,8 @@
|
||||
identifier: test15
|
||||
owner_id: 6
|
||||
namespace_id: 10
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
-
|
||||
id: 16
|
||||
title: Test16
|
||||
@@ -140,8 +140,8 @@
|
||||
identifier: test16
|
||||
owner_id: 6
|
||||
namespace_id: 11
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
-
|
||||
id: 17
|
||||
title: Test17
|
||||
@@ -149,8 +149,8 @@
|
||||
identifier: test17
|
||||
owner_id: 6
|
||||
namespace_id: 12
|
||||
updated: 0
|
||||
created: 0
|
||||
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.
|
||||
-
|
||||
@@ -160,8 +160,8 @@
|
||||
identifier: test18
|
||||
owner_id: 7
|
||||
namespace_id: 13
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
-
|
||||
id: 19
|
||||
title: Test19
|
||||
@@ -169,8 +169,9 @@
|
||||
identifier: test19
|
||||
owner_id: 7
|
||||
namespace_id: 14
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
# User 1 does not have access to this list
|
||||
-
|
||||
id: 20
|
||||
title: Test20
|
||||
@@ -178,5 +179,34 @@
|
||||
identifier: test20
|
||||
owner_id: 13
|
||||
namespace_id: 15
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
-
|
||||
id: 21
|
||||
title: Test21 archived through namespace
|
||||
description: Lorem Ipsum
|
||||
identifier: test21
|
||||
owner_id: 1
|
||||
namespace_id: 16
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
-
|
||||
id: 22
|
||||
title: Test22 archived individually
|
||||
description: Lorem Ipsum
|
||||
identifier: test22
|
||||
owner_id: 1
|
||||
namespace_id: 1
|
||||
is_archived: 1
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
-
|
||||
id: 23
|
||||
title: Test23
|
||||
description: Lorem Ipsum
|
||||
identifier: test23
|
||||
owner_id: 12
|
||||
namespace_id: 17
|
||||
is_favorite: true
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
|
||||
@@ -1,78 +1,90 @@
|
||||
- id: 1
|
||||
name: testnamespace
|
||||
title: testnamespace
|
||||
description: Lorem Ipsum
|
||||
owner_id: 1
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 2
|
||||
name: testnamespace2
|
||||
title: testnamespace2
|
||||
description: Lorem Ipsum
|
||||
owner_id: 2
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 3
|
||||
name: testnamespace3
|
||||
title: testnamespace3
|
||||
description: Lorem Ipsum
|
||||
owner_id: 3
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 6
|
||||
name: testnamespace6
|
||||
title: testnamespace6
|
||||
description: Lorem Ipsum
|
||||
owner_id: 6
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 7
|
||||
name: testnamespace7
|
||||
title: testnamespace7
|
||||
description: Lorem Ipsum
|
||||
owner_id: 6
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 8
|
||||
name: testnamespace8
|
||||
title: testnamespace8
|
||||
description: Lorem Ipsum
|
||||
owner_id: 6
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 9
|
||||
name: testnamespace9
|
||||
title: testnamespace9
|
||||
description: Lorem Ipsum
|
||||
owner_id: 6
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 10
|
||||
name: testnamespace10
|
||||
title: testnamespace10
|
||||
description: Lorem Ipsum
|
||||
owner_id: 6
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 11
|
||||
name: testnamespace11
|
||||
title: testnamespace11
|
||||
description: Lorem Ipsum
|
||||
owner_id: 6
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 12
|
||||
name: testnamespace12
|
||||
title: testnamespace12
|
||||
description: Lorem Ipsum
|
||||
owner_id: 6
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 13
|
||||
name: testnamespace13
|
||||
title: testnamespace13
|
||||
description: Lorem Ipsum
|
||||
owner_id: 7
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 14
|
||||
name: testnamespace14
|
||||
title: testnamespace14
|
||||
description: Lorem Ipsum
|
||||
owner_id: 7
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 15
|
||||
name: testnamespace15
|
||||
title: testnamespace15
|
||||
description: Lorem Ipsum
|
||||
owner_id: 13
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 16
|
||||
title: Archived testnamespace16
|
||||
owner_id: 1
|
||||
is_archived: 1
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 17
|
||||
title: testnamespace17
|
||||
description: Lorem Ipsum
|
||||
owner_id: 12
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
|
||||
6
pkg/db/fixtures/saved_filters.yml
Normal file
6
pkg/db/fixtures/saved_filters.yml
Normal file
@@ -0,0 +1,6 @@
|
||||
- id: 1
|
||||
filters: '{"sort_by":null,"order_by":null,"filter_by":["start_date","end_date","due_date"],"filter_value":["2018-12-11T03:46:40+00:00","2018-12-13T11:20:01+00:00","2018-11-29T14:00:00+00:00"],"filter_comparator":["greater","less","greater"],"filter_concat":"","filter_include_nulls":false}'
|
||||
title: testfilter1
|
||||
owner_id: 1
|
||||
updated: 2020-09-08 15:13:12
|
||||
created: 2020-09-08 14:13:12
|
||||
@@ -1,8 +1,16 @@
|
||||
- id: 1
|
||||
task_id: 30
|
||||
user_id: 1
|
||||
created: 0
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 2
|
||||
task_id: 30
|
||||
user_id: 2
|
||||
created: 0
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 3
|
||||
task_id: 35
|
||||
user_id: 2
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 4
|
||||
task_id: 36
|
||||
user_id: 2
|
||||
created: 2018-12-01 15:13:12
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
task_id: 1
|
||||
file_id: 1
|
||||
created_by_id: 1
|
||||
created: 0
|
||||
created: 2018-12-01 15:13:12
|
||||
# The file for this attachment does not exist
|
||||
- id: 2
|
||||
task_id: 1
|
||||
file_id: 9999
|
||||
created_by_id: 1
|
||||
created: 0
|
||||
created: 2018-12-01 15:13:12
|
||||
|
||||
@@ -2,83 +2,95 @@
|
||||
comment: Lorem Ipsum Dolor Sit Amet
|
||||
author_id: 1
|
||||
task_id: 1
|
||||
created: 1582135626
|
||||
updated: 1582135626
|
||||
created: 2020-02-19 18:07:06
|
||||
updated: 2020-02-19 18:07:06
|
||||
- id: 2
|
||||
comment: comment 2
|
||||
author_id: 5
|
||||
task_id: 14
|
||||
created: 1582135626
|
||||
updated: 1582135626
|
||||
created: 2020-02-19 18:07:06
|
||||
updated: 2020-02-19 18:07:06
|
||||
- id: 3
|
||||
comment: comment 3
|
||||
author_id: 5
|
||||
task_id: 15
|
||||
created: 1582135626
|
||||
updated: 1582135626
|
||||
created: 2020-02-19 18:07:06
|
||||
updated: 2020-02-19 18:07:06
|
||||
- id: 4
|
||||
comment: comment 4
|
||||
author_id: 6
|
||||
task_id: 16
|
||||
created: 1582135626
|
||||
updated: 1582135626
|
||||
created: 2020-02-19 18:07:06
|
||||
updated: 2020-02-19 18:07:06
|
||||
- id: 5
|
||||
comment: comment 5
|
||||
author_id: 6
|
||||
task_id: 17
|
||||
created: 1582135626
|
||||
updated: 1582135626
|
||||
created: 2020-02-19 18:07:06
|
||||
updated: 2020-02-19 18:07:06
|
||||
- id: 6
|
||||
comment: comment 6
|
||||
author_id: 6
|
||||
task_id: 18
|
||||
created: 1582135626
|
||||
updated: 1582135626
|
||||
created: 2020-02-19 18:07:06
|
||||
updated: 2020-02-19 18:07:06
|
||||
- id: 7
|
||||
comment: comment 7
|
||||
author_id: 6
|
||||
task_id: 19
|
||||
created: 1582135626
|
||||
updated: 1582135626
|
||||
created: 2020-02-19 18:07:06
|
||||
updated: 2020-02-19 18:07:06
|
||||
- id: 8
|
||||
comment: comment 8
|
||||
author_id: 6
|
||||
task_id: 20
|
||||
created: 1582135626
|
||||
updated: 1582135626
|
||||
created: 2020-02-19 18:07:06
|
||||
updated: 2020-02-19 18:07:06
|
||||
- id: 9
|
||||
comment: comment 9
|
||||
author_id: 6
|
||||
task_id: 21
|
||||
created: 1582135626
|
||||
updated: 1582135626
|
||||
created: 2020-02-19 18:07:06
|
||||
updated: 2020-02-19 18:07:06
|
||||
- id: 10
|
||||
comment: comment 10
|
||||
author_id: 6
|
||||
task_id: 22
|
||||
created: 1582135626
|
||||
updated: 1582135626
|
||||
created: 2020-02-19 18:07:06
|
||||
updated: 2020-02-19 18:07:06
|
||||
- id: 11
|
||||
comment: comment 11
|
||||
author_id: 6
|
||||
task_id: 23
|
||||
created: 1582135626
|
||||
updated: 1582135626
|
||||
created: 2020-02-19 18:07:06
|
||||
updated: 2020-02-19 18:07:06
|
||||
- id: 12
|
||||
comment: comment 12
|
||||
author_id: 6
|
||||
task_id: 24
|
||||
created: 1582135626
|
||||
updated: 1582135626
|
||||
created: 2020-02-19 18:07:06
|
||||
updated: 2020-02-19 18:07:06
|
||||
- id: 13
|
||||
comment: comment 13
|
||||
author_id: 6
|
||||
task_id: 25
|
||||
created: 1582135626
|
||||
updated: 1582135626
|
||||
created: 2020-02-19 18:07:06
|
||||
updated: 2020-02-19 18:07:06
|
||||
- id: 14
|
||||
comment: comment 14
|
||||
author_id: 6
|
||||
task_id: 26
|
||||
created: 1582135626
|
||||
updated: 1582135626
|
||||
created: 2020-02-19 18:07:06
|
||||
updated: 2020-02-19 18:07:06
|
||||
- id: 15
|
||||
comment: comment 15
|
||||
author_id: 1
|
||||
task_id: 35
|
||||
created: 2020-02-19 18:07:06
|
||||
updated: 2020-02-19 18:07:06
|
||||
- id: 16
|
||||
comment: comment 16
|
||||
author_id: 1
|
||||
task_id: 36
|
||||
created: 2020-02-19 18:07:06
|
||||
updated: 2020-02-19 18:07:06
|
||||
|
||||
@@ -3,10 +3,34 @@
|
||||
other_task_id: 29
|
||||
relation_kind: 'subtask'
|
||||
created_by_id: 1
|
||||
created: 0
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 2
|
||||
task_id: 29
|
||||
other_task_id: 1
|
||||
relation_kind: 'parenttask'
|
||||
created_by_id: 1
|
||||
created: 0
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 3
|
||||
task_id: 35
|
||||
other_task_id: 1
|
||||
relation_kind: 'related'
|
||||
created_by_id: 1
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 4
|
||||
task_id: 35
|
||||
other_task_id: 1
|
||||
relation_kind: 'related'
|
||||
created_by_id: 1
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 5
|
||||
task_id: 36
|
||||
other_task_id: 1
|
||||
relation_kind: 'related'
|
||||
created_by_id: 1
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 6
|
||||
task_id: 36
|
||||
other_task_id: 1
|
||||
relation_kind: 'related'
|
||||
created_by_id: 1
|
||||
created: 2018-12-01 15:13:12
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
- id: 1
|
||||
task_id: 27
|
||||
reminder_unix: 1543626724
|
||||
created: 1543626724
|
||||
reminder: 2018-12-01 01:12:04
|
||||
created: 2018-12-01 01:12:04
|
||||
- id: 2
|
||||
task_id: 27
|
||||
reminder_unix: 1543626824
|
||||
created: 1543626724
|
||||
reminder: 2018-12-01 01:13:44
|
||||
created: 2018-12-01 01:12:04
|
||||
|
||||
@@ -1,255 +1,342 @@
|
||||
- id: 1
|
||||
text: 'task #1'
|
||||
title: 'task #1'
|
||||
description: 'Lorem Ipsum'
|
||||
done: false
|
||||
created_by_id: 1
|
||||
list_id: 1
|
||||
index: 1
|
||||
created: 1543626724
|
||||
updated: 1543626724
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
bucket_id: 1
|
||||
is_favorite: true
|
||||
- id: 2
|
||||
text: 'task #2 done'
|
||||
title: 'task #2 done'
|
||||
done: true
|
||||
created_by_id: 1
|
||||
list_id: 1
|
||||
index: 2
|
||||
created: 1543626724
|
||||
updated: 1543626724
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
bucket_id: 1
|
||||
- id: 3
|
||||
text: 'task #3 high prio'
|
||||
title: 'task #3 high prio'
|
||||
done: false
|
||||
created_by_id: 1
|
||||
list_id: 1
|
||||
index: 3
|
||||
created: 1543626724
|
||||
updated: 1543626724
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
priority: 100
|
||||
bucket_id: 2
|
||||
- id: 4
|
||||
text: 'task #4 low prio'
|
||||
title: 'task #4 low prio'
|
||||
done: false
|
||||
created_by_id: 1
|
||||
list_id: 1
|
||||
index: 4
|
||||
created: 1543626724
|
||||
updated: 1543626724
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
priority: 1
|
||||
bucket_id: 2
|
||||
- id: 5
|
||||
text: 'task #5 higher due date'
|
||||
title: 'task #5 higher due date'
|
||||
done: false
|
||||
created_by_id: 1
|
||||
list_id: 1
|
||||
index: 5
|
||||
created: 1543626724
|
||||
updated: 1543626724
|
||||
due_date_unix: 1543636724
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
due_date: 2018-12-01 03:58:44
|
||||
bucket_id: 2
|
||||
- id: 6
|
||||
text: 'task #6 lower due date'
|
||||
title: 'task #6 lower due date'
|
||||
done: false
|
||||
created_by_id: 1
|
||||
list_id: 1
|
||||
index: 6
|
||||
created: 1543626724
|
||||
updated: 1543626724
|
||||
due_date_unix: 1543616724
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
due_date: 2018-11-30 22:25:24
|
||||
bucket_id: 3
|
||||
- id: 7
|
||||
text: 'task #7 with start date'
|
||||
title: 'task #7 with start date'
|
||||
done: false
|
||||
created_by_id: 1
|
||||
list_id: 1
|
||||
index: 7
|
||||
created: 1543626724
|
||||
updated: 1543626724
|
||||
start_date_unix: 1544600000
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
start_date: 2018-12-12 07:33:20
|
||||
bucket_id: 3
|
||||
- id: 8
|
||||
text: 'task #8 with end date'
|
||||
title: 'task #8 with end date'
|
||||
done: false
|
||||
created_by_id: 1
|
||||
list_id: 1
|
||||
index: 8
|
||||
created: 1543626724
|
||||
updated: 1543626724
|
||||
end_date_unix: 1544700000
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
end_date: 2018-12-13 11:20:00
|
||||
bucket_id: 3
|
||||
- id: 9
|
||||
text: 'task #9 with start and end date'
|
||||
title: 'task #9 with start and end date'
|
||||
done: false
|
||||
created_by_id: 1
|
||||
list_id: 1
|
||||
index: 9
|
||||
created: 1543626724
|
||||
updated: 1543626724
|
||||
start_date_unix: 1544600000
|
||||
end_date_unix: 1544700000
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
start_date: 2018-12-12 07:33:20
|
||||
end_date: 2018-12-13 11:20:00
|
||||
bucket_id: 1
|
||||
- id: 10
|
||||
text: 'task #10 basic'
|
||||
title: 'task #10 basic'
|
||||
done: false
|
||||
created_by_id: 1
|
||||
list_id: 1
|
||||
index: 10
|
||||
created: 1543626724
|
||||
updated: 1543626724
|
||||
bucket_id: 1
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
- id: 11
|
||||
text: 'task #11 basic'
|
||||
title: 'task #11 basic'
|
||||
done: false
|
||||
created_by_id: 1
|
||||
list_id: 1
|
||||
index: 11
|
||||
created: 1543626724
|
||||
updated: 1543626724
|
||||
bucket_id: 1
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
- id: 12
|
||||
text: 'task #12 basic'
|
||||
title: 'task #12 basic'
|
||||
done: false
|
||||
created_by_id: 1
|
||||
list_id: 1
|
||||
index: 12
|
||||
created: 1543626724
|
||||
updated: 1543626724
|
||||
bucket_id: 1
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
- id: 13
|
||||
text: 'task #13 basic other list'
|
||||
title: 'task #13 basic other list'
|
||||
done: false
|
||||
created_by_id: 1
|
||||
list_id: 2
|
||||
index: 1
|
||||
created: 1543626724
|
||||
updated: 1543626724
|
||||
bucket_id: 4
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
- id: 14
|
||||
text: 'task #14 basic other list'
|
||||
title: 'task #14 basic other list'
|
||||
done: false
|
||||
created_by_id: 5
|
||||
list_id: 5
|
||||
index: 1
|
||||
created: 1543626724
|
||||
updated: 1543626724
|
||||
bucket_id: 18
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
- id: 15
|
||||
text: 'task #15'
|
||||
title: 'task #15'
|
||||
done: false
|
||||
created_by_id: 6
|
||||
list_id: 6
|
||||
index: 1
|
||||
created: 1543626724
|
||||
updated: 1543626724
|
||||
bucket_id: 6
|
||||
is_favorite: true
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
- id: 16
|
||||
text: 'task #16'
|
||||
title: 'task #16'
|
||||
done: false
|
||||
created_by_id: 6
|
||||
list_id: 7
|
||||
index: 1
|
||||
created: 1543626724
|
||||
updated: 1543626724
|
||||
bucket_id: 7
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
- id: 17
|
||||
text: 'task #17'
|
||||
title: 'task #17'
|
||||
done: false
|
||||
created_by_id: 6
|
||||
list_id: 8
|
||||
index: 1
|
||||
created: 1543626724
|
||||
updated: 1543626724
|
||||
bucket_id: 8
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
- id: 18
|
||||
text: 'task #18'
|
||||
title: 'task #18'
|
||||
done: false
|
||||
created_by_id: 6
|
||||
list_id: 9
|
||||
index: 1
|
||||
created: 1543626724
|
||||
updated: 1543626724
|
||||
bucket_id: 9
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
- id: 19
|
||||
text: 'task #19'
|
||||
title: 'task #19'
|
||||
done: false
|
||||
created_by_id: 6
|
||||
list_id: 10
|
||||
index: 1
|
||||
created: 1543626724
|
||||
updated: 1543626724
|
||||
bucket_id: 10
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
- id: 20
|
||||
text: 'task #20'
|
||||
title: 'task #20'
|
||||
done: false
|
||||
created_by_id: 6
|
||||
list_id: 11
|
||||
index: 1
|
||||
created: 1543626724
|
||||
updated: 1543626724
|
||||
bucket_id: 11
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
- id: 21
|
||||
text: 'task #21'
|
||||
title: 'task #21'
|
||||
done: false
|
||||
created_by_id: 6
|
||||
list_id: 12
|
||||
index: 1
|
||||
created: 1543626724
|
||||
updated: 1543626724
|
||||
bucket_id: 12
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
- id: 22
|
||||
text: 'task #22'
|
||||
title: 'task #22'
|
||||
done: false
|
||||
created_by_id: 6
|
||||
list_id: 13
|
||||
index: 1
|
||||
created: 1543626724
|
||||
updated: 1543626724
|
||||
bucket_id: 13
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
- id: 23
|
||||
text: 'task #23'
|
||||
title: 'task #23'
|
||||
done: false
|
||||
created_by_id: 6
|
||||
list_id: 14
|
||||
index: 1
|
||||
created: 1543626724
|
||||
updated: 1543626724
|
||||
bucket_id: 14
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
- id: 24
|
||||
text: 'task #24'
|
||||
title: 'task #24'
|
||||
done: false
|
||||
created_by_id: 6
|
||||
list_id: 15
|
||||
index: 1
|
||||
created: 1543626724
|
||||
updated: 1543626724
|
||||
bucket_id: 15
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
- id: 25
|
||||
text: 'task #25'
|
||||
title: 'task #25'
|
||||
done: false
|
||||
created_by_id: 6
|
||||
list_id: 16
|
||||
index: 1
|
||||
created: 1543626724
|
||||
updated: 1543626724
|
||||
bucket_id: 16
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
- id: 26
|
||||
text: 'task #26'
|
||||
title: 'task #26'
|
||||
done: false
|
||||
created_by_id: 6
|
||||
list_id: 17
|
||||
index: 1
|
||||
created: 1543626724
|
||||
updated: 1543626724
|
||||
bucket_id: 17
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
- id: 27
|
||||
text: 'task #27 with reminders'
|
||||
title: 'task #27 with reminders'
|
||||
done: false
|
||||
created_by_id: 1
|
||||
list_id: 1
|
||||
index: 12
|
||||
created: 1543626724
|
||||
updated: 1543626724
|
||||
bucket_id: 1
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
- id: 28
|
||||
text: 'task #28 with repeat after'
|
||||
title: 'task #28 with repeat after'
|
||||
done: false
|
||||
created_by_id: 1
|
||||
repeat_after: 3600
|
||||
list_id: 1
|
||||
index: 13
|
||||
created: 1543626724
|
||||
updated: 1543626724
|
||||
bucket_id: 1
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
- id: 29
|
||||
text: 'task #29 with parent task (1)'
|
||||
title: 'task #29 with parent task (1)'
|
||||
done: false
|
||||
created_by_id: 1
|
||||
list_id: 1
|
||||
index: 14
|
||||
created: 1543626724
|
||||
updated: 1543626724
|
||||
bucket_id: 1
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
- id: 30
|
||||
text: 'task #30 with assignees'
|
||||
title: 'task #30 with assignees'
|
||||
done: false
|
||||
created_by_id: 1
|
||||
list_id: 1
|
||||
index: 15
|
||||
created: 1543626724
|
||||
updated: 1543626724
|
||||
bucket_id: 1
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
- id: 31
|
||||
text: 'task #31 with color'
|
||||
title: 'task #31 with color'
|
||||
done: false
|
||||
created_by_id: 1
|
||||
list_id: 1
|
||||
index: 16
|
||||
hex_color: f0f0f0
|
||||
created: 1543626724
|
||||
updated: 1543626724
|
||||
bucket_id: 1
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
- id: 32
|
||||
text: 'task #32'
|
||||
title: 'task #32'
|
||||
done: false
|
||||
created_by_id: 1
|
||||
list_id: 3
|
||||
index: 1
|
||||
created: 1543626724
|
||||
updated: 1543626724
|
||||
bucket_id: 21
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
- id: 33
|
||||
text: 'task #33 with percent done'
|
||||
title: 'task #33 with percent done'
|
||||
done: false
|
||||
created_by_id: 1
|
||||
list_id: 1
|
||||
index: 17
|
||||
percent_done: 0.5
|
||||
created: 1543626724
|
||||
updated: 1543626724
|
||||
bucket_id: 1
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
# This task is forbidden for user1
|
||||
- id: 34
|
||||
text: 'task #34'
|
||||
title: 'task #34'
|
||||
done: false
|
||||
created_by_id: 13
|
||||
list_id: 20
|
||||
index: 20
|
||||
created: 1543626724
|
||||
updated: 1543626724
|
||||
bucket_id: 5
|
||||
is_favorite: true
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
- id: 35
|
||||
title: 'task #35'
|
||||
done: false
|
||||
created_by_id: 1
|
||||
list_id: 21
|
||||
index: 1
|
||||
bucket_id: 19
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
- id: 36
|
||||
title: 'task #36'
|
||||
done: false
|
||||
created_by_id: 1
|
||||
list_id: 22
|
||||
index: 1
|
||||
bucket_id: 20
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
|
||||
|
||||
|
||||
@@ -2,47 +2,47 @@
|
||||
team_id: 1
|
||||
list_id: 3
|
||||
right: 0
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
# This team has read only access on list 6
|
||||
- id: 2
|
||||
team_id: 2
|
||||
list_id: 6
|
||||
right: 0
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
# This team has write access on list 7
|
||||
- id: 3
|
||||
team_id: 3
|
||||
list_id: 7
|
||||
right: 1
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
# This team has admin access on list 8
|
||||
- id: 4
|
||||
team_id: 4
|
||||
list_id: 8
|
||||
right: 2
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
# Readonly acces on list 19
|
||||
- id: 5
|
||||
team_id: 8
|
||||
list_id: 19
|
||||
right: 2
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
# Write acces on list 19
|
||||
- id: 6
|
||||
team_id: 9
|
||||
list_id: 19
|
||||
right: 1
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
# Admin acces on list 19
|
||||
- id: 7
|
||||
team_id: 10
|
||||
list_id: 19
|
||||
right: 2
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
@@ -2,56 +2,56 @@
|
||||
team_id: 1
|
||||
user_id: 1
|
||||
admin: true
|
||||
created: 0
|
||||
created: 2018-12-01 15:13:12
|
||||
-
|
||||
team_id: 1
|
||||
user_id: 2
|
||||
created: 0
|
||||
created: 2018-12-01 15:13:12
|
||||
-
|
||||
team_id: 2
|
||||
user_id: 1
|
||||
created: 0
|
||||
created: 2018-12-01 15:13:12
|
||||
-
|
||||
team_id: 3
|
||||
user_id: 1
|
||||
created: 0
|
||||
created: 2018-12-01 15:13:12
|
||||
-
|
||||
team_id: 4
|
||||
user_id: 1
|
||||
created: 0
|
||||
created: 2018-12-01 15:13:12
|
||||
-
|
||||
team_id: 5
|
||||
user_id: 1
|
||||
created: 0
|
||||
created: 2018-12-01 15:13:12
|
||||
-
|
||||
team_id: 6
|
||||
user_id: 1
|
||||
created: 0
|
||||
created: 2018-12-01 15:13:12
|
||||
-
|
||||
team_id: 7
|
||||
user_id: 1
|
||||
created: 0
|
||||
created: 2018-12-01 15:13:12
|
||||
-
|
||||
team_id: 8
|
||||
user_id: 1
|
||||
created: 0
|
||||
created: 2018-12-01 15:13:12
|
||||
-
|
||||
team_id: 9
|
||||
user_id: 2
|
||||
created: 0
|
||||
created: 2018-12-01 15:13:12
|
||||
-
|
||||
team_id: 10
|
||||
user_id: 3
|
||||
created: 0
|
||||
created: 2018-12-01 15:13:12
|
||||
-
|
||||
team_id: 11
|
||||
user_id: 8
|
||||
created: 0
|
||||
created: 2018-12-01 15:13:12
|
||||
-
|
||||
team_id: 12
|
||||
user_id: 9
|
||||
created: 0
|
||||
created: 2018-12-01 15:13:12
|
||||
-
|
||||
team_id: 13
|
||||
user_id: 10
|
||||
created: 0
|
||||
created: 2018-12-01 15:13:12
|
||||
|
||||
@@ -2,51 +2,51 @@
|
||||
team_id: 1
|
||||
namespace_id: 3
|
||||
right: 0
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
|
||||
- id: 2
|
||||
team_id: 2
|
||||
namespace_id: 3
|
||||
right: 0
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
|
||||
- id: 3
|
||||
team_id: 5
|
||||
namespace_id: 7
|
||||
right: 0
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
|
||||
- id: 4
|
||||
team_id: 6
|
||||
namespace_id: 8
|
||||
right: 1
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
|
||||
- id: 5
|
||||
team_id: 7
|
||||
namespace_id: 9
|
||||
right: 2
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 6
|
||||
team_id: 11
|
||||
namespace_id: 14
|
||||
right: 0
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 7
|
||||
team_id: 12
|
||||
namespace_id: 14
|
||||
right: 1
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 8
|
||||
team_id: 13
|
||||
namespace_id: 14
|
||||
right: 2
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
|
||||
@@ -4,31 +4,35 @@
|
||||
password: '$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.' # 1234
|
||||
email: 'user1@example.com'
|
||||
is_active: true
|
||||
updated: 0
|
||||
created: 0
|
||||
issuer: local
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
-
|
||||
id: 2
|
||||
username: 'user2'
|
||||
password: '$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.' # 1234
|
||||
email: 'user2@example.com'
|
||||
updated: 0
|
||||
created: 0
|
||||
issuer: local
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
-
|
||||
id: 3
|
||||
username: 'user3'
|
||||
password: '$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.' # 1234
|
||||
email: 'user3@example.com'
|
||||
password_reset_token: passwordresettesttoken
|
||||
updated: 0
|
||||
created: 0
|
||||
issuer: local
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
-
|
||||
id: 4
|
||||
username: 'user4'
|
||||
password: '$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.' # 1234
|
||||
email: 'user4@example.com'
|
||||
email_confirm_token: tiepiQueed8ahc7zeeFe1eveiy4Ein8osooxegiephauph2Ael
|
||||
updated: 0
|
||||
created: 0
|
||||
issuer: local
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
-
|
||||
id: 5
|
||||
username: 'user5'
|
||||
@@ -36,62 +40,80 @@
|
||||
email: 'user5@example.com'
|
||||
email_confirm_token: tiepiQueed8ahc7zeeFe1eveiy4Ein8osooxegiephauph2Ael
|
||||
is_active: false
|
||||
updated: 0
|
||||
created: 0
|
||||
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
|
||||
- id: 6
|
||||
username: 'user6'
|
||||
password: '$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.' # 1234
|
||||
email: 'user6@example.com'
|
||||
is_active: true
|
||||
updated: 0
|
||||
created: 0
|
||||
issuer: local
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 7
|
||||
username: 'user7'
|
||||
password: '$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.' # 1234
|
||||
email: 'user7@example.com'
|
||||
is_active: true
|
||||
updated: 0
|
||||
created: 0
|
||||
issuer: local
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 8
|
||||
username: 'user8'
|
||||
password: '$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.' # 1234
|
||||
email: 'user8@example.com'
|
||||
is_active: true
|
||||
updated: 0
|
||||
created: 0
|
||||
issuer: local
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 9
|
||||
username: 'user9'
|
||||
password: '$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.' # 1234
|
||||
email: 'user9@example.com'
|
||||
is_active: true
|
||||
updated: 0
|
||||
created: 0
|
||||
issuer: local
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 10
|
||||
username: 'user10'
|
||||
password: '$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.' # 1234
|
||||
email: 'user10@example.com'
|
||||
is_active: true
|
||||
updated: 0
|
||||
created: 0
|
||||
issuer: local
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 11
|
||||
username: 'user11'
|
||||
password: '$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.' # 1234
|
||||
email: 'user11@example.com'
|
||||
is_active: true
|
||||
updated: 0
|
||||
created: 0
|
||||
issuer: local
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 12
|
||||
username: 'user12'
|
||||
password: '$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.' # 1234
|
||||
email: 'user12@example.com'
|
||||
is_active: true
|
||||
updated: 0
|
||||
created: 0
|
||||
issuer: local
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 13
|
||||
username: 'user13'
|
||||
password: '$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.' # 1234
|
||||
email: 'user14@example.com'
|
||||
is_active: true
|
||||
updated: 0
|
||||
created: 0
|
||||
issuer: local
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 14
|
||||
username: 'user14'
|
||||
password: '$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.' # 1234
|
||||
email: 'user15@some.service.com'
|
||||
is_active: true
|
||||
issuer: 'https://some.service.com'
|
||||
subject: '12345'
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
|
||||
@@ -2,47 +2,47 @@
|
||||
user_id: 1
|
||||
list_id: 3
|
||||
right: 0
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 2
|
||||
user_id: 2
|
||||
list_id: 3
|
||||
right: 0
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 3
|
||||
user_id: 1
|
||||
list_id: 9
|
||||
right: 0
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 4
|
||||
user_id: 1
|
||||
list_id: 10
|
||||
right: 1
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 5
|
||||
user_id: 1
|
||||
list_id: 11
|
||||
right: 2
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 6
|
||||
user_id: 4
|
||||
list_id: 19
|
||||
right: 0
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 7
|
||||
user_id: 5
|
||||
list_id: 19
|
||||
right: 1
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 8
|
||||
user_id: 6
|
||||
list_id: 19
|
||||
right: 2
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
|
||||
@@ -2,51 +2,51 @@
|
||||
user_id: 1
|
||||
namespace_id: 3
|
||||
right: 0
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
|
||||
- id: 2
|
||||
user_id: 2
|
||||
namespace_id: 3
|
||||
right: 0
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
|
||||
- id: 3
|
||||
user_id: 1
|
||||
namespace_id: 10
|
||||
right: 0
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
|
||||
- id: 4
|
||||
user_id: 1
|
||||
namespace_id: 11
|
||||
right: 1
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
|
||||
- id: 5
|
||||
user_id: 1
|
||||
namespace_id: 12
|
||||
right: 2
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 6
|
||||
user_id: 11
|
||||
namespace_id: 14
|
||||
right: 0
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 7
|
||||
user_id: 12
|
||||
namespace_id: 14
|
||||
right: 1
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
- id: 8
|
||||
user_id: 13
|
||||
namespace_id: 14
|
||||
right: 2
|
||||
updated: 0
|
||||
created: 0
|
||||
updated: 2018-12-02 15:13:12
|
||||
created: 2018-12-01 15:13:12
|
||||
|
||||
@@ -1,26 +1,29 @@
|
||||
// Copyright2018-2020 Vikunja and contriubtors. All rights reserved.
|
||||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This file is part of Vikunja.
|
||||
//
|
||||
// Vikunja is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// 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.
|
||||
//
|
||||
// Vikunja is distributed in the hope that it will be useful,
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
// GNU Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Vikunja. If not, see <https://www.gnu.org/licenses/>.
|
||||
// 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 db
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"os"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"xorm.io/core"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
@@ -46,9 +49,10 @@ func CreateTestEngine() (engine *xorm.Engine, err error) {
|
||||
}
|
||||
|
||||
engine.SetMapper(core.GonicMapper{})
|
||||
logger := xorm.NewSimpleLogger(log.GetLogWriter("database"))
|
||||
logger := log.NewXormLogger("DEBUG")
|
||||
logger.ShowSQL(os.Getenv("UNIT_TESTS_VERBOSE") == "1")
|
||||
engine.SetLogger(logger)
|
||||
engine.SetTZLocation(config.GetTimeZone())
|
||||
x = engine
|
||||
return
|
||||
}
|
||||
@@ -68,3 +72,32 @@ func InitTestFixtures(tablenames ...string) (err error) {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AssertExists checks and asserts the existence of certain entries in the db
|
||||
func AssertExists(t *testing.T, table string, values map[string]interface{}, custom bool) {
|
||||
var exists bool
|
||||
var err error
|
||||
v := make(map[string]interface{})
|
||||
// Postgres sometimes needs to build raw sql. Because it won't always need to do this and this isn't fun, it's a flag.
|
||||
if custom {
|
||||
//#nosec
|
||||
sql := "SELECT * FROM " + table + " WHERE "
|
||||
for col, val := range values {
|
||||
sql += col + "=" + fmt.Sprintf("%v", val) + " AND "
|
||||
}
|
||||
sql = sql[:len(sql)-5]
|
||||
exists, err = x.SQL(sql).Get(&v)
|
||||
} else {
|
||||
exists, err = x.Table(table).Where(values).Get(&v)
|
||||
}
|
||||
assert.NoError(t, err, fmt.Sprintf("Failed to assert entries exist in db, error was: %s", err))
|
||||
assert.True(t, exists, fmt.Sprintf("Entries %v do not exist in table %s", values, table))
|
||||
}
|
||||
|
||||
// AssertMissing checks and asserts the nonexiste nce of certain entries in the db
|
||||
func AssertMissing(t *testing.T, table string, values map[string]interface{}) {
|
||||
v := make(map[string]interface{})
|
||||
exists, err := x.Table(table).Where(values).Exist(&v)
|
||||
assert.NoError(t, err, fmt.Sprintf("Failed to assert entries don't exist in db, error was: %s", err))
|
||||
assert.False(t, exists, fmt.Sprintf("Entries %v exist in table %s", values, table))
|
||||
}
|
||||
|
||||
@@ -1,29 +1,30 @@
|
||||
// Copyright 2018-2020 Vikunja and contriubtors. All rights reserved.
|
||||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This file is part of Vikunja.
|
||||
//
|
||||
// Vikunja is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// 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.
|
||||
//
|
||||
// Vikunja is distributed in the hope that it will be useful,
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
// GNU Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Vikunja. If not, see <https://www.gnu.org/licenses/>.
|
||||
// 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 db
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"fmt"
|
||||
"github.com/go-testfixtures/testfixtures/v3"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"github.com/go-testfixtures/testfixtures/v3"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"xorm.io/xorm/schemas"
|
||||
)
|
||||
|
||||
var fixtures *testfixtures.Loader
|
||||
@@ -49,6 +50,7 @@ func InitFixtures(tablenames ...string) (err error) {
|
||||
testfixtures.Database(x.DB().DB),
|
||||
testfixtures.Dialect(config.DatabaseType.GetString()),
|
||||
testfixtures.DangerousSkipTestDatabaseCheck(),
|
||||
testfixtures.Location(config.GetTimeZone()),
|
||||
testfiles,
|
||||
}
|
||||
|
||||
@@ -73,7 +75,7 @@ func LoadFixtures() error {
|
||||
|
||||
// Copied from https://github.com/go-gitea/gitea/blob/master/models/test_fixtures.go#L39
|
||||
// Now if we're running postgres we need to tell it to update the sequences
|
||||
if x.Dialect().DriverName() == "postgres" {
|
||||
if x.Dialect().URI().DBType == schemas.POSTGRES {
|
||||
results, err := x.QueryString(`SELECT 'SELECT SETVAL(' ||
|
||||
quote_literal(quote_ident(PGT.schemaname) || '.' || quote_ident(S.relname)) ||
|
||||
', COALESCE(MAX(' ||quote_ident(C.attname)|| '), 1) ) FROM ' ||
|
||||
|
||||
@@ -2,16 +2,16 @@
|
||||
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// 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 General Public License for more details.
|
||||
// GNU Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// 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 files
|
||||
|
||||
41
pkg/files/dump.go
Normal file
41
pkg/files/dump.go
Normal file
@@ -0,0 +1,41 @@
|
||||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU 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 files
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// Dump dumps all saved files
|
||||
// This only includes the raw files, no db entries.
|
||||
func Dump() (allFiles map[int64]io.ReadCloser, err error) {
|
||||
files := []*File{}
|
||||
err = x.Find(&files)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
allFiles = make(map[int64]io.ReadCloser, len(files))
|
||||
for _, file := range files {
|
||||
if err := file.LoadFileByID(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
allFiles[file.ID] = file.File
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -1,19 +1,18 @@
|
||||
// Copyright 2018-2020 Vikunja and contriubtors. All rights reserved.
|
||||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This file is part of Vikunja.
|
||||
//
|
||||
// Vikunja is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// 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.
|
||||
//
|
||||
// Vikunja is distributed in the hope that it will be useful,
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
// GNU Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Vikunja. If not, see <https://www.gnu.org/licenses/>.
|
||||
// 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 files
|
||||
|
||||
@@ -29,7 +28,7 @@ func (err ErrFileDoesNotExist) Error() string {
|
||||
return fmt.Sprintf("file %d does not exist", err.FileID)
|
||||
}
|
||||
|
||||
//IsErrFileDoesNotExist checks if an error is ErrFileDoesNotExist
|
||||
// IsErrFileDoesNotExist checks if an error is ErrFileDoesNotExist
|
||||
func IsErrFileDoesNotExist(err error) bool {
|
||||
_, ok := err.(ErrFileDoesNotExist)
|
||||
return ok
|
||||
@@ -45,8 +44,25 @@ func (err ErrFileIsTooLarge) Error() string {
|
||||
return fmt.Sprintf("file is too large [Size: %d]", err.Size)
|
||||
}
|
||||
|
||||
//IsErrFileIsTooLarge checks if an error is ErrFileIsTooLarge
|
||||
// IsErrFileIsTooLarge checks if an error is ErrFileIsTooLarge
|
||||
func IsErrFileIsTooLarge(err error) bool {
|
||||
_, ok := err.(ErrFileIsTooLarge)
|
||||
return ok
|
||||
}
|
||||
|
||||
// ErrFileIsNotUnsplashFile defines an error where a file is not downloaded from unsplash.
|
||||
// Used in cases whenever unsplash information about a file is requested, but the file was not downloaded from unsplash.
|
||||
type ErrFileIsNotUnsplashFile struct {
|
||||
FileID int64
|
||||
}
|
||||
|
||||
// Error is the error implementation of ErrFileIsNotUnsplashFile
|
||||
func (err ErrFileIsNotUnsplashFile) Error() string {
|
||||
return fmt.Sprintf("file was not downloaded from unsplash [FileID: %d]", err.FileID)
|
||||
}
|
||||
|
||||
// IsErrFileIsNotUnsplashFile checks if an error is ErrFileIsNotUnsplashFile
|
||||
func IsErrFileIsNotUnsplashFile(err error) bool {
|
||||
_, ok := err.(ErrFileIsNotUnsplashFile)
|
||||
return ok
|
||||
}
|
||||
|
||||
@@ -2,28 +2,29 @@
|
||||
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// 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 General Public License for more details.
|
||||
// GNU Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// 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 files
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/db"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// This file handles storing and retrieving a file for different backends
|
||||
@@ -49,7 +50,7 @@ func initFixtures(t *testing.T) {
|
||||
InitTestFileFixtures(t)
|
||||
}
|
||||
|
||||
//InitTestFileFixtures initializes file fixtures
|
||||
// InitTestFileFixtures initializes file fixtures
|
||||
func InitTestFileFixtures(t *testing.T) {
|
||||
// Init fixture files
|
||||
filename := config.FilesBasePath.GetString() + "/1"
|
||||
|
||||
@@ -2,42 +2,41 @@
|
||||
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// 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 General Public License for more details.
|
||||
// GNU Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// 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 files
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/timeutil"
|
||||
"code.vikunja.io/web"
|
||||
"github.com/c2h5oh/datasize"
|
||||
"github.com/spf13/afero"
|
||||
"io"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/db"
|
||||
"code.vikunja.io/web"
|
||||
"github.com/c2h5oh/datasize"
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
// File holds all information about a file
|
||||
type File struct {
|
||||
ID int64 `xorm:"int(11) autoincr not null unique pk" json:"id"`
|
||||
ID int64 `xorm:"bigint autoincr not null unique pk" json:"id"`
|
||||
Name string `xorm:"text not null" json:"name"`
|
||||
Mime string `xorm:"text null" json:"mime"`
|
||||
Size uint64 `xorm:"int(11) not null" json:"size"`
|
||||
Size uint64 `xorm:"bigint not null" json:"size"`
|
||||
|
||||
Created time.Time `xorm:"-" json:"created"`
|
||||
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"created" json:"-"`
|
||||
CreatedByID int64 `xorm:"int(11) not null" json:"-"`
|
||||
Created time.Time `xorm:"created" json:"created"`
|
||||
CreatedByID int64 `xorm:"bigint not null" json:"-"`
|
||||
|
||||
File afero.File `xorm:"-" json:"-"`
|
||||
// This ReadCloser is only used for migration purposes. Use with care!
|
||||
@@ -66,12 +65,16 @@ func (f *File) LoadFileMetaByID() (err error) {
|
||||
if !exists {
|
||||
return ErrFileDoesNotExist{FileID: f.ID}
|
||||
}
|
||||
f.Created = f.CreatedUnix.ToTime()
|
||||
return
|
||||
}
|
||||
|
||||
// Create creates a new file from an FileHeader
|
||||
func Create(f io.ReadCloser, realname string, realsize uint64, a web.Auth) (file *File, err error) {
|
||||
func Create(f io.Reader, realname string, realsize uint64, a web.Auth) (file *File, err error) {
|
||||
return CreateWithMime(f, realname, realsize, a, "")
|
||||
}
|
||||
|
||||
// CreateWithMime creates a new file from an FileHeader and sets its mime type
|
||||
func CreateWithMime(f io.Reader, realname string, realsize uint64, a web.Auth, mime string) (file *File, err error) {
|
||||
|
||||
// Get and parse the configured file size
|
||||
var maxSize datasize.ByteSize
|
||||
@@ -88,28 +91,51 @@ func Create(f io.ReadCloser, realname string, realsize uint64, a web.Auth) (file
|
||||
Name: realname,
|
||||
Size: realsize,
|
||||
CreatedByID: a.GetID(),
|
||||
Mime: mime,
|
||||
}
|
||||
|
||||
_, err = x.Insert(file)
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
_, err = s.Insert(file)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return
|
||||
}
|
||||
|
||||
// Save the file to storage with its new ID as path
|
||||
err = afs.WriteReader(file.getFileName(), f)
|
||||
err = file.Save(f)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Delete removes a file from the DB and the file system
|
||||
func (f *File) Delete() (err error) {
|
||||
deleted, err := x.Where("id = ?", f.ID).Delete(f)
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
deleted, err := s.Where("id = ?", f.ID).Delete(f)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return err
|
||||
}
|
||||
if deleted == 0 {
|
||||
_ = s.Rollback()
|
||||
return ErrFileDoesNotExist{FileID: f.ID}
|
||||
}
|
||||
|
||||
err = afs.Remove(f.getFileName())
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Save saves a file to storage
|
||||
func (f *File) Save(fcontent io.Reader) error {
|
||||
return afs.WriteReader(f.getFileName(), fcontent)
|
||||
}
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
// Copyright 2018-2020 Vikunja and contriubtors. All rights reserved.
|
||||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This file is part of Vikunja.
|
||||
//
|
||||
// Vikunja is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// 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.
|
||||
//
|
||||
// Vikunja is distributed in the hope that it will be useful,
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
// GNU Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Vikunja. If not, see <https://www.gnu.org/licenses/>.
|
||||
// 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 files
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"io"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type testfile struct {
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
// Copyright 2018-2020 Vikunja and contriubtors. All rights reserved.
|
||||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This file is part of Vikunja.
|
||||
//
|
||||
// Vikunja is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// 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.
|
||||
//
|
||||
// Vikunja is distributed in the hope that it will be useful,
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
// GNU Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Vikunja. If not, see <https://www.gnu.org/licenses/>.
|
||||
// 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 files
|
||||
|
||||
|
||||
88
pkg/initialize/init.go
Normal file
88
pkg/initialize/init.go
Normal file
@@ -0,0 +1,88 @@
|
||||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU 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 initialize
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/cron"
|
||||
"code.vikunja.io/api/pkg/files"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/mail"
|
||||
"code.vikunja.io/api/pkg/migration"
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/api/pkg/modules/keyvalue"
|
||||
migrator "code.vikunja.io/api/pkg/modules/migration"
|
||||
"code.vikunja.io/api/pkg/red"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
)
|
||||
|
||||
// LightInit will only fullInit config, redis, logger but no db connection.
|
||||
func LightInit() {
|
||||
// Init the config
|
||||
config.InitConfig()
|
||||
|
||||
// Init redis
|
||||
red.InitRedis()
|
||||
|
||||
// Init keyvalue store
|
||||
keyvalue.InitStorage()
|
||||
|
||||
// Set logger
|
||||
log.InitLogger()
|
||||
}
|
||||
|
||||
// InitEngines intializes all db connections
|
||||
func InitEngines() {
|
||||
err := models.SetEngine()
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
err = user.InitDB()
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
err = files.SetEngine()
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
err = migrator.InitDB()
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// FullInit initializes all kinds of things in the right order
|
||||
func FullInit() {
|
||||
|
||||
LightInit()
|
||||
|
||||
// Run the migrations
|
||||
migration.Migrate(nil)
|
||||
|
||||
// Set Engine
|
||||
InitEngines()
|
||||
|
||||
// Initialize the files handler
|
||||
files.InitFileHandler()
|
||||
|
||||
// Start the mail daemon
|
||||
mail.StartMailDaemon()
|
||||
|
||||
// Start the cron
|
||||
cron.Init()
|
||||
models.RegisterReminderCron()
|
||||
}
|
||||
238
pkg/integrations/archived_test.go
Normal file
238
pkg/integrations/archived_test.go
Normal file
@@ -0,0 +1,238 @@
|
||||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU 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/url"
|
||||
"testing"
|
||||
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/web/handler"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// This tests the following behaviour:
|
||||
// 1. A namespace should not be editable if it is archived.
|
||||
// 1. With the exception being to un-archive it.
|
||||
// 2. A list which belongs to an archived namespace cannot be edited.
|
||||
// 3. An archived list should not be editable.
|
||||
// 1. Except for un-archiving it.
|
||||
// 4. It is not possible to un-archive a list individually if its namespace is archived.
|
||||
// 5. Creating new lists on an archived namespace should not work.
|
||||
// 6. Creating new tasks on an archived list should not work.
|
||||
// 7. Creating new tasks on a list who's namespace is archived should not work.
|
||||
// 8. Editing tasks on an archived list should not work.
|
||||
// 9. Editing tasks on a list who's namespace is archived should not work.
|
||||
// 10. Archived namespaces should not appear in the list with all namespaces.
|
||||
// 11. Archived lists should not appear in the list with all lists.
|
||||
// 12. Lists who's namespace is archived should not appear in the list with all lists.
|
||||
//
|
||||
// All of this is tested through integration tests because it's not yet clear if this will be implemented directly
|
||||
// or with some kind of middleware.
|
||||
//
|
||||
// Maybe the inheritance of lists from namespaces could be solved with some kind of is_archived_inherited flag -
|
||||
// that way I'd only need to implement the checking on a list level and update the flag for all lists once the
|
||||
// namespace is archived. The archived flag would then be used to not accedentially unarchive lists which were
|
||||
// already individually archived when the namespace was archived.
|
||||
// Should still test it all though.
|
||||
//
|
||||
// Namespace 16 is archived
|
||||
// List 21 belongs to namespace 16
|
||||
// List 22 is archived individually
|
||||
|
||||
func TestArchived(t *testing.T) {
|
||||
testListHandler := webHandlerTest{
|
||||
user: &testuser1,
|
||||
strFunc: func() handler.CObject {
|
||||
return &models.List{}
|
||||
},
|
||||
t: t,
|
||||
}
|
||||
testNamespaceHandler := webHandlerTest{
|
||||
user: &testuser1,
|
||||
strFunc: func() handler.CObject {
|
||||
return &models.Namespace{}
|
||||
},
|
||||
t: t,
|
||||
}
|
||||
testTaskHandler := webHandlerTest{
|
||||
user: &testuser1,
|
||||
strFunc: func() handler.CObject {
|
||||
return &models.Task{}
|
||||
},
|
||||
t: t,
|
||||
}
|
||||
testLabelHandler := webHandlerTest{
|
||||
user: &testuser1,
|
||||
strFunc: func() handler.CObject {
|
||||
return &models.LabelTask{}
|
||||
},
|
||||
t: t,
|
||||
}
|
||||
testAssigneeHandler := webHandlerTest{
|
||||
user: &testuser1,
|
||||
strFunc: func() handler.CObject {
|
||||
return &models.TaskAssginee{}
|
||||
},
|
||||
t: t,
|
||||
}
|
||||
testRelationHandler := webHandlerTest{
|
||||
user: &testuser1,
|
||||
strFunc: func() handler.CObject {
|
||||
return &models.TaskRelation{}
|
||||
},
|
||||
t: t,
|
||||
}
|
||||
testCommentHandler := webHandlerTest{
|
||||
user: &testuser1,
|
||||
strFunc: func() handler.CObject {
|
||||
return &models.TaskComment{}
|
||||
},
|
||||
t: t,
|
||||
}
|
||||
|
||||
t.Run("namespace", func(t *testing.T) {
|
||||
t.Run("not editable", func(t *testing.T) {
|
||||
_, err := testNamespaceHandler.testUpdateWithUser(nil, map[string]string{"namespace": "16"}, `{"title":"TestIpsum","is_archived":true}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeNamespaceIsArchived)
|
||||
})
|
||||
t.Run("unarchivable", func(t *testing.T) {
|
||||
rec, err := testNamespaceHandler.testUpdateWithUser(nil, map[string]string{"namespace": "16"}, `{"title":"TestIpsum","is_archived":false}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"is_archived":false`)
|
||||
})
|
||||
t.Run("no new lists", func(t *testing.T) {
|
||||
_, err := testListHandler.testCreateWithUser(nil, map[string]string{"namespace": "16"}, `{"title":"Lorem"}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeNamespaceIsArchived)
|
||||
})
|
||||
t.Run("should not appear in the list", func(t *testing.T) {
|
||||
rec, err := testNamespaceHandler.testReadAllWithUser(nil, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.NotContains(t, rec.Body.String(), `"title":"Archived testnamespace16"`)
|
||||
})
|
||||
t.Run("should appear in the list if explicitly requested", func(t *testing.T) {
|
||||
rec, err := testNamespaceHandler.testReadAllWithUser(url.Values{"is_archived": []string{"true"}}, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Archived testnamespace16"`)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("list", func(t *testing.T) {
|
||||
|
||||
taskTests := func(taskID string, errCode int, t *testing.T) {
|
||||
t.Run("task", func(t *testing.T) {
|
||||
t.Run("edit task", func(t *testing.T) {
|
||||
_, err := testTaskHandler.testUpdateWithUser(nil, map[string]string{"listtask": taskID}, `{"title":"TestIpsum"}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, errCode)
|
||||
})
|
||||
t.Run("delete", func(t *testing.T) {
|
||||
_, err := testTaskHandler.testDeleteWithUser(nil, map[string]string{"listtask": taskID})
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, errCode)
|
||||
})
|
||||
t.Run("add new labels", func(t *testing.T) {
|
||||
_, err := testLabelHandler.testCreateWithUser(nil, map[string]string{"listtask": taskID}, `{"label_id":1}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, errCode)
|
||||
})
|
||||
t.Run("remove lables", func(t *testing.T) {
|
||||
_, err := testLabelHandler.testDeleteWithUser(nil, map[string]string{"listtask": taskID, "label": "4"})
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, errCode)
|
||||
})
|
||||
t.Run("add assignees", func(t *testing.T) {
|
||||
_, err := testAssigneeHandler.testCreateWithUser(nil, map[string]string{"listtask": taskID}, `{"user_id":3}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, errCode)
|
||||
})
|
||||
t.Run("remove assignees", func(t *testing.T) {
|
||||
_, err := testAssigneeHandler.testDeleteWithUser(nil, map[string]string{"listtask": taskID, "user": "2"})
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, errCode)
|
||||
})
|
||||
t.Run("add relation", func(t *testing.T) {
|
||||
_, err := testRelationHandler.testCreateWithUser(nil, map[string]string{"task": taskID}, `{"other_task_id":1,"relation_kind":"related"}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, errCode)
|
||||
})
|
||||
t.Run("remove relation", func(t *testing.T) {
|
||||
_, err := testRelationHandler.testDeleteWithUser(nil, map[string]string{"task": taskID}, `{"other_task_id":2,"relation_kind":"related"}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, errCode)
|
||||
})
|
||||
t.Run("add comment", func(t *testing.T) {
|
||||
_, err := testCommentHandler.testCreateWithUser(nil, map[string]string{"task": taskID}, `{"comment":"Lorem"}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, errCode)
|
||||
})
|
||||
t.Run("remove comment", func(t *testing.T) {
|
||||
var commentID = "15"
|
||||
if taskID == "36" {
|
||||
commentID = "16"
|
||||
}
|
||||
_, err := testCommentHandler.testDeleteWithUser(nil, map[string]string{"task": taskID, "commentid": commentID})
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, errCode)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// The list belongs to an archived namespace
|
||||
t.Run("archived namespace", func(t *testing.T) {
|
||||
t.Run("not editable", func(t *testing.T) {
|
||||
_, err := testListHandler.testUpdateWithUser(nil, map[string]string{"list": "21"}, `{"title":"TestIpsum","is_archived":true}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeNamespaceIsArchived)
|
||||
})
|
||||
t.Run("no new tasks", func(t *testing.T) {
|
||||
_, err := testTaskHandler.testCreateWithUser(nil, map[string]string{"list": "21"}, `{"title":"Lorem"}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeNamespaceIsArchived)
|
||||
})
|
||||
t.Run("not unarchivable", func(t *testing.T) {
|
||||
_, err := testListHandler.testUpdateWithUser(nil, map[string]string{"list": "21"}, `{"title":"LoremIpsum","is_archived":false}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeNamespaceIsArchived)
|
||||
})
|
||||
|
||||
taskTests("35", models.ErrCodeNamespaceIsArchived, t)
|
||||
})
|
||||
// The list itself is archived
|
||||
t.Run("archived individually", func(t *testing.T) {
|
||||
t.Run("not editable", func(t *testing.T) {
|
||||
_, err := testListHandler.testUpdateWithUser(nil, map[string]string{"list": "22"}, `{"title":"TestIpsum","is_archived":true}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeListIsArchived)
|
||||
})
|
||||
t.Run("no new tasks", func(t *testing.T) {
|
||||
_, err := testTaskHandler.testCreateWithUser(nil, map[string]string{"list": "22"}, `{"title":"Lorem"}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeListIsArchived)
|
||||
})
|
||||
t.Run("unarchivable", func(t *testing.T) {
|
||||
rec, err := testListHandler.testUpdateWithUser(nil, map[string]string{"list": "22"}, `{"title":"LoremIpsum","is_archived":false}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"is_archived":false`)
|
||||
})
|
||||
|
||||
taskTests("36", models.ErrCodeListIsArchived, t)
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -2,39 +2,40 @@
|
||||
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// 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 General Public License for more details.
|
||||
// GNU Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// 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 (
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/db"
|
||||
"code.vikunja.io/api/pkg/files"
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/api/pkg/routes"
|
||||
v1 "code.vikunja.io/api/pkg/routes/api/v1"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"code.vikunja.io/web"
|
||||
"code.vikunja.io/web/handler"
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/db"
|
||||
"code.vikunja.io/api/pkg/files"
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/api/pkg/modules/auth"
|
||||
"code.vikunja.io/api/pkg/routes"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"code.vikunja.io/web"
|
||||
"code.vikunja.io/web/handler"
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// These are the test users, the same way they are in the test database
|
||||
@@ -118,7 +119,7 @@ func newTestRequest(t *testing.T, method string, handler func(ctx echo.Context)
|
||||
|
||||
func addUserTokenToContext(t *testing.T, user *user.User, c echo.Context) {
|
||||
// Get the token as a string
|
||||
token, err := v1.NewUserJWTAuthtoken(user)
|
||||
token, err := auth.NewUserJWTAuthtoken(user)
|
||||
assert.NoError(t, err)
|
||||
// We send the string token through the parsing function to get a valid jwt.Token
|
||||
tken, err := jwt.Parse(token, func(t *jwt.Token) (interface{}, error) {
|
||||
@@ -130,7 +131,7 @@ func addUserTokenToContext(t *testing.T, user *user.User, c echo.Context) {
|
||||
|
||||
func addLinkShareTokenToContext(t *testing.T, share *models.LinkSharing, c echo.Context) {
|
||||
// Get the token as a string
|
||||
token, err := v1.NewLinkShareJWTAuthtoken(share)
|
||||
token, err := auth.NewLinkShareJWTAuthtoken(share)
|
||||
assert.NoError(t, err)
|
||||
// We send the string token through the parsing function to get a valid jwt.Token
|
||||
tken, err := jwt.Parse(token, func(t *jwt.Token) (interface{}, error) {
|
||||
@@ -221,9 +222,13 @@ func (h *webHandlerTest) testUpdateWithUser(queryParams url.Values, urlParams ma
|
||||
return newTestRequestWithUser(h.t, http.MethodPost, hndl.UpdateWeb, h.user, payload, queryParams, urlParams)
|
||||
}
|
||||
|
||||
func (h *webHandlerTest) testDeleteWithUser(queryParams url.Values, urlParams map[string]string) (rec *httptest.ResponseRecorder, err error) {
|
||||
func (h *webHandlerTest) testDeleteWithUser(queryParams url.Values, urlParams map[string]string, payload ...string) (rec *httptest.ResponseRecorder, err error) {
|
||||
pl := ""
|
||||
if len(payload) > 0 {
|
||||
pl = payload[0]
|
||||
}
|
||||
hndl := h.getHandler()
|
||||
return newTestRequestWithUser(h.t, http.MethodDelete, hndl.DeleteWeb, h.user, "", queryParams, urlParams)
|
||||
return newTestRequestWithUser(h.t, http.MethodDelete, hndl.DeleteWeb, h.user, pl, queryParams, urlParams)
|
||||
}
|
||||
|
||||
func (h *webHandlerTest) testReadAllWithLinkShare(queryParams url.Values, urlParams map[string]string) (rec *httptest.ResponseRecorder, err error) {
|
||||
|
||||
301
pkg/integrations/kanban_test.go
Normal file
301
pkg/integrations/kanban_test.go
Normal file
@@ -0,0 +1,301 @@
|
||||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU 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 (
|
||||
"testing"
|
||||
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/web/handler"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestBucket(t *testing.T) {
|
||||
testHandler := webHandlerTest{
|
||||
user: &testuser1,
|
||||
strFunc: func() handler.CObject {
|
||||
return &models.Bucket{}
|
||||
},
|
||||
t: t,
|
||||
}
|
||||
t.Run("ReadAll", func(t *testing.T) {
|
||||
t.Run("Normal", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(nil, map[string]string{"list": "1"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `testbucket1`)
|
||||
assert.Contains(t, rec.Body.String(), `testbucket2`)
|
||||
assert.Contains(t, rec.Body.String(), `testbucket3`)
|
||||
assert.NotContains(t, rec.Body.String(), `testbucket4`) // Different List
|
||||
})
|
||||
})
|
||||
t.Run("Update", func(t *testing.T) {
|
||||
t.Run("Normal", func(t *testing.T) {
|
||||
// Check the list was loaded successfully afterwards, see testReadOneWithUser
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"bucket": "1"}, `{"title":"TestLoremIpsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
|
||||
})
|
||||
t.Run("Nonexisting Bucket", func(t *testing.T) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"bucket": "9999"}, `{"title":"TestLoremIpsum"}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeBucketDoesNotExist)
|
||||
})
|
||||
t.Run("Empty title", func(t *testing.T) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"bucket": "1"}, `{"title":""}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message.(models.ValidationHTTPError).InvalidFields, "title: non zero value required")
|
||||
})
|
||||
t.Run("Rights check", func(t *testing.T) {
|
||||
t.Run("Forbidden", func(t *testing.T) {
|
||||
// Owned by user13
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"bucket": "5"}, `{"title":"TestLoremIpsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via Team readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"bucket": "6"}, `{"title":"TestLoremIpsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via Team write", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"bucket": "7"}, `{"title":"TestLoremIpsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
|
||||
})
|
||||
t.Run("Shared Via Team admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"bucket": "8"}, `{"title":"TestLoremIpsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
|
||||
})
|
||||
|
||||
t.Run("Shared Via User readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"bucket": "9"}, `{"title":"TestLoremIpsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via User write", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"bucket": "10"}, `{"title":"TestLoremIpsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
|
||||
})
|
||||
t.Run("Shared Via User admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"bucket": "11"}, `{"title":"TestLoremIpsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
|
||||
})
|
||||
|
||||
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"bucket": "12"}, `{"title":"TestLoremIpsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"bucket": "13"}, `{"title":"TestLoremIpsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"bucket": "14"}, `{"title":"TestLoremIpsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
|
||||
})
|
||||
|
||||
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"bucket": "15"}, `{"title":"TestLoremIpsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"bucket": "16"}, `{"title":"TestLoremIpsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"bucket": "17"}, `{"title":"TestLoremIpsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
|
||||
})
|
||||
})
|
||||
})
|
||||
t.Run("Delete", func(t *testing.T) {
|
||||
t.Run("Normal", func(t *testing.T) {
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "1", "bucket": "1"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
|
||||
})
|
||||
t.Run("Nonexisting", func(t *testing.T) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"bucket": "999"})
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeBucketDoesNotExist)
|
||||
})
|
||||
t.Run("Rights check", func(t *testing.T) {
|
||||
t.Run("Forbidden", func(t *testing.T) {
|
||||
// Owned by user13
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "20", "bucket": "5"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via Team readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "6", "bucket": "6"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via Team write", func(t *testing.T) {
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "7", "bucket": "7"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
|
||||
})
|
||||
t.Run("Shared Via Team admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "8", "bucket": "8"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
|
||||
})
|
||||
|
||||
t.Run("Shared Via User readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "9", "bucket": "9"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via User write", func(t *testing.T) {
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "10", "bucket": "10"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
|
||||
})
|
||||
t.Run("Shared Via User admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "11", "bucket": "11"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
|
||||
})
|
||||
|
||||
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "12", "bucket": "12"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "13", "bucket": "13"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "14", "bucket": "14"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
|
||||
})
|
||||
|
||||
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "15", "bucket": "15"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "16", "bucket": "16"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "17", "bucket": "17"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
|
||||
})
|
||||
})
|
||||
})
|
||||
t.Run("Create", func(t *testing.T) {
|
||||
t.Run("Normal", func(t *testing.T) {
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "1"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
|
||||
})
|
||||
t.Run("Nonexisting", func(t *testing.T) {
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "9999"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeListDoesNotExist)
|
||||
})
|
||||
t.Run("Rights check", func(t *testing.T) {
|
||||
t.Run("Forbidden", func(t *testing.T) {
|
||||
// Owned by user13
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "20"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via Team readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "6"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via Team write", func(t *testing.T) {
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "7"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
|
||||
})
|
||||
t.Run("Shared Via Team admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "8"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
|
||||
})
|
||||
|
||||
t.Run("Shared Via User readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "9"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via User write", func(t *testing.T) {
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "10"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
|
||||
})
|
||||
t.Run("Shared Via User admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "11"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
|
||||
})
|
||||
|
||||
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "12"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "13"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "14"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
|
||||
})
|
||||
|
||||
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "15"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "16"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "17"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -2,27 +2,28 @@
|
||||
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// 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 General Public License for more details.
|
||||
// GNU Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// 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/url"
|
||||
"testing"
|
||||
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/web/handler"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/url"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLinkSharing(t *testing.T) {
|
||||
@@ -54,6 +55,84 @@ func TestLinkSharing(t *testing.T) {
|
||||
SharedByID: 1,
|
||||
}
|
||||
|
||||
t.Run("New Link Share", func(t *testing.T) {
|
||||
testHandler := webHandlerTest{
|
||||
user: &testuser1,
|
||||
strFunc: func() handler.CObject {
|
||||
return &models.LinkSharing{}
|
||||
},
|
||||
t: 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}`)
|
||||
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}`)
|
||||
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}`)
|
||||
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}`)
|
||||
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}`)
|
||||
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}`)
|
||||
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}`)
|
||||
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}`)
|
||||
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}`)
|
||||
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}`)
|
||||
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}`)
|
||||
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}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, req.Body.String(), `"hash":`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Lists", func(t *testing.T) {
|
||||
testHandlerListReadOnly := webHandlerTest{
|
||||
linkShare: linkshareRead,
|
||||
@@ -262,34 +341,34 @@ func TestLinkSharing(t *testing.T) {
|
||||
})
|
||||
t.Run("Create", func(t *testing.T) {
|
||||
t.Run("Shared readonly", func(t *testing.T) {
|
||||
_, err := testHandlerListUserReadOnly.testCreateWithLinkShare(nil, map[string]string{"list": "1"}, `{"userID":"user1"}`)
|
||||
_, err := testHandlerListUserReadOnly.testCreateWithLinkShare(nil, map[string]string{"list": "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"}, `{"userID":"user1"}`)
|
||||
_, err := testHandlerListUserWrite.testCreateWithLinkShare(nil, map[string]string{"list": "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"}, `{"userID":"user1"}`)
|
||||
_, err := testHandlerListUserAdmin.testCreateWithLinkShare(nil, map[string]string{"list": "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"}, `{"userID":"user1"}`)
|
||||
_, err := testHandlerListUserReadOnly.testUpdateWithLinkShare(nil, map[string]string{"list": "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"}, `{"userID":"user1"}`)
|
||||
_, err := testHandlerListUserWrite.testUpdateWithLinkShare(nil, map[string]string{"list": "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"}, `{"userID":"user1"}`)
|
||||
_, err := testHandlerListUserAdmin.testUpdateWithLinkShare(nil, map[string]string{"list": "3"}, `{"user_id":"user1"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
@@ -354,34 +433,34 @@ func TestLinkSharing(t *testing.T) {
|
||||
})
|
||||
t.Run("Create", func(t *testing.T) {
|
||||
t.Run("Shared readonly", func(t *testing.T) {
|
||||
_, err := testHandlerListTeamReadOnly.testCreateWithLinkShare(nil, map[string]string{"list": "1"}, `{"teamID":1}`)
|
||||
_, err := testHandlerListTeamReadOnly.testCreateWithLinkShare(nil, map[string]string{"list": "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"}, `{"teamID":1}`)
|
||||
_, err := testHandlerListTeamWrite.testCreateWithLinkShare(nil, map[string]string{"list": "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"}, `{"teamID":1}`)
|
||||
_, err := testHandlerListTeamAdmin.testCreateWithLinkShare(nil, map[string]string{"list": "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"}, `{"teamID":1}`)
|
||||
_, err := testHandlerListTeamReadOnly.testUpdateWithLinkShare(nil, map[string]string{"list": "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"}, `{"teamID":1}`)
|
||||
_, err := testHandlerListTeamWrite.testUpdateWithLinkShare(nil, map[string]string{"list": "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"}, `{"teamID":1}`)
|
||||
_, err := testHandlerListTeamAdmin.testUpdateWithLinkShare(nil, map[string]string{"list": "3"}, `{"team_id":1}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
@@ -507,36 +586,36 @@ 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"}, `{"text":"Lorem Ipsum"}`)
|
||||
_, err := testHandlerTaskReadOnly.testCreateWithLinkShare(nil, map[string]string{"list": "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"}, `{"text":"Lorem Ipsum"}`)
|
||||
rec, err := testHandlerTaskWrite.testCreateWithLinkShare(nil, map[string]string{"list": "2"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"text":"Lorem Ipsum"`)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
|
||||
})
|
||||
t.Run("Shared admin", func(t *testing.T) {
|
||||
rec, err := testHandlerTaskAdmin.testCreateWithLinkShare(nil, map[string]string{"list": "3"}, `{"text":"Lorem Ipsum"}`)
|
||||
rec, err := testHandlerTaskAdmin.testCreateWithLinkShare(nil, map[string]string{"list": "3"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"text":"Lorem Ipsum"`)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
|
||||
})
|
||||
})
|
||||
t.Run("Update", func(t *testing.T) {
|
||||
t.Run("Shared readonly", func(t *testing.T) {
|
||||
_, err := testHandlerTaskReadOnly.testUpdateWithLinkShare(nil, map[string]string{"listtask": "1"}, `{"text":"Lorem Ipsum"}`)
|
||||
_, err := testHandlerTaskReadOnly.testUpdateWithLinkShare(nil, map[string]string{"listtask": "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"}, `{"text":"Lorem Ipsum"}`)
|
||||
rec, err := testHandlerTaskWrite.testUpdateWithLinkShare(nil, map[string]string{"listtask": "13"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"text":"Lorem Ipsum"`)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
|
||||
})
|
||||
t.Run("Shared admin", func(t *testing.T) {
|
||||
rec, err := testHandlerTaskAdmin.testUpdateWithLinkShare(nil, map[string]string{"listtask": "32"}, `{"text":"Lorem Ipsum"}`)
|
||||
rec, err := testHandlerTaskAdmin.testUpdateWithLinkShare(nil, map[string]string{"listtask": "32"}, `{"title":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"text":"Lorem Ipsum"`)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Lorem Ipsum"`)
|
||||
})
|
||||
|
||||
})
|
||||
@@ -769,34 +848,34 @@ func TestLinkSharing(t *testing.T) {
|
||||
})
|
||||
t.Run("Create", func(t *testing.T) {
|
||||
t.Run("Shared readonly", func(t *testing.T) {
|
||||
_, err := testHandlerNamespaceReadOnly.testCreateWithLinkShare(nil, nil, `{"name":"LoremIpsum"}`)
|
||||
_, err := testHandlerNamespaceReadOnly.testCreateWithLinkShare(nil, nil, `{"title":"LoremIpsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared write", func(t *testing.T) {
|
||||
_, err := testHandlerNamespaceWrite.testCreateWithLinkShare(nil, nil, `{"name":"LoremIpsum"}`)
|
||||
_, err := testHandlerNamespaceWrite.testCreateWithLinkShare(nil, nil, `{"title":"LoremIpsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared admin", func(t *testing.T) {
|
||||
_, err := testHandlerNamespaceAdmin.testCreateWithLinkShare(nil, nil, `{"name":"LoremIpsum"}`)
|
||||
_, err := testHandlerNamespaceAdmin.testCreateWithLinkShare(nil, nil, `{"title":"LoremIpsum"}`)
|
||||
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 := testHandlerNamespaceReadOnly.testUpdateWithLinkShare(nil, map[string]string{"namespace": "1"}, `{"name":"LoremIpsum"}`)
|
||||
_, err := testHandlerNamespaceReadOnly.testUpdateWithLinkShare(nil, map[string]string{"namespace": "1"}, `{"title":"LoremIpsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared write", func(t *testing.T) {
|
||||
_, err := testHandlerNamespaceWrite.testUpdateWithLinkShare(nil, map[string]string{"namespace": "2"}, `{"name":"LoremIpsum"}`)
|
||||
_, err := testHandlerNamespaceWrite.testUpdateWithLinkShare(nil, map[string]string{"namespace": "2"}, `{"title":"LoremIpsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared admin", func(t *testing.T) {
|
||||
_, err := testHandlerNamespaceAdmin.testUpdateWithLinkShare(nil, map[string]string{"namespace": "3"}, `{"name":"LoremIpsum"}`)
|
||||
_, err := testHandlerNamespaceAdmin.testUpdateWithLinkShare(nil, map[string]string{"namespace": "3"}, `{"title":"LoremIpsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
@@ -862,34 +941,34 @@ func TestLinkSharing(t *testing.T) {
|
||||
})
|
||||
t.Run("Create", func(t *testing.T) {
|
||||
t.Run("Shared readonly", func(t *testing.T) {
|
||||
_, err := testHandlerNamespaceUserReadOnly.testCreateWithLinkShare(nil, nil, `{"userID":"user1"}`)
|
||||
_, err := testHandlerNamespaceUserReadOnly.testCreateWithLinkShare(nil, nil, `{"user_id":"user1"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared write", func(t *testing.T) {
|
||||
_, err := testHandlerNamespaceUserWrite.testCreateWithLinkShare(nil, nil, `{"userID":"user1"}`)
|
||||
_, err := testHandlerNamespaceUserWrite.testCreateWithLinkShare(nil, nil, `{"user_id":"user1"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared admin", func(t *testing.T) {
|
||||
_, err := testHandlerNamespaceUserAdmin.testCreateWithLinkShare(nil, nil, `{"userID":"user1"}`)
|
||||
_, err := testHandlerNamespaceUserAdmin.testCreateWithLinkShare(nil, nil, `{"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 := testHandlerNamespaceUserReadOnly.testUpdateWithLinkShare(nil, map[string]string{"namespace": "1"}, `{"userID":"user1"}`)
|
||||
_, err := testHandlerNamespaceUserReadOnly.testUpdateWithLinkShare(nil, map[string]string{"namespace": "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 := testHandlerNamespaceUserWrite.testUpdateWithLinkShare(nil, map[string]string{"namespace": "2"}, `{"userID":"user1"}`)
|
||||
_, err := testHandlerNamespaceUserWrite.testUpdateWithLinkShare(nil, map[string]string{"namespace": "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 := testHandlerNamespaceUserAdmin.testUpdateWithLinkShare(nil, map[string]string{"namespace": "3"}, `{"userID":"user1"}`)
|
||||
_, err := testHandlerNamespaceUserAdmin.testUpdateWithLinkShare(nil, map[string]string{"namespace": "3"}, `{"user_id":"user1"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
@@ -954,34 +1033,34 @@ func TestLinkSharing(t *testing.T) {
|
||||
})
|
||||
t.Run("Create", func(t *testing.T) {
|
||||
t.Run("Shared readonly", func(t *testing.T) {
|
||||
_, err := testHandlerNamespaceTeamReadOnly.testCreateWithLinkShare(nil, map[string]string{"namespace": "1"}, `{"teamID":1}`)
|
||||
_, err := testHandlerNamespaceTeamReadOnly.testCreateWithLinkShare(nil, map[string]string{"namespace": "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 := testHandlerNamespaceTeamWrite.testCreateWithLinkShare(nil, map[string]string{"namespace": "2"}, `{"teamID":1}`)
|
||||
_, err := testHandlerNamespaceTeamWrite.testCreateWithLinkShare(nil, map[string]string{"namespace": "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 := testHandlerNamespaceTeamAdmin.testCreateWithLinkShare(nil, map[string]string{"namespace": "3"}, `{"teamID":1}`)
|
||||
_, err := testHandlerNamespaceTeamAdmin.testCreateWithLinkShare(nil, map[string]string{"namespace": "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 := testHandlerNamespaceTeamReadOnly.testUpdateWithLinkShare(nil, map[string]string{"namespace": "1"}, `{"teamID":1}`)
|
||||
_, err := testHandlerNamespaceTeamReadOnly.testUpdateWithLinkShare(nil, map[string]string{"namespace": "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 := testHandlerNamespaceTeamWrite.testUpdateWithLinkShare(nil, map[string]string{"namespace": "2"}, `{"teamID":1}`)
|
||||
_, err := testHandlerNamespaceTeamWrite.testUpdateWithLinkShare(nil, map[string]string{"namespace": "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 := testHandlerNamespaceTeamAdmin.testUpdateWithLinkShare(nil, map[string]string{"namespace": "3"}, `{"teamID":1}`)
|
||||
_, err := testHandlerNamespaceTeamAdmin.testUpdateWithLinkShare(nil, map[string]string{"namespace": "3"}, `{"team_id":1}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
|
||||
@@ -2,27 +2,28 @@
|
||||
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// 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 General Public License for more details.
|
||||
// GNU Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// 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/url"
|
||||
"testing"
|
||||
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/web/handler"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/url"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
@@ -38,10 +39,12 @@ func TestList(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(nil, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `Test1`)
|
||||
assert.NotContains(t, rec.Body.String(), `Test2`)
|
||||
assert.NotContains(t, rec.Body.String(), `Test2"`)
|
||||
assert.Contains(t, rec.Body.String(), `Test3`) // Shared directly via users_list
|
||||
assert.Contains(t, rec.Body.String(), `Test4`) // Shared via namespace
|
||||
assert.NotContains(t, rec.Body.String(), `Test5`)
|
||||
assert.NotContains(t, rec.Body.String(), `Test21`) // Archived through namespace
|
||||
assert.NotContains(t, rec.Body.String(), `Test22`) // Archived directly
|
||||
})
|
||||
t.Run("Search", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(url.Values{"s": []string{"Test1"}}, nil)
|
||||
@@ -52,6 +55,17 @@ func TestList(t *testing.T) {
|
||||
assert.NotContains(t, rec.Body.String(), `Test4`)
|
||||
assert.NotContains(t, rec.Body.String(), `Test5`)
|
||||
})
|
||||
t.Run("Normal with archived lists", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(url.Values{"is_archived": []string{"true"}}, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `Test1`)
|
||||
assert.NotContains(t, rec.Body.String(), `Test2"`)
|
||||
assert.Contains(t, rec.Body.String(), `Test3`) // Shared directly via users_list
|
||||
assert.Contains(t, rec.Body.String(), `Test4`) // Shared via namespace
|
||||
assert.NotContains(t, rec.Body.String(), `Test5`)
|
||||
assert.Contains(t, rec.Body.String(), `Test21`) // Archived through namespace
|
||||
assert.Contains(t, rec.Body.String(), `Test22`) // Archived directly
|
||||
})
|
||||
})
|
||||
t.Run("ReadOne", func(t *testing.T) {
|
||||
t.Run("Normal", func(t *testing.T) {
|
||||
@@ -59,9 +73,10 @@ func TestList(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Test1"`)
|
||||
assert.NotContains(t, rec.Body.String(), `"title":"Test2"`)
|
||||
assert.Contains(t, rec.Body.String(), `"owner":{"id":1,"username":"user1",`)
|
||||
assert.NotContains(t, rec.Body.String(), `"owner":{"id":2,"username":"user2",`)
|
||||
assert.Contains(t, rec.Body.String(), `"owner":{"id":1,"name":"","username":"user1",`)
|
||||
assert.NotContains(t, rec.Body.String(), `"owner":{"id":2,"name":"","username":"user2",`)
|
||||
assert.NotContains(t, rec.Body.String(), `"tasks":`)
|
||||
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"})
|
||||
@@ -71,72 +86,85 @@ func TestList(t *testing.T) {
|
||||
t.Run("Rights check", func(t *testing.T) {
|
||||
t.Run("Forbidden", func(t *testing.T) {
|
||||
// Owned by user13
|
||||
_, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "20"})
|
||||
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "20"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `You don't have the right to see this`)
|
||||
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"})
|
||||
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"})
|
||||
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"})
|
||||
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"})
|
||||
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"})
|
||||
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"})
|
||||
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"})
|
||||
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"})
|
||||
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"})
|
||||
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"})
|
||||
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"})
|
||||
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"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Test17"`)
|
||||
assert.Equal(t, "2", rec.Result().Header.Get("x-max-right"))
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -165,15 +193,10 @@ func TestList(t *testing.T) {
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message.(models.ValidationHTTPError).InvalidFields, "title: non zero value required")
|
||||
})
|
||||
t.Run("Almost empty title", func(t *testing.T) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "1"}, `{"title":"nn"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message.(models.ValidationHTTPError).InvalidFields[0], "does not validate as runelength(3|250)")
|
||||
})
|
||||
t.Run("Title too long", func(t *testing.T) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "1"}, `{"title":"Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea taki"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message.(models.ValidationHTTPError).InvalidFields[0], "does not validate as runelength(3|250)")
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message.(models.ValidationHTTPError).InvalidFields[0], "does not validate as runelength(1|250)")
|
||||
})
|
||||
t.Run("Rights check", func(t *testing.T) {
|
||||
t.Run("Forbidden", func(t *testing.T) {
|
||||
@@ -358,15 +381,10 @@ func TestList(t *testing.T) {
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message.(models.ValidationHTTPError).InvalidFields, "title: non zero value required")
|
||||
})
|
||||
t.Run("Almost empty title", func(t *testing.T) {
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"namespace": "1"}, `{"title":"nn"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message.(models.ValidationHTTPError).InvalidFields[0], "does not validate as runelength(3|250)")
|
||||
})
|
||||
t.Run("Title too long", func(t *testing.T) {
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"namespace": "1"}, `{"title":"Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea taki"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message.(models.ValidationHTTPError).InvalidFields[0], "does not validate as runelength(3|250)")
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message.(models.ValidationHTTPError).InvalidFields[0], "does not validate as runelength(1|250)")
|
||||
})
|
||||
t.Run("Rights check", func(t *testing.T) {
|
||||
|
||||
|
||||
@@ -2,26 +2,27 @@
|
||||
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// 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 General Public License for more details.
|
||||
// GNU Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// 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"
|
||||
|
||||
apiv1 "code.vikunja.io/api/pkg/routes/api/v1"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLogin(t *testing.T) {
|
||||
|
||||
@@ -2,26 +2,27 @@
|
||||
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// 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 General Public License for more details.
|
||||
// GNU Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// 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"
|
||||
|
||||
apiv1 "code.vikunja.io/api/pkg/routes/api/v1"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRegister(t *testing.T) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user