Compare commits
169 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6045f4a426 | ||
|
|
7be14239a3 | ||
|
|
27ba31366c | ||
|
|
9dc419e854 | ||
|
|
37efd5cb49 | ||
|
|
dcec9511dc | ||
|
|
c203d73b33 | ||
|
|
9be5ab248c | ||
|
|
ed4c17892e | ||
|
|
886110a20a | ||
|
|
8948a5f219 | ||
|
|
89f385a53d | ||
|
|
82f11c4ec2 | ||
|
|
eb279fdba3 | ||
|
|
48e422b379 | ||
|
|
854fde1e4c | ||
|
|
b653278e42 | ||
|
|
35e4ab198f | ||
|
|
b81cd6128a | ||
|
|
2169464983 | ||
|
|
e2f481a6e5 | ||
|
|
af1d1e1921 | ||
|
|
8fe33fd616 | ||
|
|
1272255975 | ||
|
|
1e2ec17343 | ||
|
|
2f267f3f68 | ||
|
|
694fc62495 | ||
|
|
8d5a2685c4 | ||
|
|
71ef86e0df | ||
|
|
6140920cb8 | ||
|
|
fdd1624121 | ||
|
|
158ad631e1 | ||
|
|
c126d2f55c | ||
|
|
1d98e4cabe | ||
|
|
43676f045c | ||
|
|
ba7db545fe | ||
|
|
021ee48ec3 | ||
|
|
a324921dce | ||
|
|
8d57923a7d | ||
|
|
88ea66798b | ||
|
|
66cdd79666 | ||
|
|
be14634e1e | ||
|
|
8fbe721453 | ||
|
|
5d3e169883 | ||
|
|
208eb4e68c | ||
|
|
8d76280811 | ||
|
|
10fbbe1db6 | ||
|
|
4327a559e5 | ||
|
|
2e599e792e | ||
|
|
48826a6ed7 | ||
|
|
15a0963bd1 | ||
|
|
9a7d470ce2 | ||
|
|
ef0e7c9769 | ||
|
|
12eaddc8ee | ||
|
|
b63928850a | ||
|
|
4005cd2f32 | ||
|
|
5cc89fd474 | ||
|
|
e2d9de191d | ||
|
|
c3ea45d900 | ||
|
|
50ca8bd28e | ||
|
|
1f1a079fd3 | ||
|
|
f1d21ea52b | ||
|
|
fc3c5f2187 | ||
|
|
5d3b6573ca | ||
|
|
9930f98f8e | ||
|
|
16825ba7c6 | ||
|
|
4e8c2a7bf6 | ||
|
|
7f5a6e338c | ||
|
|
2763df20c9 | ||
|
|
9bc09ebbe5 | ||
|
|
6746984c97 | ||
|
|
7acf318b28 | ||
|
|
f638fae4fd | ||
|
|
bd930bf654 | ||
|
|
2cdb0eed3e | ||
|
|
85d08e5697 | ||
|
|
4c4eb76fed | ||
|
|
50d1f29125 | ||
|
|
3fffcbd986 | ||
|
|
24ce940885 | ||
|
|
802a13cffd | ||
|
|
6b348fad04 | ||
|
|
951d92b2f0 | ||
|
|
24f06b2da5 | ||
|
|
a7bbaf7c5f | ||
|
|
3356a691f7 | ||
|
|
7d46c88285 | ||
|
|
7107d030fc | ||
|
|
de24fcc2f8 | ||
|
|
3d7fd9ca20 | ||
|
|
2b160b73c3 | ||
|
|
24704bd4ed | ||
|
|
733d0be753 | ||
|
|
e40ab8652d | ||
|
|
b854112704 | ||
|
|
e2820e6806 | ||
|
|
a6e70f1b29 | ||
|
|
44223bde77 | ||
|
|
d404716342 | ||
|
|
7e38937efc | ||
|
|
ed671baf8f | ||
|
|
d4ff1eca80 | ||
|
|
6bab8fb769 | ||
|
|
07bdb08f9c | ||
|
|
1117b6eb47 | ||
|
|
10f80c190b | ||
|
|
c8858f6cfb | ||
|
|
dd1749f11f | ||
|
|
99f83542f6 | ||
|
|
5dc1fd0f86 | ||
|
|
b1701ca769 | ||
|
|
5cfaa0f322 | ||
|
|
7b332808f9 | ||
|
|
228c47d79e | ||
|
|
27339a7cf0 | ||
|
|
3adfb81d5e | ||
|
|
3872d1d8a7 | ||
|
|
46efcb1005 | ||
|
|
0ed6c6cc0d | ||
|
|
93dbaea303 | ||
|
|
55e33c1694 | ||
|
|
2d9d27f63c | ||
|
|
87873e53c5 | ||
|
|
19faee0102 | ||
|
|
ac5446e4f5 | ||
|
|
a799527a3c | ||
|
|
2b28ab12f1 | ||
|
|
be5a17e993 | ||
|
|
e21471a193 | ||
|
|
81f76f09ce | ||
|
|
c27e8fe2f1 | ||
|
|
8d78e473f5 | ||
|
|
5525ee0328 | ||
|
|
47352d3ed4 | ||
|
|
11e7c071ce | ||
|
|
1dc14d5ddf | ||
|
|
c42e22f352 | ||
|
|
72e64f7203 | ||
|
|
25999d9b69 | ||
|
|
eb4d38b5b8 | ||
|
|
65f428fe78 | ||
|
|
2cc968ec61 | ||
|
|
073aa9940f | ||
|
|
5506dd1ceb | ||
|
|
e47313b17e | ||
|
|
7cc172cc0c | ||
|
|
2bcd6e9cb6 | ||
|
|
afd55d8cf8 | ||
|
|
caed219d39 | ||
|
|
1b84292332 | ||
|
|
15ef6deabc | ||
|
|
a06a5fc4f4 | ||
|
|
2d88fad5b1 | ||
|
|
5e7c9b9eb9 | ||
|
|
9e635ea54e | ||
|
|
d0fa9ddaec | ||
|
|
add491d1e0 | ||
|
|
e735378caf | ||
|
|
f95aa431b8 | ||
|
|
01e7540530 | ||
|
|
75431e1ca5 | ||
|
|
f6c7c764d1 | ||
|
|
eedc19a49e | ||
|
|
c07e2b6cd4 | ||
|
|
08cbd18bc5 | ||
|
|
8362799a93 | ||
|
|
3edc728094 | ||
|
|
d3975193fe | ||
|
|
6b00ccc942 |
352
.drone1.yml
352
.drone1.yml
@@ -2,39 +2,51 @@ kind: pipeline
|
||||
name: testing
|
||||
|
||||
workspace:
|
||||
base: /srv/app
|
||||
base: /go
|
||||
path: src/code.vikunja.io/api
|
||||
|
||||
clone:
|
||||
depth: 50
|
||||
|
||||
services:
|
||||
- name: test-db
|
||||
- name: test-db-unit
|
||||
image: mariadb:10
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: vikunjatest
|
||||
MYSQL_DATABASE: vikunjatest
|
||||
- name: test-db-integration
|
||||
image: mariadb:10
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: vikunjatest
|
||||
MYSQL_DATABASE: vikunjatest
|
||||
|
||||
trigger:
|
||||
branch:
|
||||
include:
|
||||
- master
|
||||
event:
|
||||
include:
|
||||
- push
|
||||
- pull_request
|
||||
|
||||
steps:
|
||||
- name: fetch-tags
|
||||
image: docker:git
|
||||
commands:
|
||||
- git fetch --tags
|
||||
when:
|
||||
event: [ tag ]
|
||||
|
||||
- name: build
|
||||
image: vikunja/golang-build:latest
|
||||
pull: true
|
||||
environment:
|
||||
TAGS: bindata sqlite
|
||||
GOFLAGS: '-mod=vendor'
|
||||
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
|
||||
when:
|
||||
event: [ push, tag, pull_request ]
|
||||
@@ -66,7 +78,7 @@ steps:
|
||||
environment:
|
||||
VIKUNJA_TESTS_USE_CONFIG: 1
|
||||
VIKUNJA_DATABASE_TYPE: mysql
|
||||
VIKUNJA_DATABASE_HOST: test-db
|
||||
VIKUNJA_DATABASE_HOST: test-db-unit
|
||||
VIKUNJA_DATABASE_USER: root
|
||||
VIKUNJA_DATABASE_PASSWORD: vikunjatest
|
||||
VIKUNJA_DATABASE_DATABASE: vikunjatest
|
||||
@@ -76,6 +88,43 @@ steps:
|
||||
when:
|
||||
event: [ push, tag, pull_request ]
|
||||
|
||||
- name: integration-test
|
||||
image: vikunja/golang-build:latest
|
||||
pull: true
|
||||
commands:
|
||||
- make integration-test
|
||||
depends_on: [ build ]
|
||||
when:
|
||||
event: [ push, tag, pull_request ]
|
||||
|
||||
- name: integration-test-sqlite
|
||||
image: vikunja/golang-build:latest
|
||||
pull: true
|
||||
environment:
|
||||
VIKUNJA_TESTS_USE_CONFIG: 1
|
||||
VIKUNJA_DATABASE_TYPE: sqlite
|
||||
commands:
|
||||
- make integration-test
|
||||
depends_on: [ build ]
|
||||
when:
|
||||
event: [ push, tag, pull_request ]
|
||||
|
||||
- name: integration-test-mysql
|
||||
image: vikunja/golang-build:latest
|
||||
pull: true
|
||||
environment:
|
||||
VIKUNJA_TESTS_USE_CONFIG: 1
|
||||
VIKUNJA_DATABASE_TYPE: mysql
|
||||
VIKUNJA_DATABASE_HOST: test-db-integration
|
||||
VIKUNJA_DATABASE_USER: root
|
||||
VIKUNJA_DATABASE_PASSWORD: vikunjatest
|
||||
VIKUNJA_DATABASE_DATABASE: vikunjatest
|
||||
commands:
|
||||
- make integration-test
|
||||
depends_on: [ build ]
|
||||
when:
|
||||
event: [ push, tag, pull_request ]
|
||||
|
||||
---
|
||||
########
|
||||
# Build a release when pushing to master
|
||||
@@ -88,12 +137,9 @@ depends_on:
|
||||
- testing
|
||||
|
||||
workspace:
|
||||
base: /srv/app
|
||||
base: /go
|
||||
path: src/code.vikunja.io/api
|
||||
|
||||
clone:
|
||||
depth: 50
|
||||
|
||||
trigger:
|
||||
branch:
|
||||
- master
|
||||
@@ -101,49 +147,72 @@ trigger:
|
||||
- push
|
||||
|
||||
steps:
|
||||
# Needed to get the versions right as they depend on tags
|
||||
- name: fetch-tags
|
||||
image: docker:git
|
||||
commands:
|
||||
- git fetch --tags
|
||||
|
||||
- name: before-static-build
|
||||
image: karalabe/xgo-latest:latest
|
||||
image: techknowlogick/xgo:latest
|
||||
pull: true
|
||||
commands:
|
||||
- export PATH=$PATH:$GOPATH/bin
|
||||
- make generate
|
||||
- make release-dirs
|
||||
depends_on: [ fetch-tags ]
|
||||
|
||||
- name: static-build-windows
|
||||
image: karalabe/xgo-latest:latest
|
||||
image: techknowlogick/xgo:latest
|
||||
pull: true
|
||||
environment:
|
||||
TAGS: bindata sqlite
|
||||
# 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: karalabe/xgo-latest:latest
|
||||
image: techknowlogick/xgo:latest
|
||||
pull: true
|
||||
environment:
|
||||
TAGS: bindata sqlite
|
||||
# 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: karalabe/xgo-latest:latest
|
||||
image: techknowlogick/xgo:latest
|
||||
pull: true
|
||||
environment:
|
||||
TAGS: bindata sqlite
|
||||
# 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-static
|
||||
image: karalabe/xgo-latest:latest
|
||||
- name: after-build-compress
|
||||
image: kolaente/upx
|
||||
pull: true
|
||||
depends_on:
|
||||
- static-build-windows
|
||||
- static-build-linux
|
||||
- static-build-darwin
|
||||
commands:
|
||||
- make release-compress
|
||||
|
||||
- name: after-build-static
|
||||
image: techknowlogick/xgo:latest
|
||||
pull: true
|
||||
depends_on:
|
||||
- after-build-compress
|
||||
commands:
|
||||
- make release-copy
|
||||
- make release-check
|
||||
@@ -180,6 +249,49 @@ steps:
|
||||
target: /master/
|
||||
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
|
||||
@@ -191,20 +303,25 @@ steps:
|
||||
from_secret: docker_password
|
||||
repo: vikunja/api
|
||||
auto_tag: true
|
||||
depends_on: [ fetch-tags ]
|
||||
|
||||
# Update the instance on try.vikunja.io
|
||||
- name: rancher
|
||||
image: peloton/drone-rancher
|
||||
- name: telegram
|
||||
image: appleboy/drone-telegram
|
||||
depends_on:
|
||||
- release-latest
|
||||
settings:
|
||||
url: http://server01.kolaente.de:8080/v1
|
||||
access_key:
|
||||
from_secret: RANCHER_ACCESS_KEY
|
||||
secret_key:
|
||||
from_secret: RANCHER_SECRET_KEY
|
||||
service: vikunja-dev/api
|
||||
docker_image: vikunja/api
|
||||
confirm: true
|
||||
depends_on: [ docker ]
|
||||
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
|
||||
@@ -217,12 +334,9 @@ depends_on:
|
||||
- testing
|
||||
|
||||
workspace:
|
||||
base: /srv/app
|
||||
base: /go
|
||||
path: src/code.vikunja.io/api
|
||||
|
||||
clone:
|
||||
depth: 50
|
||||
|
||||
trigger:
|
||||
event:
|
||||
- tag
|
||||
@@ -234,48 +348,64 @@ steps:
|
||||
- git fetch --tags
|
||||
|
||||
- name: before-static-build
|
||||
image: karalabe/xgo-latest:latest
|
||||
image: techknowlogick/xgo:latest
|
||||
pull: true
|
||||
commands:
|
||||
- make generate
|
||||
- make release-dirs
|
||||
depends_on: [ fetch-tags ]
|
||||
|
||||
- name: static-build-windows
|
||||
image: karalabe/xgo-latest:latest
|
||||
image: techknowlogick/xgo:latest
|
||||
pull: true
|
||||
environment:
|
||||
TAGS: bindata sqlite
|
||||
# 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: karalabe/xgo-latest:latest
|
||||
image: techknowlogick/xgo:latest
|
||||
pull: true
|
||||
environment:
|
||||
TAGS: bindata sqlite
|
||||
# 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: karalabe/xgo-latest:latest
|
||||
image: techknowlogick/xgo:latest
|
||||
pull: true
|
||||
environment:
|
||||
TAGS: bindata sqlite
|
||||
# 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-static
|
||||
image: karalabe/xgo-latest:latest
|
||||
- name: after-build-compress
|
||||
image: kolaente/upx
|
||||
pull: true
|
||||
depends_on:
|
||||
- static-build-windows
|
||||
- static-build-linux
|
||||
- static-build-darwin
|
||||
commands:
|
||||
- make release-compress
|
||||
|
||||
- name: after-build-static
|
||||
image: techknowlogick/xgo:latest
|
||||
pull: true
|
||||
depends_on:
|
||||
- after-build-compress
|
||||
commands:
|
||||
- make release-copy
|
||||
- make release-check
|
||||
@@ -312,6 +442,49 @@ steps:
|
||||
target: /${DRONE_TAG##v}/
|
||||
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
|
||||
@@ -323,35 +496,13 @@ steps:
|
||||
from_secret: docker_password
|
||||
repo: vikunja/api
|
||||
auto_tag: true
|
||||
depends_on: [ fetch-tags ]
|
||||
|
||||
# Update the instance on try.vikunja.io
|
||||
- name: rancher
|
||||
image: peloton/drone-rancher
|
||||
settings:
|
||||
url: http://server01.kolaente.de:8080/v1
|
||||
access_key:
|
||||
from_secret: RANCHER_ACCESS_KEY
|
||||
secret_key:
|
||||
from_secret: RANCHER_SECRET_KEY
|
||||
service: vikunja-dev/api
|
||||
docker_image: vikunja/api
|
||||
confirm: true
|
||||
depends_on: [ docker ]
|
||||
|
||||
---
|
||||
#############
|
||||
# Tell people vikunja was updated
|
||||
#############
|
||||
|
||||
kind: pipeline
|
||||
name: notify
|
||||
depends_on:
|
||||
- deploy-version
|
||||
- deploy-master
|
||||
|
||||
steps:
|
||||
- name: telegram
|
||||
image: appleboy/drone-telegram
|
||||
depends_on:
|
||||
- docker
|
||||
- release-version
|
||||
settings:
|
||||
token:
|
||||
from_secret: TELEGRAM_TOKEN
|
||||
@@ -364,4 +515,57 @@ steps:
|
||||
when:
|
||||
status:
|
||||
- success
|
||||
- failure
|
||||
- failure
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
name: deploy-docs
|
||||
|
||||
workspace:
|
||||
base: /go
|
||||
path: src/code.vikunja.io/api
|
||||
|
||||
clone:
|
||||
depth: 50
|
||||
|
||||
trigger:
|
||||
event:
|
||||
- push
|
||||
branch:
|
||||
- master
|
||||
|
||||
steps:
|
||||
- name: submodules
|
||||
image: docker:git
|
||||
commands:
|
||||
- git submodule update --init
|
||||
- git submodule update --recursive --remote
|
||||
|
||||
- name: theme
|
||||
image: kolaente/yarn
|
||||
pull: true
|
||||
group: build-static
|
||||
commands:
|
||||
- cd docs/themes/vikunja
|
||||
- yarn --production=false
|
||||
- ./node_modules/.bin/gulp
|
||||
|
||||
- name: build
|
||||
image: monachus/hugo:v0.54.0
|
||||
pull: true
|
||||
commands:
|
||||
- cd docs
|
||||
- hugo
|
||||
- mv public/docs/* public # Hugo seems to be not capable of setting a different theme for a home page, so we do this ugly hack to fix it.
|
||||
|
||||
- name: docker
|
||||
image: plugins/docker
|
||||
pull: true
|
||||
settings:
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
repo: vikunja/docs
|
||||
context: docs/
|
||||
dockerfile: docs/Dockerfile
|
||||
|
||||
12
.gitea/pull_request_template.md
Normal file
12
.gitea/pull_request_template.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# Description
|
||||
|
||||
|
||||
|
||||
# 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`)
|
||||
* [ ] Error codes
|
||||
* [ ] New config options
|
||||
12
.gitignore
vendored
12
.gitignore
vendored
@@ -3,10 +3,20 @@
|
||||
.idea/httpRequests
|
||||
config.yml
|
||||
config.yaml
|
||||
!docs/config.yml
|
||||
*.db
|
||||
Run
|
||||
dist/
|
||||
cover.*
|
||||
/vikunja
|
||||
Test_*
|
||||
bin/
|
||||
bin/
|
||||
secrets
|
||||
*.deb
|
||||
debian/
|
||||
logs/
|
||||
docs/public/
|
||||
docs/resources/
|
||||
pkg/static/templates_vfsdata.go
|
||||
files/
|
||||
!pkg/files/
|
||||
|
||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
[submodule "docs/themes/vikunja"]
|
||||
path = docs/themes/vikunja
|
||||
url = https://git.kolaente.de/vikunja/theme.git
|
||||
199
CHANGELOG.md
Normal file
199
CHANGELOG.md
Normal file
@@ -0,0 +1,199 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
All releases can be found on https://code.vikunja.io/api/releases.
|
||||
|
||||
## [0.9] - 2019-11-24
|
||||
|
||||
### Added
|
||||
|
||||
* Task Attachments (#104)
|
||||
* Task Relations (#103)
|
||||
* Add endpoint to get a single task (#106)
|
||||
* Add file volume to the docker image
|
||||
* Added extra depth to logging to correctly show the functions calling the logger in logs
|
||||
* Added more infos to a link share auth (#98)
|
||||
* Added percent done to tasks (#102)
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fix default logging settings (#107)
|
||||
* Fixed a bug where adding assignees or reminders via an update would re-create them and not respect already inserted ones, leaving a lot of garbage
|
||||
* Fixed a bug where deleting an attachment would cause a nil panic
|
||||
* Fixed building docs theme
|
||||
* Fixed error when setting max file size on 32-Bit systems
|
||||
* Fixed labels being displayed multiple times if they were associated with more than one task (#99)
|
||||
* Fixed metrics on/off setting
|
||||
* Fixed migration for task relations
|
||||
* Fixed not getting all labels when retrieving a list with all tasks
|
||||
* Fixed panic when using link share and metrics
|
||||
* Fixed rate limit panic when authenticating with a link share auth token (#97)
|
||||
* Fixed removing reminders
|
||||
* Small link share fixes (#96)
|
||||
|
||||
### Changed
|
||||
|
||||
* Improve pagination (#105)
|
||||
* Moved `teams_{namespace|list}_*` to `{namespace|list}_teams_*` for better consistency (#101)
|
||||
* Refactored getting all lists for a namespace (#100)
|
||||
* Refactored getting task IDs for labels
|
||||
* Switched default logger to stdout instead of stderr
|
||||
* update docs theme
|
||||
|
||||
### Misc
|
||||
|
||||
* Move from markdown lists to Vikunja for roadmap
|
||||
|
||||
## [0.8] - 2019-09-01
|
||||
|
||||
### Added
|
||||
|
||||
* Better Caldav support (#73)
|
||||
* Added settings for max open/idle connections and max connection lifetime (#74)
|
||||
* /info endpoint (#85)
|
||||
* Added http endpoint to list all users on a list (#87)
|
||||
* Rate limits (#91)
|
||||
* Sharing of lists via public links (#94)
|
||||
|
||||
### Changed
|
||||
|
||||
* Reminders now use an extra table (#75)
|
||||
* Use the username instead of a full user object when adding a user to a team or giving it rights (#76)
|
||||
* Add the md5-hashed user email to user objects for use with gravatar (#78)
|
||||
* Use the auth methods to get IDs to avoid unneeded casts
|
||||
* Better config handling with constants (#83)
|
||||
* Statically compile templates in the final binary (#84)
|
||||
* Use longtext instead of varchar(1000) on description fields (#88)
|
||||
* Logger refactoring (#90)
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed `listID` not being returned in tasks
|
||||
* Fixed tests (#72)
|
||||
* Fixed metrics endpoint not working
|
||||
* Fixed check if the user really exists before updating/deleting its rights (#77)
|
||||
* Fixed duedate spelling issue (#79)
|
||||
|
||||
### Misc
|
||||
|
||||
* Integration tests (#71)
|
||||
* Make sure the version works when building in drone
|
||||
* Switched to another version of xgo
|
||||
* Simplified the docker image (#80)
|
||||
* Update echo (#82)
|
||||
* Compress binaries after building them (#81)
|
||||
* Simplify structure by having less files (#86)
|
||||
* Limit the test pipeline to run only on pull requests (#89)
|
||||
* GetUser now returns a pointer (#93)
|
||||
* Refactor ListTask to Task (#92)
|
||||
|
||||
## [0.7] - 2019-04-05
|
||||
|
||||
### Added
|
||||
|
||||
* DB migrations (#67)
|
||||
* More cli options for Vikunja (#66 #68)
|
||||
* Use query params to sort tasks instead of url params (#61)
|
||||
* More config paths (#55)
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed Priority not updating when setting it to 0
|
||||
* Fixed getting lists by namespace
|
||||
* Fixed rights check (#70 #62)
|
||||
* Fixed labels not being queried correctly on tasks
|
||||
* Fixed bulk update label tasks
|
||||
|
||||
### Changed
|
||||
|
||||
* Hide a user's email address everywhere (#69)
|
||||
* Refactored `canRead()` to get the list before checking rights #65
|
||||
* Let rights methods return errors (#64 #63)
|
||||
* Improved Swagger docs for label tasks
|
||||
* Docs improvements (#58)
|
||||
* Logging Handling (#57)
|
||||
* Rights performance improvements (#54)
|
||||
|
||||
### Misc
|
||||
|
||||
* Releases also as Debian packages (#56)
|
||||
|
||||
## [0.6] - 2019-01-16
|
||||
|
||||
### Added
|
||||
|
||||
* Added prometheus endpoint to get metrics (#33)
|
||||
* More unit tests (#34)
|
||||
* Tests can now use config files (#36)
|
||||
* Redoc for swagger ui (#39, #46)
|
||||
* Start and end dates for tasks (#40)
|
||||
* Get tasks between a date range (#41)
|
||||
* Bulk edit for tasks (#42)
|
||||
* More ci checks (#43)
|
||||
* Task assignees (#44, #47)
|
||||
* Task labels (#45, #48)
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed path to get all tasks (echo bug)
|
||||
* Explicitly get the peudonamespace with all shared lists (#32)
|
||||
* Properly init tabels Redis
|
||||
* unexpected EOF when using metrics (#35)
|
||||
* Task sorting in lists (#36)
|
||||
* Various user fixes (#38)
|
||||
* Fixed a bug where updating a list would update it with the same values it had
|
||||
|
||||
### Changed
|
||||
|
||||
* Simplified list rights check (#50)
|
||||
* Refactored some structs to not expose unneded values via json (#52)
|
||||
|
||||
### Misc
|
||||
|
||||
* Updated libraries
|
||||
* Updated drone to version 1
|
||||
* Releases are now signed with our pgp key (more info about this on [the download page](https://vikunja.io/en/download/)).
|
||||
|
||||
## [0.5] - 2018-12-02
|
||||
|
||||
### Added
|
||||
|
||||
* Shared lists are now shown in a pseudonamespace with all other namespaces, has the ID -1
|
||||
* Tasks can have multiple reminders
|
||||
* Tasks can have subtasks. Subtasks are fully-fleged tasks, but not shown in the task list of a list.
|
||||
* Tasks can have priorities
|
||||
|
||||
### Changed
|
||||
|
||||
* Validation not so verbose anymore
|
||||
* [License](https://git.kolaente.de/vikunja/api/src/branch/master/LICENSE) is now GPLv3
|
||||
* The crudhandler now has its [own repo](https://git.kolaente.de/vikunja/web) - you can use it in your own projects!
|
||||
|
||||
## [0.4] - 2018-11-16
|
||||
|
||||
#### Added
|
||||
|
||||
* Get all tasks for the authenticated user sorted by their due date
|
||||
* CalDAV support
|
||||
* Pagination for everything which returns an array
|
||||
* Search all the things
|
||||
* More validation for most of the structs
|
||||
* Improved Swagger docs (available on `/api/v1/swagger`)
|
||||
|
||||
## [0.3] - 2018-11-02
|
||||
|
||||
### Added
|
||||
|
||||
* Password reset
|
||||
* Email verification when registering
|
||||
|
||||
Misc bugfixes and improvements to the build process
|
||||
|
||||
## [0.2] - 2018-10-17
|
||||
|
||||
## [0.1] - 2018-09-20
|
||||
|
||||
51
Dockerfile
51
Dockerfile
@@ -1,45 +1,42 @@
|
||||
|
||||
###################################
|
||||
#Build stage
|
||||
FROM golang:1.11-alpine AS build-env
|
||||
##############
|
||||
# Build stage
|
||||
FROM golang:1-alpine AS build-env
|
||||
|
||||
ARG VIKUNJA_VERSION
|
||||
ENV TAGS "sqlite"
|
||||
ENV GO111MODULE=on
|
||||
ENV GOFLAGS=-mod=vendor
|
||||
|
||||
#Build deps
|
||||
# Build deps
|
||||
RUN apk --no-cache add build-base git
|
||||
|
||||
#Setup repo
|
||||
# Setup repo
|
||||
COPY . ${GOPATH}/src/code.vikunja.io/api
|
||||
WORKDIR ${GOPATH}/src/code.vikunja.io/api
|
||||
|
||||
#Checkout version if set
|
||||
# Checkout version if set
|
||||
RUN if [ -n "${VIKUNJA_VERSION}" ]; then git checkout "${VIKUNJA_VERSION}"; fi \
|
||||
&& make clean build
|
||||
&& make clean generate build
|
||||
|
||||
FROM alpine:3.7
|
||||
###################
|
||||
# The actual image
|
||||
# Note: I wanted to use the scratch image here, but unfortunatly the go-sqlite bindings require cgo and
|
||||
# because of this, the container would not start when I compiled the image without cgo.
|
||||
FROM alpine:3.9
|
||||
LABEL maintainer="maintainers@vikunja.io"
|
||||
|
||||
EXPOSE 3456
|
||||
|
||||
RUN apk --no-cache add \
|
||||
bash \
|
||||
ca-certificates \
|
||||
curl \
|
||||
gettext \
|
||||
linux-pam \
|
||||
s6 \
|
||||
sqlite \
|
||||
su-exec \
|
||||
tzdata
|
||||
|
||||
COPY docker /
|
||||
COPY --from=build-env /go/src/code.vikunja.io/api/templates /app/vikunja/templates
|
||||
COPY --from=build-env /go/src/code.vikunja.io/api/vikunja /app/vikunja/vikunja
|
||||
|
||||
WORKDIR /app/vikunja/
|
||||
COPY --from=build-env /go/src/code.vikunja.io/api/vikunja .
|
||||
RUN adduser -S -D vikunja -h /app/vikunja -H \
|
||||
&& chown vikunja -R /app/vikunja
|
||||
ENV VIKUNJA_SERVICE_ROOTPATH=/app/vikunja/
|
||||
|
||||
ENTRYPOINT ["/bin/s6-svscan", "/etc/services.d"]
|
||||
CMD []
|
||||
# Files permissions
|
||||
RUN mkdir /app/vikunja/files && \
|
||||
chown -R vikunja /app/vikunja/files
|
||||
VOLUME /app/vikunja/files
|
||||
|
||||
USER vikunja
|
||||
CMD ["/app/vikunja/vikunja"]
|
||||
EXPOSE 3456
|
||||
|
||||
165
Featurecreep.md
165
Featurecreep.md
@@ -1,165 +0,0 @@
|
||||
# Featurecreep
|
||||
|
||||
This is the place where I write down ideas to work on at some point.
|
||||
Sorry for some of them being in German, I'll tranlate them at some point.
|
||||
|
||||
## Feature-Ideen
|
||||
|
||||
* [x] Priorities
|
||||
* [x] Repeating tasks
|
||||
* [x] Tagesübersicht ("Was ist heute/diese Woche due?") -> Machen letztenendes die Clients, wir brauchen nur nen endpoint, der alle tasks auskotzt, der Client macht dann die Sortierung.
|
||||
* [x] Subtasks
|
||||
|
||||
## Anderes
|
||||
|
||||
* [x] Refactor!!!! Alle Funktionen raus, die nicht mehr grbaucht werden + Funktionen vereinfachen/zusammenführen.
|
||||
Wenn ein Objekt 5x hin und hergereicht wird, und jedesmal nur geringfügig was dran geändert wird sollte das
|
||||
doch auch in einer Funktion machbar sein.
|
||||
* [x] ganz viel in eigene neue Dateien + Packages auslagern, am besten eine package pro model mit allen methoden etc.
|
||||
* [x] Alle alten dinger die nicht mehr gebraucht werden, weg.
|
||||
* [x] Die alten handlerfunktionen alle in eine datei packen und erstmal "lagern", erstmal brauchen wir die noch für swagger.
|
||||
* [x] Drone aufsetzen
|
||||
* [x] Tests schreiben
|
||||
* [x] Namen finden
|
||||
* [x] Alle Packages umziehen
|
||||
* [x] Swagger UI aufsetzen
|
||||
+ [x] CORS fixen
|
||||
* [x] Überall echo.NewHTTPError statt c.JSON(Message{}) benutzen
|
||||
* [x] Bessere Fehlermeldungen wenn das Model was ankommt falsch ist und nicht geparst werden kann
|
||||
* [x] Fehlerhandling irgendwie besser machen. Zb mit "World error messages"? Sprich, die Methode ruft einfach auf obs die entsprechende Fehlermeldung gibt und zeigt sonst 500 an.
|
||||
* [x] Viper für config einbauen und ini rauswerfen
|
||||
* [x] Docs für installationsanleitung
|
||||
* [x] Tests für Rechtekram
|
||||
* [x] "Apiformat" Methoden, damit in der Ausgabe zb kein Passwort drin ist..., oder created/updated von Nutzern... oder ownerID nicht drin ist sondern nur das ownerobject
|
||||
* [x] Rechte überprüfen:
|
||||
* [x] Listen erstellen
|
||||
* [x] Listen bearbeiten (nur eigene im Moment)
|
||||
* [x] Listenpunkte hinzufügen
|
||||
* [x] Listenpunkte bearbeiten
|
||||
* [x] Der -1 namespace sollte auch seperat angesprochen werden können, gibt sonst probleme mit der app.
|
||||
|
||||
### Short Term
|
||||
|
||||
* [x] Cacher konfigurierbar
|
||||
* [x] Wenn die ID bei irgendeiner GetByID... Methode < 1 ist soll ein error not exist geworfen werden
|
||||
* [x] /users sollte die Rechte mit ausgeben
|
||||
* [x] Nen endpoint um /teams/members /list/users etc die Rechte updazudaten ohne erst zu löschen und dann neu einzufügen
|
||||
* [x] namespaces & listen updaten geht nicht, gibt nen 500er zurück
|
||||
* [x] Logging für alle Fehler irgendwohin, da gibts bestimmt ne coole library für
|
||||
* [x] Ne extra funktion für list exists machen, damit die nicht immer über GetListByID gehen, um sql-abfragen zu sparen
|
||||
* [x] Rausfinden warum xorm teilweise beim einfügen IDs mit einfügen will -> Das schlägt dann wegen duplicate fehl
|
||||
* [x] Bei den Structs "AfterLoad" raus, das verbraucht bei Gruppenabfragen zu viele SQL-Abfragen -> Die sollen einfach die entsprechenden Read()-Methoden verwenden (Krassestes bsp. ist GET /namespaces mit so ca 50 Abfragen)
|
||||
* [x] General search endpoints
|
||||
* [x] Validation der ankommenden structs, am besten mit https://github.com/go-validator/validator oder mit dem Ding von echo
|
||||
* [x] Pagination
|
||||
* Sollte in der Config definierbar sein, wie viel pro Seite angezeigt werden soll, die CRUD-Methoden übergeben dann ein "gibt mir die Seite sowieso" an die CRUDable-Funktionenen, die müssen das dann Auswerten. Geht leider nicht anders, wenn man erst 2342352 Einträge hohlt und die dann nachträglich auf 200 begrenzt ist das ne massive Ressourcenverschwendung.
|
||||
* [x] Testen, ob man über die Routen methode von echo irgendwie ein swagger spec generieren könnte -> Andere Swagger library
|
||||
* [ ] CalDAV
|
||||
* [x] Basics
|
||||
* [x] Reminders
|
||||
* [ ] Discovery, stichwort PROPFIND
|
||||
* [x] Wir brauchen noch ne gute idee, wie man die listen kriegt, auf die man nur so Zugriff hat (ohne namespace)
|
||||
* Dazu am Besten nen pseudonamespace anlegen (id -1 oder so), der hat das dann alles
|
||||
* [x] Testing mit locust: https://locust.io/
|
||||
|
||||
#### Userstuff
|
||||
|
||||
* [x] Userstuff aufräumen
|
||||
-> Soweit es geht und Sinnvoll ist auf den neuen Handler umziehen
|
||||
-> Login/Register/Password-reset geht natürlich nicht
|
||||
-> Bleibt noch Profile abrufen und Einstellungen -> Macht also keinen Sinn das auf den neuen Handler umzuziehen
|
||||
* [x] Email-Verifizierung beim Registrieren
|
||||
* [x] Password Reset -> Link via email oder so
|
||||
* [ ] Settings
|
||||
* [ ] Password update
|
||||
* [ ] Email update
|
||||
* [ ] Ob man über email oder Benutzernamen gefunden werden darf
|
||||
|
||||
### Bugfixes
|
||||
|
||||
* [x] Panic wenn mailer nicht erreichbar -> Als workaround mailer deaktivierbar machen, bzw keine mails verschicken
|
||||
* [x] "unexpected EOF"
|
||||
* [x] Beim Login & Password reset gibt die API zurück dass der Nutzer nicht existiert
|
||||
|
||||
### Docs
|
||||
|
||||
* [ ] Readme
|
||||
* [x] Auch noch nen "link" zum Featurecreep
|
||||
* [ ] ToC
|
||||
* [ ] Logo
|
||||
* [ ] How to build -> Docs
|
||||
* [ ] How to use -> Docs
|
||||
* [ ] How to dev -> Docs
|
||||
* [ ] License
|
||||
* [ ] Contributing
|
||||
* [x] Redocs
|
||||
* [x] Swaggerdocs verbessern
|
||||
* [x] Descriptions in structs
|
||||
* [x] Maxlength specify etc. (see swaggo docs)
|
||||
* [x] Rights
|
||||
* [x] API
|
||||
* [ ] Anleitung zum Makefile
|
||||
* [ ] Struktur erklären
|
||||
* [ ] How to build from source
|
||||
* [ ] Dev instructions (tests, makefile, mod, structure, etc.)
|
||||
* [ ] Deploy in die docs
|
||||
* [ ] Docker
|
||||
* [ ] Native (systemd + nginx/apache)
|
||||
* [ ] Backups
|
||||
* [ ] Docs aufsetzen
|
||||
|
||||
### Tasks
|
||||
|
||||
* [x] Start/Enddatum für Tasks
|
||||
* [x] Timeline/Calendar view -> Dazu tasks die in einem Bestimmten Bereich due sind, macht dann das Frontend
|
||||
* [x] Tasks innerhalb eines definierbarem Bereich, sollte aber trotzdem der server machen, so à la "Gib mir alles für diesen Monat"
|
||||
* [x] Bulk-edit -> Transactions
|
||||
* [x] Assignees
|
||||
* [x] Check if something changed at all before running everything
|
||||
* [x] Don't use `list.ReadOne()`, gets too much unnessecary shit
|
||||
* [x] Wegen Performance auf eigene endpoints umziehen, wie labels
|
||||
* [x] "One endpoint to rule them all" -> Array-addable
|
||||
* [x] Labels
|
||||
* [x] Check if something changed at all before running everything
|
||||
* [x] Editable via task edit, like assignees
|
||||
* [x] "One endpoint to rule them all" -> Array-addable
|
||||
* [ ] Attachments
|
||||
* [ ] Task-Templates innerhalb namespaces und Listen (-> Mehrere, die auswählbar sind)
|
||||
* [ ] Ein Task muss von mehreren Assignees abgehakt werden bis er als done markiert wird
|
||||
* [ ] Besseres Rechtesystem, damit man so fine-graded sachen machen kann wie "Der da darf aber nur Tasks hinzufügen, aber keine abhaken"
|
||||
|
||||
### General features
|
||||
|
||||
* [x] Deps nach mod umziehen
|
||||
* [ ] Performance bei rechtchecks verbessern
|
||||
* User & Teamright sollte sich für n rechte in einer Funktion testen lassen
|
||||
* [ ] Globale Limits für anlegbare Listen + Namespaces
|
||||
* [ ] "Smart Lists", Listen nach bestimmten Kriterien gefiltert -> nur UI?
|
||||
* [ ] "Performance-Statistik" -> Wie viele Tasks man in bestimmten Zeiträumen so geschafft hat etc
|
||||
* [ ] IMAP-Integration -> Man schickt eine email an Vikunja und es macht daraus dann nen task -> Achtung missbrauchsmöglichkeiten
|
||||
* [ ] In und Out webhooks, mit Templates vom Payload
|
||||
* [ ] Reminders via mail
|
||||
* [ ] Activity Feed, so à la "der und der hat das und das gemacht etc"
|
||||
* [ ] ~~Websockets~~ SSE https://github.com/kljensen/golang-html5-sse-example
|
||||
* User authenticates (with jwt)
|
||||
* When updating/creating/etc an event struct is sent to the broker
|
||||
* The broker has a list of subscribed users
|
||||
* It then checks who is allowed to the see the event it recieved and sends it
|
||||
* [ ] Mgl., dass die Instanz geschlossen ist, also sich keiner registrieren kann, und man sich einloggen muss
|
||||
* [ ] mgl. zum Emailmaskieren haben (in den Nutzereinstellungen, wenn man seine Email nicht an alle Welt rausposaunen will)
|
||||
* [ ] Mgl. zum Accountlöschen haben (so richtig krass mit emailverifiezierung und dass alle Privaten Listen gelöscht werden und man alle geteilten entweder wem übertragen muss oder auf privat stellen)
|
||||
* [ ] /info endpoint, in dem dann zb die limits und version etc steht
|
||||
* [ ] Deprecate /namespaces/{id}/lists in favour of namespace.ReadOne() <-- should also return the lists
|
||||
|
||||
### Refactor
|
||||
|
||||
* [x] ListTaskRights, sollte überall gleich funktionieren, gibt ja mittlerweile auch eine Methode um liste von nem Task aus zu kriegen oder so
|
||||
* [x] Re-check all `{List|Namespace}{User|Team}` if really all parameters need to be exposed via json or are overwritten via param anyway.
|
||||
|
||||
### Linters
|
||||
|
||||
* [x] goconst
|
||||
* [ ] Gosimple -> waiting for mod
|
||||
* [ ] Staticcheck -> waiting for mod
|
||||
* [ ] unused -> waiting for mod
|
||||
* [ ] gosec -> waiting for mod
|
||||
100
Makefile
100
Makefile
@@ -19,15 +19,13 @@ GOFMT ?= gofmt -s
|
||||
GOFLAGS := -v -mod=vendor
|
||||
EXTRA_GOFLAGS ?=
|
||||
|
||||
LDFLAGS := -X "main.Version=$(shell git describe --tags --always --abbrev=10 | sed 's/-/+/' | sed 's/^v//' | sed 's/-g/-/')" -X "main.Tags=$(TAGS)"
|
||||
LDFLAGS := -X "code.vikunja.io/api/pkg/version.Version=$(shell git describe --tags --always --abbrev=10 | sed 's/-/+/' | sed 's/^v//' | sed 's/-g/-/')" -X "main.Tags=$(TAGS)"
|
||||
|
||||
PACKAGES ?= $(filter-out code.vikunja.io/api/integrations,$(shell go list -mod=vendor ./... | grep -v /vendor/))
|
||||
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 ?=
|
||||
|
||||
TMPDIR := $(shell mktemp -d 2>/dev/null || mktemp -d -t 'kasino-temp')
|
||||
|
||||
ifeq ($(OS), Windows_NT)
|
||||
EXECUTABLE := vikunja.exe
|
||||
else
|
||||
@@ -44,7 +42,17 @@ else
|
||||
endif
|
||||
endif
|
||||
|
||||
VERSION := $(shell echo $(VERSION) | sed 's/\//\-/g')
|
||||
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
|
||||
@@ -59,8 +67,9 @@ test:
|
||||
VIKUNJA_SERVICE_ROOTPATH=$(shell pwd) go test $(GOFLAGS) -cover -coverprofile cover.out $(PACKAGES)
|
||||
go tool cover -html=cover.out -o cover.html
|
||||
|
||||
required-gofmt-version:
|
||||
@go version | grep -q '\(1.7\|1.8\|1.9\|1.10\|1.11\)' || { echo "We require go version 1.7, 1.8, 1.9, 1.10 or 1.11 to format code" >&2 && exit 1; }
|
||||
.PHONY: integration-test
|
||||
integration-test:
|
||||
VIKUNJA_SERVICE_ROOTPATH=$(shell pwd) go test $(GOFLAGS) code.vikunja.io/api/pkg/integrations
|
||||
|
||||
.PHONY: lint
|
||||
lint:
|
||||
@@ -70,11 +79,11 @@ lint:
|
||||
for PKG in $(PACKAGES); do golint -set_exit_status $$PKG || exit 1; done;
|
||||
|
||||
.PHONY: fmt
|
||||
fmt: required-gofmt-version
|
||||
fmt:
|
||||
$(GOFMT) -w $(GOFILES)
|
||||
|
||||
.PHONY: fmt-check
|
||||
fmt-check: required-gofmt-version
|
||||
fmt-check:
|
||||
# get all go files and run go fmt on them
|
||||
@diff=$$($(GOFMT) -d $(GOFILES)); \
|
||||
if [ -n "$$diff" ]; then \
|
||||
@@ -83,16 +92,20 @@ fmt-check: required-gofmt-version
|
||||
exit 1; \
|
||||
fi;
|
||||
|
||||
.PHONY: install
|
||||
install: $(wildcard *.go)
|
||||
go install -v -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)'
|
||||
|
||||
.PHONY: build
|
||||
build: $(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
|
||||
|
||||
@@ -103,7 +116,7 @@ release-dirs:
|
||||
.PHONY: release-windows
|
||||
release-windows:
|
||||
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
go install $(GOFLAGS) github.com/karalabe/xgo; \
|
||||
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),'')
|
||||
@@ -113,7 +126,7 @@ endif
|
||||
.PHONY: release-linux
|
||||
release-linux:
|
||||
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
go install $(GOFLAGS) github.com/karalabe/xgo; \
|
||||
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),'')
|
||||
@@ -123,18 +136,21 @@ endif
|
||||
.PHONY: release-darwin
|
||||
release-darwin:
|
||||
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
go install $(GOFLAGS) github.com/karalabe/xgo; \
|
||||
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));)
|
||||
mkdir $(DIST)/release/templates -p
|
||||
cp templates/ $(DIST)/templates/ -R
|
||||
|
||||
.PHONY: release-check
|
||||
release-check:
|
||||
@@ -142,12 +158,21 @@ release-check:
|
||||
|
||||
.PHONY: release-os-package
|
||||
release-os-package:
|
||||
$(foreach file,$(filter-out %.sha256,$(wildcard $(DIST)/release/$(EXECUTABLE)-*)),mkdir $(file)-full;mv $(file) $(file)-full/; mv $(file).sha256 $(file)-full/; cp config.yml.sample $(file)-full/config.yml; cp $(DIST)/release/templates $(file)-full/ -R; cp LICENSE $(file)-full/; )
|
||||
$(foreach file,$(filter-out %.sha256,$(wildcard $(DIST)/release/$(EXECUTABLE)-*)),mkdir $(file)-full;mv $(file) $(file)-full/; mv $(file).sha256 $(file)-full/; cp config.yml.sample $(file)-full/config.yml; cp LICENSE $(file)-full/; )
|
||||
|
||||
.PHONY: release-zip
|
||||
release-zip:
|
||||
$(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); \
|
||||
@@ -162,11 +187,12 @@ do-the-swag:
|
||||
@hash swag > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
go install $(GOFLAGS) github.com/swaggo/swag/cmd/swag; \
|
||||
fi
|
||||
swag init -g pkg/routes/routes.go;
|
||||
swag init -g pkg/routes/routes.go -o ./pkg/swagger;
|
||||
# Fix the generated swagger file, currently a workaround until swaggo can properly use go mod
|
||||
sed -i '/"definitions": {/a "code.vikunja.io.web.HTTPError": {"type": "object","properties": {"code": {"type": "integer"},"message": {"type": "string"}}},' docs/docs.go;
|
||||
sed -i 's/code.vikunja.io\/web.HTTPError/code.vikunja.io.web.HTTPError/g' docs/docs.go;
|
||||
sed -i 's/` + \\"`\\" + `/` + "`" + `/g' docs/docs.go; # Replace replacements
|
||||
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:
|
||||
@@ -185,30 +211,18 @@ ineffassign-check:
|
||||
.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 14 $$S || exit 1; done;
|
||||
|
||||
.PHONY: gosimple-check
|
||||
gosimple-check:
|
||||
@hash gosimple > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
go get honnef.co/go/tools/cmd/gosimple; \
|
||||
fi
|
||||
for S in $(PACKAGES); do gosimple $$S || exit 1; done;
|
||||
for S in $(GOFILES); do gocyclo -over 23 $$S || exit 1; done;
|
||||
|
||||
.PHONY: static-check
|
||||
static-check:
|
||||
@hash gocyclo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
go get honnef.co/go/tools/cmd/staticcheck; \
|
||||
@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;
|
||||
|
||||
.PHONY: unused-check
|
||||
unused-check:
|
||||
@hash unused > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
go get honnef.co/go/tools/cmd/unused; \
|
||||
fi
|
||||
unused;
|
||||
staticcheck $(PACKAGES);
|
||||
|
||||
.PHONY: gosec-check
|
||||
gosec-check:
|
||||
@@ -220,7 +234,7 @@ gosec-check:
|
||||
.PHONY: goconst-check
|
||||
goconst-check:
|
||||
@hash goconst > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
go get github.com/jgautheron/goconst/cmd/goconst; \
|
||||
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;
|
||||
|
||||
|
||||
71
README.md
71
README.md
@@ -1,14 +1,23 @@
|
||||
<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://hub.docker.com/r/vikunja/api/)
|
||||
[](https://try.vikunja.io/api/v1/docs)
|
||||
[](https://goreportcard.com/report/git.kolaente.de/vikunja/api)
|
||||
|
||||
# Vikunja API
|
||||
|
||||
> The Todo-app to organize your life.
|
||||
|
||||
[](https://drone1.kolaente.de/vikunja/api)
|
||||
[](LICENSE)
|
||||
[](https://storage.kolaente.de/minio/vikunja/)
|
||||
[](https://hub.docker.com/r/vikunja/api/)
|
||||
[](https://try.vikunja.io/api/v1/swagger)
|
||||
[](https://goreportcard.com/report/git.kolaente.de/vikunja/api)
|
||||
[](https://cover.run/go?tag=golang-1.10&repo=code.vikunja.io%2Fapi)
|
||||
# Table of contents
|
||||
|
||||
* [Features](#features)
|
||||
* [Docs](#docs)
|
||||
* [Roadmap](#roadmap)
|
||||
* [Contributing](#contributing)
|
||||
* [License](#license)
|
||||
|
||||
## Features
|
||||
|
||||
@@ -16,8 +25,20 @@
|
||||
* Reminder for tasks
|
||||
* Namespaces: A "group" which bundels multiple lists
|
||||
* Share lists and namespaces with teams and users with granular permissions
|
||||
* Plenty of details for tasks
|
||||
|
||||
Try it under [try.vikunja.io](https://try.vikunja.io)!
|
||||
See [the features page](https://vikunja.io/en/features/) on our website for a more exaustive list or
|
||||
try it on [try.vikunja.io](https://try.vikunja.io)!
|
||||
|
||||
## Docs
|
||||
|
||||
* [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/)
|
||||
* [Testing](https://vikunja.io/docs/testing/)
|
||||
|
||||
All docs can be found on [the vikunja home page](https://vikunja.io/docs/).
|
||||
|
||||
### Roadmap
|
||||
|
||||
@@ -30,11 +51,11 @@ Try it under [try.vikunja.io](https://try.vikunja.io)!
|
||||
* [x] Get all your tasks for an interval (day/month/period)
|
||||
* [x] Labels for tasks
|
||||
* [x] Assign users to tasks
|
||||
* [ ] Attachments on tasks
|
||||
* [ ] More sharing features
|
||||
* [x] Attachments on tasks
|
||||
* [x] More sharing features
|
||||
* [x] Share with individual users
|
||||
* [ ] Share via a world-readable link with or without password, like Nextcloud
|
||||
* [ ] Read-only websocket to notify multiple clients of updates when something was changed
|
||||
* [x] Share via a world-readable link with or without password, like Nextcloud
|
||||
* [ ] 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)
|
||||
@@ -45,31 +66,15 @@ Try it under [try.vikunja.io](https://try.vikunja.io)!
|
||||
* [ ] Global limits for namespaces/lists/tasks
|
||||
* [ ] Disable registration, making an instance "invite-only"
|
||||
|
||||
See [Featurecreep.md](Featurecreep.md) for even more! (mostly ideas, for now)
|
||||
See [our roadmap](https://my.vikunja.cloud/share/QFyzYEmEYfSyQfTOmIRSwLUpkFjboaBqQCnaPmWd/auth) (hosted on Vikunja!) for even more!
|
||||
|
||||
* [ ] [Mobile apps](https://code.vikunja.io/app) (seperate repo) *In Progress*
|
||||
* [ ] [Webapp](https://code.vikunja.io/frontend) (seperate repo) *In Progress*
|
||||
|
||||
## Development
|
||||
## Contributing
|
||||
|
||||
We use go modules to vendor libraries for Vikunja, so you'll need at least go `1.11`.
|
||||
Fork -> Push -> Pull-Request. Also see the [dev docs](https://vikunja.io/docs/development/) for more infos.
|
||||
|
||||
To contribute to Vikunja, fork the project and work on the master branch.
|
||||
## License
|
||||
|
||||
Some internal packages are referenced using their respective package URL. This can become problematic. To “trick” the Go tool into thinking this is a clone from the official repository, download the source code into `$GOPATH/code.vikunja.io/api`. Fork the Vikunja repository, it should then be possible to switch the source directory on the command line.
|
||||
|
||||
```bash
|
||||
cd $GOPATH/src/code.vikunja.io/api
|
||||
```
|
||||
|
||||
To be able to create pull requests, the forked repository should be added as a remote to the Vikunja sources, otherwise changes can’t be pushed.
|
||||
|
||||
```bash
|
||||
git remote rename origin upstream
|
||||
git remote add origin git@git.kolaente.de:<USERNAME>/api.git
|
||||
git fetch --all --prune
|
||||
```
|
||||
|
||||
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` 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.
|
||||
|
||||
That’s it! You are ready to hack on Vikunja. Test changes, push them to the repository, and open a pull request.
|
||||
This project is licensed under the GPLv3 License. See the [LICENSE](LICENSE) file for the full license text.
|
||||
|
||||
12
REST-Tests/attachment_from_scratch.sh
Executable file
12
REST-Tests/attachment_from_scratch.sh
Executable file
@@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
curl -X POST http://localhost:3456/api/v1/register -H 'Content-Type: application/json' -d '{"username":"demo","password":"demo","email":"demo@vikunja.io"}'
|
||||
BEARER=`curl -X POST -H 'Content-Type: application/json' -d '{"username": "demo", "password":"demo"}' localhost:3456/api/v1/login | jq -r '.token'`
|
||||
|
||||
echo "Bearer: $BEARER"
|
||||
|
||||
curl -X POST localhost:3456/api/v1/tokenTest -H "Authorization: Bearer $BEARER"
|
||||
|
||||
curl -X PUT localhost:3456/api/v1/namespaces/1/lists -H 'Content-Type: application/json' -H "Authorization: Bearer $BEARER" -d '{"title":"lorem"}'
|
||||
curl -X PUT localhost:3456/api/v1/lists/1 -H 'Content-Type: application/json' -H "Authorization: Bearer $BEARER" -d '{"text":"lorem"}'
|
||||
curl -X PUT -H "Authorization: Bearer $BEARER" localhost:3456/api/v1/tasks/1/attachments -F 'files=@/home/konrad/Pictures/Wallpaper/greg-rakozy-_Q4mepyyjMw-unsplash.jpg'
|
||||
@@ -3,7 +3,7 @@ POST http://localhost:8080/api/v1/login
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"username": "user",
|
||||
"username": "user3",
|
||||
"password": "1234"
|
||||
}
|
||||
|
||||
@@ -21,3 +21,9 @@ Content-Type: application/json
|
||||
}
|
||||
|
||||
###
|
||||
# Token test
|
||||
POST http://localhost:8080/api/v1/tokenTest
|
||||
Authorization: Bearer {{auth_token}}
|
||||
Content-Type: application/json
|
||||
|
||||
###
|
||||
|
||||
@@ -40,7 +40,7 @@ Authorization: Bearer {{auth_token}}
|
||||
|
||||
###
|
||||
# Add a new label to a task
|
||||
PUT http://localhost:8080/api/v1/tasks/3565/labels
|
||||
PUT http://localhost:8080/api/v1/tasks/35236365/labels
|
||||
Authorization: Bearer {{auth_token}}
|
||||
Content-Type: application/json
|
||||
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
# Get all lists
|
||||
GET http://localhost:8080/api/v1/namespaces/1
|
||||
GET http://localhost:8080/api/v1/namespaces/35/lists
|
||||
Authorization: Bearer {{auth_token}}
|
||||
|
||||
###
|
||||
|
||||
# Get one list
|
||||
GET http://localhost:8080/api/v1/lists/1172
|
||||
GET http://localhost:8080/api/v1/lists/3
|
||||
Authorization: Bearer {{auth_token}}
|
||||
|
||||
###
|
||||
|
||||
# Add a new list
|
||||
PUT http://localhost:8080/api/v1/namespaces/1/lists
|
||||
PUT http://localhost:8080/api/v1/namespaces/35/lists
|
||||
Authorization: Bearer {{auth_token}}
|
||||
Content-Type: application/json
|
||||
|
||||
@@ -82,11 +82,11 @@ Authorization: Bearer {{auth_token}}
|
||||
###
|
||||
|
||||
# Give a user access to that list
|
||||
PUT http://localhost:8080/api/v1/lists/1172/users
|
||||
PUT http://localhost:8080/api/v1/lists/3/users
|
||||
Authorization: Bearer {{auth_token}}
|
||||
Content-Type: application/json
|
||||
|
||||
{"user_id":1, "right":1}
|
||||
{"userID":"user4", "right":1}
|
||||
|
||||
###
|
||||
|
||||
@@ -112,7 +112,7 @@ Authorization: Bearer {{auth_token}}
|
||||
###
|
||||
|
||||
# Get all pending tasks with priorities
|
||||
GET http://localhost:8080/api/v1/tasks/all/desc
|
||||
GET http://localhost:8080/api/v1/tasks/all?sort=priorityasc
|
||||
Authorization: Bearer {{auth_token}}
|
||||
|
||||
###
|
||||
@@ -135,10 +135,7 @@ Authorization: Bearer {{auth_token}}
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"labels": [
|
||||
{"id": 1},
|
||||
{"id": 2}
|
||||
]
|
||||
"priority": 0
|
||||
}
|
||||
|
||||
###
|
||||
@@ -172,3 +169,9 @@ Content-Type: application/json
|
||||
}
|
||||
|
||||
###
|
||||
|
||||
# Get all users who have access to a list
|
||||
GET http://localhost:8080/api/v1/lists/3/users
|
||||
Authorization: Bearer {{auth_token}}
|
||||
|
||||
###
|
||||
|
||||
@@ -5,7 +5,7 @@ Authorization: Bearer {{auth_token}}
|
||||
###
|
||||
|
||||
# Get one namespaces
|
||||
GET http://localhost:8080/api/v1/namespaces/125476
|
||||
GET http://localhost:8080/api/v1/namespaces/-1
|
||||
Authorization: Bearer {{auth_token}}
|
||||
|
||||
###
|
||||
|
||||
8
build/after-install.sh
Normal file
8
build/after-install.sh
Normal file
@@ -0,0 +1,8 @@
|
||||
#!/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)
|
||||
sed -i "s/<jwt-secret>/$NEW_SECRET/g" /etc/vikunja/config.yml
|
||||
sed -i "s/<rootpath>/\/opt\/vikunja\//g" /etc/vikunja/config.yml
|
||||
sed -i "s/Path: \"\.\/vikunja.db\"/Path: \"\\/opt\/vikunja\/vikunja.db\"/g" /etc/vikunja/config.yml
|
||||
8
build/reprepro-dist-conf
Normal file
8
build/reprepro-dist-conf
Normal file
@@ -0,0 +1,8 @@
|
||||
Origin: dl.vikunja.io
|
||||
Label: Vikunja
|
||||
Codename: strech
|
||||
Architectures: amd64
|
||||
Components: main
|
||||
Description: The debian repo for Vikunja builds.
|
||||
SignWith: yes
|
||||
Pull: strech
|
||||
@@ -2,7 +2,7 @@ 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"
|
||||
JWTSecret: "<jwt-secret>"
|
||||
# The interface on which to run the webserver
|
||||
interface: ":3456"
|
||||
# The URL of the frontend, used to send password reset emails.
|
||||
@@ -10,12 +10,16 @@ service:
|
||||
# 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 number of items which gets returned per page
|
||||
pagecount: 50
|
||||
rootpath: <rootpath>
|
||||
# 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
|
||||
# Enable sharing of lists via a link
|
||||
enablelinksharing: true
|
||||
|
||||
database:
|
||||
# Database type to use. Supported types are mysql and sqlite.
|
||||
@@ -30,10 +34,12 @@ database:
|
||||
database: "vikunja"
|
||||
# When using sqlite, this is the path where to store the data
|
||||
Path: "./vikunja.db"
|
||||
# Whether to show mysql queries or not. Useful for debugging.
|
||||
showqueries: "false"
|
||||
# Sets the max open connections to the database. Only used when using mysql.
|
||||
openconnections: 100
|
||||
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
|
||||
|
||||
cache:
|
||||
# If cache is enabled or not
|
||||
@@ -47,9 +53,9 @@ redis:
|
||||
# Whether to enable redis or not
|
||||
enabled: false
|
||||
# The host of the redis server including its port.
|
||||
redishost: 'localhost:6379'
|
||||
host: 'localhost:6379'
|
||||
# The password used to authenicate against the redis server
|
||||
redispassword: ''
|
||||
password: ''
|
||||
# 0 means default database
|
||||
db: 0
|
||||
|
||||
@@ -71,4 +77,39 @@ mailer:
|
||||
# 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
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
#!/bin/sh
|
||||
/bin/true
|
||||
@@ -1,2 +0,0 @@
|
||||
#!/bin/sh
|
||||
/bin/true
|
||||
@@ -1,2 +0,0 @@
|
||||
#!/bin/execlineb -P
|
||||
/app/vikunja/vikunja
|
||||
3
docs/Dockerfile
Normal file
3
docs/Dockerfile
Normal file
@@ -0,0 +1,3 @@
|
||||
FROM nginx
|
||||
ADD public /usr/share/nginx/html/docs
|
||||
ADD nginx.conf /etc/nginx/conf.d/default.conf
|
||||
39
docs/config.yml
Normal file
39
docs/config.yml
Normal file
@@ -0,0 +1,39 @@
|
||||
baseurl: https://vikunja.io/docs/
|
||||
title: Vikunja
|
||||
theme: vikunja
|
||||
enableRobotsTXT: true
|
||||
canonifyURLs: true
|
||||
|
||||
pygmentsUseClasses: true
|
||||
|
||||
permalinks:
|
||||
post: /:year/:month/:title/
|
||||
doc: /:slug/
|
||||
page: /:slug/
|
||||
default: /:slug/
|
||||
|
||||
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
|
||||
|
||||
menu:
|
||||
page:
|
||||
- name: Home
|
||||
url: https://vikunja.io/en/
|
||||
weight: 10
|
||||
- name: Features
|
||||
url: https://vikunja.io/en/features
|
||||
weight: 20
|
||||
- name: Download
|
||||
url: https://vikunja.io/en/download
|
||||
weight: 30
|
||||
- name: Docs
|
||||
url: https://vikunja.io/docs
|
||||
weight: 40
|
||||
- name: Code
|
||||
url: https://code.vikunja.io/
|
||||
weight: 50
|
||||
25
docs/content/doc/_index.md
Normal file
25
docs/content/doc/_index.md
Normal file
@@ -0,0 +1,25 @@
|
||||
---
|
||||
date: "2019-02-12:00:00+02:00"
|
||||
title: "Docs"
|
||||
draft: false
|
||||
url: "/docs"
|
||||
type: "doc"
|
||||
weight: 10
|
||||
---
|
||||
# Documentation
|
||||
|
||||
This is the documentation for Vikunja.
|
||||
You can find available articles in the menu on the left.
|
||||
|
||||
## About
|
||||
|
||||
To learn more about the what, why and how, take a look at [the features page](https://vikunja.io/en/features).
|
||||
|
||||
## 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).
|
||||
|
||||
## Developing
|
||||
|
||||
If you want to start contributing to Vikunja, take a look at [the development docs](development).
|
||||
34
docs/content/doc/development/cli.md
Normal file
34
docs/content/doc/development/cli.md
Normal file
@@ -0,0 +1,34 @@
|
||||
---
|
||||
date: "2019-03-31:00:00+01:00"
|
||||
title: "Adding new cli commands"
|
||||
draft: false
|
||||
type: "doc"
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "development"
|
||||
---
|
||||
|
||||
# Adding new cli commands
|
||||
|
||||
All cli-related functions are located in `pkg/cmd`.
|
||||
Each cli command usually calls a function in another package.
|
||||
For example, the `vikunja migrate` command calls `migration.Migrate()`.
|
||||
|
||||
Vikunja uses the amazing [cobra](https://github.com/spf13/cobra) library for its cli.
|
||||
Please refer to its documentation for informations about how to use flags etc.
|
||||
|
||||
To add a new cli command, add something like the following:
|
||||
|
||||
{{< highlight golang >}}
|
||||
func init() {
|
||||
rootCmd.AddCommand(myCmd)
|
||||
}
|
||||
|
||||
var myCmd = &cobra.Command{
|
||||
Use: "My-command",
|
||||
Short: "A short description about your command.",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
// Call other functions
|
||||
},
|
||||
}
|
||||
{{</ highlight >}}
|
||||
68
docs/content/doc/development/development.md
Normal file
68
docs/content/doc/development/development.md
Normal file
@@ -0,0 +1,68 @@
|
||||
---
|
||||
date: "2019-02-12:00:00+02:00"
|
||||
title: "Development"
|
||||
toc: true
|
||||
draft: false
|
||||
type: "doc"
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "development"
|
||||
name: "Development"
|
||||
---
|
||||
|
||||
# Development
|
||||
|
||||
We use go modules to vendor libraries for Vikunja, so you'll need at least go `1.11` to use these.
|
||||
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">}}).
|
||||
|
||||
## Libraries
|
||||
|
||||
We keep all libraries used for Vikunja around in the `vendor/` folder to still be able to build the project even if
|
||||
some maintainers take their libraries down like [it happened in the past](https://github.com/jteeuwen/go-bindata/issues/5).
|
||||
|
||||
## Tests
|
||||
|
||||
See [testing]({{< ref "test.md">}}).
|
||||
|
||||
#### Development using go modules
|
||||
|
||||
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.
|
||||
To “trick” the Go tool into thinking this is a clone from the official repository, download the source code
|
||||
into `$GOPATH/code.vikunja.io/api`. Fork the Vikunja repository, it should then be possible to switch the source directory on the command line.
|
||||
|
||||
{{< highlight bash >}}
|
||||
cd $GOPATH/src/code.vikunja.io/api
|
||||
{{< /highlight >}}
|
||||
|
||||
To be able to create pull requests, the forked repository should be added as a remote to the Vikunja sources, otherwise changes can’t be pushed.
|
||||
|
||||
{{< highlight bash >}}
|
||||
git remote rename origin upstream
|
||||
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`
|
||||
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.
|
||||
|
||||
That’s it! You are ready to hack on Vikunja. Test changes, push them to the repository, and open a pull request.
|
||||
|
||||
## Static assets
|
||||
|
||||
Each Vikunja release contains all static assets directly compiled into the binary.
|
||||
To prevent this during development, use the `dev` tag when developing.
|
||||
|
||||
See the [make docs](make.md#statically-compile-all-templates-into-the-binary) about how to compile with static assets for a release.
|
||||
151
docs/content/doc/development/make.md
Normal file
151
docs/content/doc/development/make.md
Normal file
@@ -0,0 +1,151 @@
|
||||
---
|
||||
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/).
|
||||
71
docs/content/doc/development/migrations.md
Normal file
71
docs/content/doc/development/migrations.md
Normal file
@@ -0,0 +1,71 @@
|
||||
---
|
||||
date: "2019-03-29:00:00+02:00"
|
||||
title: "Database migrations"
|
||||
draft: false
|
||||
type: "doc"
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "development"
|
||||
---
|
||||
|
||||
# Database Migrations
|
||||
|
||||
Vikunja runs all database migrations automatically on each start if needed.
|
||||
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.
|
||||
|
||||
## Add a new migration
|
||||
|
||||
All migrations are stored in `pkg/migrations` and files should have the same name as their id.
|
||||
|
||||
Each migration should have a function to apply and roll it back, as well as a numeric id (the datetime)
|
||||
and a more in-depth description of what the migration actually does.
|
||||
|
||||
To easily get a new id, run the following on any unix system:
|
||||
|
||||
{{< highlight bash >}}
|
||||
date +%Y%m%d%H%M%S
|
||||
{{< /highlight >}}
|
||||
|
||||
New migrations should be added via the `init()` function to the `migrations` variable.
|
||||
All migrations are sorted before being executed, since `init()` does not guarantee the order.
|
||||
|
||||
When you're adding a new struct, you also need to add it to the `models.GetTables()` function
|
||||
to ensure it will be created on new installations.
|
||||
|
||||
### Example
|
||||
|
||||
{{< highlight golang >}}
|
||||
package migration
|
||||
|
||||
import (
|
||||
"github.com/go-xorm/xorm"
|
||||
"src.techknowlogick.com/xormigrate"
|
||||
)
|
||||
|
||||
// Used for rollback
|
||||
type teamMembersMigration20190328074430 struct {
|
||||
Updated int64 `xorm:"updated"`
|
||||
}
|
||||
|
||||
func (teamMembersMigration20190328074430) TableName() string {
|
||||
return "team_members"
|
||||
}
|
||||
|
||||
func init() {
|
||||
migrations = append(migrations, &xormigrate.Migration{
|
||||
ID: "20190328074430",
|
||||
Description: "Remove updated from team_members",
|
||||
Migrate: func(tx *xorm.Engine) error {
|
||||
return dropTableColum(tx, "team_members", "updated")
|
||||
},
|
||||
Rollback: func(tx *xorm.Engine) error {
|
||||
return tx.Sync2(teamMembersMigration20190328074430{})
|
||||
},
|
||||
})
|
||||
}
|
||||
{{< /highlight >}}
|
||||
|
||||
You should always copy the changed parts of the struct you're changing when adding migraitons.
|
||||
152
docs/content/doc/development/structure.md
Normal file
152
docs/content/doc/development/structure.md
Normal file
@@ -0,0 +1,152 @@
|
||||
---
|
||||
date: "2019-02-12:00:00+02:00"
|
||||
title: "Project structure"
|
||||
draft: false
|
||||
type: "doc"
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "development"
|
||||
---
|
||||
|
||||
# Project structure
|
||||
|
||||
In general, this api repo has the following structure:
|
||||
|
||||
* `docker`
|
||||
* `docs`
|
||||
* `pkg`
|
||||
* `caldav`
|
||||
* `config`
|
||||
* `log`
|
||||
* `mail`
|
||||
* `metrics`
|
||||
* `migration`
|
||||
* `models`
|
||||
* `red`
|
||||
* `routes`
|
||||
* `api/v1`
|
||||
* `swagger`
|
||||
* `utils`
|
||||
* `REST-Tests`
|
||||
* `templates`
|
||||
* `vendor`
|
||||
|
||||
This document will explain what these mean and what you can find where.
|
||||
|
||||
## Root level
|
||||
|
||||
The root directory is where [the config file]({{< ref "../setup/config.md">}}), [Makefile]({{< ref "make.md">}}), license, drone config,
|
||||
application entry point (`main.go`) and so on are located.
|
||||
|
||||
## docker
|
||||
|
||||
This directory holds additonal files needed to build and run the docker container, mainly service configuration to properly run Vikunja inside a docker
|
||||
container.
|
||||
|
||||
## pkg
|
||||
|
||||
This is where most of the magic happens. Most packages with actual code are located in this folder.
|
||||
|
||||
### caldav
|
||||
|
||||
This folder holds a simple caldav implementation which is responsible for returning the caldav feature.
|
||||
|
||||
### cmd
|
||||
|
||||
This package contains all cli-related files and functions.
|
||||
|
||||
To learn more about how to add a new command, see [the cli docs]({{< ref "cli.md">}}).
|
||||
|
||||
To learn more about how to use this cli, see [the cli usage docs]({{< ref "../usage/cli.md">}}).
|
||||
|
||||
### config
|
||||
|
||||
This package configures the config. It sets default values and sets up viper and tells it where to look for config files,
|
||||
how to interpret which env variables for config etc.
|
||||
|
||||
If you want to add a new config parameter, you should add default value in this package.
|
||||
|
||||
### log
|
||||
|
||||
Similar to `config`, this will set up the logging, based on differen logging backends.
|
||||
This init is called in `main.go` after the config init is done.
|
||||
|
||||
### mail
|
||||
|
||||
This package handles all mail sending. To learn how to send a mail, see [sending emails]({{< ref "../practical-instructions/mail.md">}}).
|
||||
|
||||
### metrics
|
||||
|
||||
This package handles all metrics which are exposed to the prometheus endpoint.
|
||||
To learn how it works and how to add new metrics, take a look at [how metrics work]({{< ref "../practical-instructions/metrics.md">}}).
|
||||
|
||||
### migration
|
||||
|
||||
This package handles all migrations.
|
||||
All migrations are stored and executed here.
|
||||
|
||||
To learn more, take a look at the [migrations docs]({{< ref "../development/migrations.md">}}).
|
||||
|
||||
### models
|
||||
|
||||
This is where most of the magic happens.
|
||||
When adding new features or upgrading existing ones, that most likely happens here.
|
||||
|
||||
Because this package is pretty huge, there are several documents and how-to's about it:
|
||||
|
||||
* [Adding a feature]({{< ref "../practical-instructions/feature.md">}})
|
||||
* [Making calls to the database]({{< ref "../practical-instructions/database.md">}})
|
||||
|
||||
### red (redis)
|
||||
|
||||
This package initializes a connection to a redis server.
|
||||
This inizialization is automatically done at the startup of vikunja.
|
||||
|
||||
It also has a function (`GetRedis()`) which returns a redis client object you can then use in your package
|
||||
to talk to redis.
|
||||
|
||||
It uses the [go-redis](https://github.com/go-redis/redis) library, please see their configuration on how to use it.
|
||||
|
||||
### routes
|
||||
|
||||
This package defines all routes which are available for vikunja clients to use.
|
||||
To add a new route, see [adding a new route]({{< ref "../practical-instructions/feature.md">}}).
|
||||
|
||||
#### api/v1
|
||||
|
||||
This is where all http-handler functions for the api are stored.
|
||||
Every handler function which does not use the standard web handler should live here.
|
||||
|
||||
### swagger
|
||||
|
||||
This is where the [generated]({{< ref "make.md#generate-swagger-definitions-from-code-comments">}} [api docs]({{< ref "../usage/api.md">}}) live.
|
||||
You usually don't need to touch this package.
|
||||
|
||||
### utils
|
||||
|
||||
A small package, containing some helper functions:
|
||||
|
||||
* `MakeRandomString`: Generates a random string of a given length.
|
||||
* `Sha256`: Calculates a sha256 hash from a given string.
|
||||
|
||||
See their function definitions for instructions on how to use them.
|
||||
|
||||
## REST-Tests
|
||||
|
||||
Holds all kinds of test files to directly test the api from inside of [jetbrains ide's](https://www.jetbrains.com/help/idea/http-client-in-product-code-editor.html).
|
||||
|
||||
These files are currently more an experiment, maybe we will drop them in the future to use something we could integrate in the testing process with drone.
|
||||
Therefore, this has no claim to be complete yet even working, you're free to change whatever is needed to get it working for you.
|
||||
|
||||
## templates
|
||||
|
||||
Holds the email templates used to send plain text and html emails for new user registration and password changes.
|
||||
|
||||
## vendor
|
||||
|
||||
All libraries needed to build Vikunja.
|
||||
|
||||
We keep all libraries used for Vikunja around in the `vendor/` folder to still be able to build the project even if
|
||||
some maintainers take their libraries down like [it happened in the past](https://github.com/jteeuwen/go-bindata/issues/5).
|
||||
|
||||
When adding a new dependency, make sure to run `go mod vendor` to put it inside this directory.
|
||||
44
docs/content/doc/development/test.md
Normal file
44
docs/content/doc/development/test.md
Normal file
@@ -0,0 +1,44 @@
|
||||
---
|
||||
date: "2019-02-12:00:00+02:00"
|
||||
title: "Testing"
|
||||
draft: false
|
||||
type: "doc"
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "development"
|
||||
---
|
||||
|
||||
# Testing
|
||||
|
||||
You can run unit tests with [our `Makefile`]({{< ref "make.md">}}) with
|
||||
|
||||
{{< highlight bash >}}
|
||||
make test
|
||||
{{< /highlight >}}
|
||||
|
||||
### 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
|
||||
|
||||
When `UNIT_TESTS_VERBOSE=1` is set, all sql queries will be shown when tests are run.
|
||||
|
||||
### 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
|
||||
|
||||
All integration tests live in `pkg/integrations`.
|
||||
You can run them by executing `make integration-test`.
|
||||
|
||||
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`.
|
||||
38
docs/content/doc/practical-instructions/database.md
Normal file
38
docs/content/doc/practical-instructions/database.md
Normal file
@@ -0,0 +1,38 @@
|
||||
---
|
||||
date: "2019-02-12:00:00+02:00"
|
||||
title: "Database"
|
||||
draft: false
|
||||
type: "doc"
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "practical instructions"
|
||||
---
|
||||
|
||||
# Database
|
||||
|
||||
Vikunja uses [xorm](http://xorm.io/) as an abstraction layer to handle the database connection.
|
||||
Please refer to [their](http://xorm.io/docs/) documentation on how to exactly use it.
|
||||
|
||||
Inside the `models` package, a variable `x` is available which contains a pointer to an instance of `xorm.Engine`.
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
You also need to add a pointer to the `tablesWithPointer` slice to enable caching for all instances of this struct.
|
||||
|
||||
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 for test fixtures is done in via `yaml` files insinde of `pkg/models/fixtures`.
|
||||
|
||||
The name of the yaml file should equal the table name in the database.
|
||||
Adding values to it is done via array definition inside of the yaml file.
|
||||
|
||||
**Note**: Table and column names need to be in snake_case as that's what is used internally in the database
|
||||
and for mapping values from the database to xorm so your structs can use it.
|
||||
72
docs/content/doc/practical-instructions/errors.md
Normal file
72
docs/content/doc/practical-instructions/errors.md
Normal file
@@ -0,0 +1,72 @@
|
||||
---
|
||||
date: "2019-02-12:00:00+02:00"
|
||||
title: "Custom Errors"
|
||||
draft: false
|
||||
type: "doc"
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "practical instructions"
|
||||
---
|
||||
|
||||
# Custom Errors
|
||||
|
||||
All custom errors are defined in `pkg/models/errors.go`.
|
||||
You should add new ones in this file.
|
||||
|
||||
Custom errors usually have fields for the http return code, a [vikunja-specific error code]({{< ref "../usage/errors.md">}})
|
||||
and a human-readable error message about what went wrong.
|
||||
|
||||
An error consists of multiple functions and definitions:
|
||||
|
||||
{{< highlight golang >}}
|
||||
// This struct holds any information about this specific error.
|
||||
// In this case, it contains the user ID of a nonexistand user.
|
||||
// This type should always be a struct, even if it has no values in it.
|
||||
|
||||
// ErrUserDoesNotExist represents a "UserDoesNotExist" kind of error.
|
||||
type ErrUserDoesNotExist struct {
|
||||
UserID int64
|
||||
}
|
||||
|
||||
// This function is mostly used in unit tests to check if a returned error is of that type.
|
||||
// Every error type should have one of these.
|
||||
// The name should always start with IsErr... followed by the name of the error.
|
||||
|
||||
// IsErrUserDoesNotExist checks if an error is a ErrUserDoesNotExist.
|
||||
func IsErrUserDoesNotExist(err error) bool {
|
||||
_, ok := err.(ErrUserDoesNotExist)
|
||||
return ok
|
||||
}
|
||||
|
||||
// This is the definition of the actual error type.
|
||||
// Your error type is _required_ to implement this in order to be able to be returned as an "error" from functions.
|
||||
func (err ErrUserDoesNotExist) Error() string {
|
||||
return fmt.Sprintf("User does not exist [user id: %d]", err.UserID)
|
||||
}
|
||||
|
||||
// This const holds the vikunja error code used to be able to identify this error without having to
|
||||
// rely on an error string.
|
||||
// This needs to be unique, so you should check whether the error code exists or not.
|
||||
// The general convention for error codes is as follows:
|
||||
// * Every "group" errors lives in a thousend something. For example all user issues are 1000-something, all
|
||||
// list errors are 3000-something and so on.
|
||||
// * New error codes should be the current max error code + 1. Don't take free numbers to prevent old errors
|
||||
// which are depricated and removed from being "new ones". For example, if there are error codes 1001, 1002, 1004,
|
||||
// a new error should be 1005 and not 1003.
|
||||
|
||||
// ErrCodeUserDoesNotExist holds the unique world-error code of this error
|
||||
const ErrCodeUserDoesNotExist = 1005
|
||||
|
||||
// This is the implementation which returns an http error which is then passed to the client.
|
||||
// Here you define the http status code with which one the error will be returned, the vikunja error code and
|
||||
// a human-readable error message.
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrUserDoesNotExist) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{
|
||||
HTTPCode: http.StatusNotFound,
|
||||
Code: ErrCodeUserDoesNotExist,
|
||||
Message: "The user does not exist.",
|
||||
}
|
||||
}
|
||||
{{< /highlight >}}
|
||||
33
docs/content/doc/practical-instructions/feature.md
Normal file
33
docs/content/doc/practical-instructions/feature.md
Normal file
@@ -0,0 +1,33 @@
|
||||
---
|
||||
date: "2019-02-12:00:00+02:00"
|
||||
title: "Add a new api endpoint"
|
||||
draft: false
|
||||
type: "doc"
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "practical instructions"
|
||||
---
|
||||
|
||||
# Add a new api endpoint/feature
|
||||
|
||||
Most of the api endpoints/features of Vikunja are using the [common web handler](https://code.vikunja.io/web).
|
||||
This is a library created by Vikunja in an effort to facilitate the creation of REST endpoints.
|
||||
|
||||
This works by abstracting the handling of CRUD-Requests, including rights check.
|
||||
|
||||
You can learn more about the web handler on [the project's repo](https://code.vikunja.io/web).
|
||||
|
||||
### Helper for pagination
|
||||
|
||||
Pagination limits can be calculated with a helper function, `getLimitFromPageIndex(pageIndex)`
|
||||
(only available in the `models` package) from any page number.
|
||||
It returns the `limit` (max-length) and `offset` parameters needed for SQL-Queries.
|
||||
|
||||
You can feed this function directly into xorm's `Limit`-Function like so:
|
||||
|
||||
{{< highlight golang >}}
|
||||
lists := []List{}
|
||||
err := x.Limit(getLimitFromPageIndex(pageIndex, itemsPerPage)).Find(&lists)
|
||||
{{< /highlight >}}
|
||||
|
||||
// TODO: Add a full example from start to finish, like a tutorial on how to create a new endpoint?
|
||||
84
docs/content/doc/practical-instructions/mail.md
Normal file
84
docs/content/doc/practical-instructions/mail.md
Normal file
@@ -0,0 +1,84 @@
|
||||
---
|
||||
date: "2019-02-12:00:00+02:00"
|
||||
title: "Mailer"
|
||||
draft: false
|
||||
type: "doc"
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "practical instructions"
|
||||
---
|
||||
|
||||
# Mailer
|
||||
|
||||
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.
|
||||
|
||||
## Sending emails
|
||||
|
||||
**Note:** You should use mail templates whenever possible (see below).
|
||||
|
||||
To send an email, use the function `mail.SendMail(options)`. The options are defined as follows:
|
||||
|
||||
{{< highlight golang >}}
|
||||
type Opts struct {
|
||||
To string // The email address of the recipent
|
||||
Subject string // The subject of the mail
|
||||
Message string // The plaintext message in the mail
|
||||
HTMLMessage string // The html message
|
||||
ContentType ContentType // The content type of the mail. Can be either mail.ContentTypePlain, mail.ContentTypeHTML, mail.ContentTypeMultipart. You should set this according to the kind of mail you want to send.
|
||||
Boundary string
|
||||
Headers []*header // Other headers to set in the mail.
|
||||
}
|
||||
{{< /highlight >}}
|
||||
|
||||
## 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.
|
||||
|
||||
These are located in the `templates/mail` folder and follow the conventions of `template-name.{plain|hmtl}.tmpl`,
|
||||
both the plaintext and html templates are in the same folder.
|
||||
|
||||
To send a mail based on a template, use the function `mail.SendMailWithTemplate(to, subject, tpl string, data map[string]interface{})`.
|
||||
`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
|
||||
|
||||
A basic html email template would look like this:
|
||||
|
||||
{{< highlight go-html-template >}}
|
||||
{{template "mail-header.tmpl" .}}
|
||||
<p>
|
||||
Hey there!<br/>
|
||||
This is a minimal html email example.<br/>
|
||||
{{.Something}}
|
||||
</p>
|
||||
{{template "mail-footer.tmpl"}}
|
||||
{{< /highlight >}}
|
||||
|
||||
And the corresponding plaintext template:
|
||||
|
||||
{{< highlight go-text-template >}}
|
||||
Hey there!
|
||||
|
||||
This is a minimal html email example.
|
||||
|
||||
{{.Something}}
|
||||
{{< /highlight >}}
|
||||
You would then call this like so:
|
||||
|
||||
{{< highlight golang >}}
|
||||
data := make(map[string]interface{})
|
||||
data["Something"] = "I am some computed value"
|
||||
to := "test@example.com"
|
||||
subject := "A simple test mail"
|
||||
tpl := "demo" // Assuming you saved the templates as demo.plain.tmpl and demo.html.tmpl
|
||||
mail.SendMailWithTemplate(to, subject, tpl, data)
|
||||
{{< /highlight >}}
|
||||
|
||||
The function does not return an error. If an error occures when sending a mail, it is logged but not returned because sending the mail happens asinchrounly.
|
||||
|
||||
Notice the `mail-header.tmpl` and `mail-footer.tmpl` in the template. These populate some basic css, a box for your content and the vikunja logo.
|
||||
All that's left for you is to put the content in, which then will appear in a beautifully-styled box.
|
||||
|
||||
Remeber, these are email templates. This is different from normal html/css, you cannot use whatever you want (because most of the clients are wayyy to outdated).
|
||||
|
||||
46
docs/content/doc/practical-instructions/metrics.md
Normal file
46
docs/content/doc/practical-instructions/metrics.md
Normal file
@@ -0,0 +1,46 @@
|
||||
---
|
||||
date: "2019-02-12:00:00+02:00"
|
||||
title: "Metrics"
|
||||
draft: false
|
||||
type: "doc"
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "practical instructions"
|
||||
---
|
||||
|
||||
# Metrics
|
||||
|
||||
Metrics work by exposing a `/metrics` endpoint which can then be accessed by prometheus.
|
||||
|
||||
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.
|
||||
|
||||
## New metrics
|
||||
|
||||
First, define a `const` with the metric key in redis. This is done in `pkg/metrics/metrics.go`.
|
||||
|
||||
To expose a new metric, you need to register it in the `init` function inside of the `metrics` package like so:
|
||||
|
||||
{{< highlight golang >}}
|
||||
// Register total user count metric
|
||||
promauto.NewGaugeFunc(prometheus.GaugeOpts{
|
||||
Name: "vikunja_team_count", // The key of the metric. Must be unique.
|
||||
Help: "The total number of teams on this instance", // A description about the metric itself.
|
||||
}, func() float64 {
|
||||
count, _ := GetCount(TeamCountKey) // TeamCountKey is the const we defined earlier.
|
||||
return float64(count)
|
||||
})
|
||||
{{< /highlight >}}
|
||||
|
||||
Then you'll need to set the metrics initial value on every startup of vikunja.
|
||||
This is done in `pkg/routes/routes.go` to avoid cyclic imports.
|
||||
If metrics are enabled, it checks if a redis connection is available and then sets the initial values.
|
||||
A convenience function is available if the metric is based on a database struct.
|
||||
|
||||
Because metrics are stored in redis, you are responsible to increase or decrease these based on criteria you define.
|
||||
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
|
||||
|
||||
A Prometheus config with a Grafana template is available at [our git repo](https://git.kolaente.de/vikunja/monitoring).
|
||||
@@ -0,0 +1,31 @@
|
||||
---
|
||||
date: "2019-02-12:00:00+02:00"
|
||||
title: "Adding new config options"
|
||||
draft: false
|
||||
type: "doc"
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "practical instructions"
|
||||
---
|
||||
|
||||
# Adding new config options
|
||||
|
||||
Vikunja uses [viper](https://github.com/spf13/viper) to handle configuration options.
|
||||
It handles parsing all different configuration sources.
|
||||
|
||||
The configuration is done in sections. These are represented with a `.` in viper.
|
||||
Take a look at `pkg/config/config.go` to understand how these are set.
|
||||
|
||||
To add a new config option, you should add a default value to `pkg/config/config.go`.
|
||||
Default values should always enable the feature to work somehow, or turn it off completely if it always needs
|
||||
additional configuration.
|
||||
|
||||
Make sure to add the new config option to [the config document]({{< ref "../setup/config.md">}}) and the default config file
|
||||
(`config.yml.sample` at the root of the repository) to make sure it is well documented.
|
||||
|
||||
If you're using a computed value as a default, make sure to update the sample config file and debian
|
||||
post-install scripts to reflect that.
|
||||
|
||||
To get a configured option, use `viper.Get("config.option")`.
|
||||
Take a look at [viper's documentation](https://github.com/spf13/viper#getting-values-from-viper) to learn of the
|
||||
different ways available to get config options.
|
||||
47
docs/content/doc/practical-instructions/swagger-docs.md
Normal file
47
docs/content/doc/practical-instructions/swagger-docs.md
Normal file
@@ -0,0 +1,47 @@
|
||||
---
|
||||
date: "2019-02-12:00:00+02:00"
|
||||
title: "Modifying swagger api docs"
|
||||
draft: false
|
||||
type: "doc"
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "practical instructions"
|
||||
---
|
||||
|
||||
# Adding/editing swagger api docs
|
||||
|
||||
The api documentation is generated using [swaggo](https://github.com/swaggo/swag) from comments.
|
||||
|
||||
### 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.
|
||||
|
||||
As an example, this is the definition of a list with all comments:
|
||||
|
||||
{{< highlight golang >}}
|
||||
// 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"`
|
||||
// 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"`
|
||||
|
||||
// The user who created this list.
|
||||
Owner User `xorm:"-" json:"owner" valid:"-"`
|
||||
// An array of tasks which belong to the list.
|
||||
Tasks []*ListTask `xorm:"-" json:"tasks"`
|
||||
|
||||
// A unix timestamp when this list was created. You cannot change this value.
|
||||
Created int64 `xorm:"created" json:"created"`
|
||||
// A unix timestamp when this list was last updated. You cannot change this value.
|
||||
Updated int64 `xorm:"updated" json:"updated"`
|
||||
|
||||
web.CRUDable `xorm:"-" json:"-"`
|
||||
web.Rights `xorm:"-" json:"-"`
|
||||
}
|
||||
{{< /highlight >}}
|
||||
34
docs/content/doc/setup/backups.md
Normal file
34
docs/content/doc/setup/backups.md
Normal file
@@ -0,0 +1,34 @@
|
||||
---
|
||||
date: "2019-02-12:00:00+02:00"
|
||||
title: "What to backup"
|
||||
draft: false
|
||||
type: "doc"
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "setup"
|
||||
---
|
||||
|
||||
# What to backup
|
||||
|
||||
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.
|
||||
|
||||
## MySQL
|
||||
|
||||
To create a backup from mysql use the `mysqldump` command:
|
||||
|
||||
{{< highlight bash >}}
|
||||
mysqldump -u <user> -p -h <db-host> <database> > vkunja-backup.sql
|
||||
{{< /highlight >}}
|
||||
|
||||
You will be prompted for the password of the mysql user.
|
||||
|
||||
To restore it, simply pipe it back into the `mysql` command:
|
||||
|
||||
{{< highlight bash >}}
|
||||
mysql -u <user> -p -h <db-host> <database> < vkunja-backup.sql
|
||||
{{< /highlight >}}
|
||||
|
||||
## SQLite
|
||||
|
||||
To backup sqllite databases, it is enough to copy the database elsewhere.
|
||||
25
docs/content/doc/setup/build-from-source.md
Normal file
25
docs/content/doc/setup/build-from-source.md
Normal file
@@ -0,0 +1,25 @@
|
||||
---
|
||||
date: "2019-02-12:00:00+02:00"
|
||||
title: "Build from sources"
|
||||
draft: false
|
||||
type: "doc"
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "setup"
|
||||
---
|
||||
|
||||
# Build Vikunja from source
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
# 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}`.
|
||||
|
||||
More options are available, please refer to the [makefile docs]({{< ref "../development/make.md">}}) for more details.
|
||||
@@ -1,3 +1,13 @@
|
||||
---
|
||||
date: "2019-02-12:00:00+02:00"
|
||||
title: "Config options"
|
||||
draft: false
|
||||
type: "doc"
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "setup"
|
||||
---
|
||||
|
||||
# Configuration options
|
||||
|
||||
You can either use a `config.yml` file in the root directory of vikunja or set all config option with
|
||||
@@ -6,22 +16,31 @@ environment variables. If you have both, the value set in the config file is use
|
||||
Variables are nested in the `config.yml`, these nested variables become `VIKUNJA_FIRST_CHILD` when configuring via
|
||||
environment variables. So setting
|
||||
|
||||
```bash
|
||||
{{< highlight bash >}}
|
||||
export VIKUNJA_FIRST_CHILD=true
|
||||
```
|
||||
{{< /highlight >}}
|
||||
|
||||
is the same as defining it in a `config.yml` like so:
|
||||
|
||||
```yaml
|
||||
{{< highlight yaml >}}
|
||||
first:
|
||||
child: true
|
||||
```
|
||||
{{< /highlight >}}
|
||||
|
||||
## Config file locations
|
||||
|
||||
Vikunja will search on various places for a config file:
|
||||
|
||||
* Next to the location of the binary
|
||||
* In the `service.rootpath` location set in a config (remember you can set config arguments via environment variables)
|
||||
* In `/etc/vikunja`
|
||||
* In `~/.config/vikunja`
|
||||
|
||||
# Default configuration with explanations
|
||||
|
||||
This is the same as the `config.yml.sample` file you'll find in the root of vikunja.
|
||||
|
||||
```yaml
|
||||
{{< 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.
|
||||
@@ -35,11 +54,15 @@ service:
|
||||
# Vikunja will also look in this path for a config file, so you could provide only this variable to point to a folder
|
||||
# with a config file which will then be used.
|
||||
rootpath: <the path of the executable>
|
||||
# The number of items which gets returned per page
|
||||
pagecount: 50
|
||||
# The max number of items which can be returned per page
|
||||
maxitemsperpage: 50
|
||||
# If set to true, enables a /metrics endpoint for prometheus to collect metrics about the system
|
||||
# You'll need to use redis for this in order to enable common metrics over multiple nodes
|
||||
enablemetrics: false
|
||||
# Enable the caldav endpoint, see the docs for more details
|
||||
enablecaldav: true
|
||||
# Enable sharing of lists via a link
|
||||
enablelinksharing: true
|
||||
|
||||
database:
|
||||
# Database type to use. Supported types are mysql and sqlite.
|
||||
@@ -54,10 +77,12 @@ database:
|
||||
database: "vikunja"
|
||||
# When using sqlite, this is the path where to store the data
|
||||
Path: "./vikunja.db"
|
||||
# Whether to show mysql queries or not. Useful for debugging.
|
||||
showqueries: "false"
|
||||
# Sets the max open connections to the database. Only used when using mysql.
|
||||
openconnections: 100
|
||||
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
|
||||
|
||||
cache:
|
||||
# If cache is enabled or not
|
||||
@@ -71,9 +96,9 @@ redis:
|
||||
# Whether to enable redis or not
|
||||
enabled: false
|
||||
# The host of the redis server including its port.
|
||||
redishost: 'localhost:6379'
|
||||
host: 'localhost:6379'
|
||||
# The password used to authenicate against the redis server
|
||||
redispassword: ''
|
||||
password: ''
|
||||
# 0 means default database
|
||||
db: 0
|
||||
|
||||
@@ -96,4 +121,39 @@ mailer:
|
||||
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
|
||||
|
||||
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
|
||||
{{< /highlight >}}
|
||||
110
docs/content/doc/setup/full-docker-example.md
Normal file
110
docs/content/doc/setup/full-docker-example.md
Normal file
@@ -0,0 +1,110 @@
|
||||
---
|
||||
date: "2019-02-12:00:00+02:00"
|
||||
title: "Full docker example"
|
||||
draft: false
|
||||
type: "doc"
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "setup"
|
||||
---
|
||||
|
||||
# Full docker example
|
||||
|
||||
This docker compose configuration will run Vikunja with backend and frontend with a mariadb as database.
|
||||
It uses an nginx container to proxy backend and frontend into a single port.
|
||||
|
||||
You'll need to save this nginx configuration on your host under `nginx.conf`
|
||||
(or elsewhere, but then you'd need to adjust the proxy mount at the bottom of the compose file):
|
||||
|
||||
{{< highlight conf >}}
|
||||
server {
|
||||
listen 80;
|
||||
|
||||
location / {
|
||||
proxy_pass http://frontend:80;
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://api:3456;
|
||||
}
|
||||
}
|
||||
{{< /highlight >}}
|
||||
|
||||
### Without redis
|
||||
|
||||
{{< highlight yaml >}}
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
db:
|
||||
image: mariadb:10
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: supersecret
|
||||
MYSQL_DATABASE: vikunja
|
||||
volumes:
|
||||
- ./db:/var/lib/mysql
|
||||
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
|
||||
depends_on:
|
||||
- db
|
||||
frontend:
|
||||
image: vikunja/frontend
|
||||
proxy:
|
||||
image: nginx
|
||||
ports:
|
||||
- 80:80
|
||||
volumes:
|
||||
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
|
||||
depends_on:
|
||||
- api
|
||||
- frontend
|
||||
{{< /highlight >}}
|
||||
|
||||
### With redis
|
||||
|
||||
{{< highlight yaml >}}
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
db:
|
||||
image: mariadb:10
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: supersecret
|
||||
MYSQL_DATABASE: vikunja
|
||||
volumes:
|
||||
- ./db:/var/lib/mysql
|
||||
redis:
|
||||
image: redis
|
||||
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
|
||||
VIKUNJA_REDIS_ENABLED: 1
|
||||
VIKUNJA_REDIS_HOST: 'redis:6379'
|
||||
VIKUNJA_CACHE_ENABLED: 1
|
||||
VIKUNJA_CACHE_TYPE: redis
|
||||
depends_on:
|
||||
- db
|
||||
- redis
|
||||
frontend:
|
||||
image: vikunja/frontend
|
||||
proxy:
|
||||
image: nginx
|
||||
ports:
|
||||
- 80:80
|
||||
volumes:
|
||||
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
|
||||
depends_on:
|
||||
- api
|
||||
- frontend
|
||||
{{< /highlight >}}
|
||||
160
docs/content/doc/setup/install-backend.md
Normal file
160
docs/content/doc/setup/install-backend.md
Normal file
@@ -0,0 +1,160 @@
|
||||
---
|
||||
date: "2019-02-12:00:00+02:00"
|
||||
title: "Install Backend"
|
||||
draft: false
|
||||
type: "doc"
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "setup"
|
||||
---
|
||||
|
||||
# Backend
|
||||
|
||||
## Install from binary
|
||||
|
||||
Download a copy of Vikunja from the [download page](https://vikunja.io/en/download/) for your architecture.
|
||||
|
||||
{{< highlight bash >}}
|
||||
wget <download-url>
|
||||
{{< /highlight >}}
|
||||
|
||||
### Verify the GPG signature
|
||||
|
||||
Starting with version `0.7`, all releases are signed using pgp.
|
||||
Releases from `master` will always be signed.
|
||||
|
||||
To validate the downloaded zip file use the signiture file `.asc` and the key `FF054DACD908493A`:
|
||||
|
||||
{{< highlight bash >}}
|
||||
gpg --keyserver keyserver.ubuntu.com --recv FF054DACD908493A
|
||||
gpg --verify vikunja-0.7-linux-amd64-full.zip.asc vikunja-0.7-linux-amd64-full.zip
|
||||
{{< /highlight >}}
|
||||
|
||||
### Set it up
|
||||
|
||||
Once you've verified the signature, you need to unzip it and make it executable, you'll also need to
|
||||
create a symlink to it so you can execute Vikunja by typing `vikunja` on your system.
|
||||
We'll install vikunja to `/opt/vikunja`, change the path where needed if you want to install it elsewhere.
|
||||
|
||||
{{< highlight bash >}}
|
||||
mkdir -p /opt/vikunja
|
||||
unzip <vikunja-zip-file> -d /opt/vikunja
|
||||
chmod +x /opt/vikunja
|
||||
ln -s /opt/vikunja/vikunja /usr/bin/vikunja
|
||||
{{< /highlight >}}
|
||||
|
||||
### Systemd service
|
||||
|
||||
Take the following `service` file and adapt it to your needs:
|
||||
|
||||
{{< highlight service >}}
|
||||
[Unit]
|
||||
Description=Vikunja
|
||||
After=syslog.target
|
||||
After=network.target
|
||||
# Depending on how you configured Vikunja, you may want to uncomment these:
|
||||
#Requires=mysql.service
|
||||
#Requires=mariadb.service
|
||||
#Requires=redis.service
|
||||
|
||||
[Service]
|
||||
RestartSec=2s
|
||||
Type=simple
|
||||
WorkingDirectory=/opt/vikunja
|
||||
ExecStart=/usr/bin/vikunja
|
||||
Restart=always
|
||||
# If you want to bind Vikunja to a port below 1024 uncomment
|
||||
# the two values below
|
||||
###
|
||||
#CapabilityBoundingSet=CAP_NET_BIND_SERVICE
|
||||
#AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
{{< /highlight >}}
|
||||
|
||||
If you've installed Vikunja to a directory other than `/opt/vikunja`, you need to adapt `WorkingDirectory` accordingly.
|
||||
|
||||
Save the file to `/etc/systemd/system/vikunja.service`
|
||||
|
||||
After you made all nessecary modifications, it's time to start the service:
|
||||
|
||||
{{< highlight bash >}}
|
||||
sudo systemctl enable vikunja
|
||||
sudo systemctl start vikunja
|
||||
{{< /highlight >}}
|
||||
|
||||
### Build from source
|
||||
|
||||
To build vikunja from source, see [building from source]({{< ref "build-from-source.md">}}).
|
||||
|
||||
### Updating
|
||||
|
||||
Simply replace the binary and templates with the new version, then restart Vikunja.
|
||||
It will automatically run all nessecary database migrations.
|
||||
**Make sure to take a look at the changelog for the new version to not miss any manual steps the update may involve!**
|
||||
|
||||
## Docker
|
||||
|
||||
(Note: this assumes some familarity with docker)
|
||||
|
||||
Usage with docker is pretty straightforward:
|
||||
|
||||
{{< highlight bash >}}
|
||||
docker run -p 3456:3456 vikunja/api
|
||||
{{< /highlight >}}
|
||||
|
||||
to run with a standard configuration.
|
||||
This will expose
|
||||
|
||||
You can mount a local configuration like so:
|
||||
|
||||
{{< highlight bash >}}
|
||||
docker run -p 3456:3456 -v /path/to/config/on/host.yml:/app/vikunja/config.yml:ro vikunja/api
|
||||
{{< /highlight >}}
|
||||
|
||||
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.
|
||||
|
||||
### Docker compose
|
||||
|
||||
To run the backend with a mariadb database you can use this example [docker-compose](https://docs.docker.com/compose/) file:
|
||||
|
||||
{{< highlight yaml >}}
|
||||
version: '2'
|
||||
services:
|
||||
api:
|
||||
image: vikunja/api:latest
|
||||
environment:
|
||||
VIKUNJA_DATABASE_HOST: db
|
||||
VIKUNJA_DATABASE_PASSWORD: supersecret
|
||||
VIKUNJA_DATABASE_TYPE: mysql
|
||||
VIKUNJA_DATABASE_USER: root
|
||||
VIKUNJA_SERVICE_JWTSECRET: <generated secret>
|
||||
db:
|
||||
image: mariadb:10
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: supersecret
|
||||
MYSQL_DATABASE: vikunja
|
||||
volumes:
|
||||
- ./db:/var/lib/mysql
|
||||
{{< /highlight >}}
|
||||
|
||||
See [full docker example]({{< ref "full-docker-example.md">}}) for more varations of this config.
|
||||
|
||||
## Debian packages
|
||||
|
||||
Since version 0.7 Vikunja is also released as debian packages.
|
||||
|
||||
To install these, grab a copy from [the download page](https://vikunja.io/en/download/) and run
|
||||
|
||||
{{< highlight bash >}}
|
||||
dpkg -i vikunja.deb
|
||||
{{< /highlight >}}
|
||||
|
||||
This will install the backend to `/opt/vikunja`.
|
||||
To configure it, use the config file in `/etc/vikunja/config.yml`.
|
||||
|
||||
## Configuration
|
||||
|
||||
See [available configuration options]({{< ref "config.md">}}).
|
||||
116
docs/content/doc/setup/install-frontend.md
Normal file
116
docs/content/doc/setup/install-frontend.md
Normal file
@@ -0,0 +1,116 @@
|
||||
---
|
||||
date: "2019-02-12:00:00+02:00"
|
||||
title: "Install Frontend"
|
||||
draft: false
|
||||
type: "doc"
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "setup"
|
||||
---
|
||||
|
||||
# Frontend
|
||||
|
||||
Installing the frontend is just a matter of hosting a bunch of static files somewhere.
|
||||
|
||||
With nginx or apache, you have to [download](https://vikunja.io/en/download/) the frontend files first.
|
||||
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.
|
||||
|
||||
## Docker
|
||||
|
||||
The docker image is based on nginx and just contains all nessecary files for the frontend.
|
||||
|
||||
To run it, all you need is
|
||||
|
||||
{{< highlight bash >}}
|
||||
docker run -p 80:80 vikunja/frontend
|
||||
{{< /highlight >}}
|
||||
|
||||
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.
|
||||
|
||||
## NGINX
|
||||
|
||||
Below are two example configurations which you can put in your `nginx.conf`:
|
||||
|
||||
You may need to adjust `server_name` and `root` accordingly.
|
||||
|
||||
After configuring them, you need to reload nginx (`service nginx reload`).
|
||||
|
||||
### with gzip enabled (recommended)
|
||||
|
||||
{{< highlight conf >}}
|
||||
gzip on;
|
||||
gzip_disable "msie6";
|
||||
|
||||
gzip_vary on;
|
||||
gzip_proxied any;
|
||||
gzip_comp_level 6;
|
||||
gzip_buffers 16 8k;
|
||||
gzip_http_version 1.1;
|
||||
gzip_min_length 256;
|
||||
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/vnd.ms-fontobject application/x-font-ttf font/opentype image/svg+xml;
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
|
||||
location / {
|
||||
root /path/to/vikunja/static/frontend/files;
|
||||
try_files $uri $uri/ /;
|
||||
index index.html index.htm;
|
||||
}
|
||||
}
|
||||
{{< /highlight >}}
|
||||
|
||||
### without gzip
|
||||
|
||||
{{< highlight conf >}}
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
|
||||
location / {
|
||||
root /path/to/vikunja/static/frontend/files;
|
||||
try_files $uri $uri/ /;
|
||||
index index.html index.htm;
|
||||
}
|
||||
}
|
||||
{{< /highlight >}}
|
||||
|
||||
## Apache
|
||||
|
||||
Apache needs to have `mod_rewrite` enabled for this to work properly:
|
||||
|
||||
{{< highlight bash >}}
|
||||
a2enmod rewrite
|
||||
service apache2 restart
|
||||
{{< /highlight >}}
|
||||
|
||||
Put the following config in `cat /etc/apache2/sites-available/vikunja.conf`:
|
||||
|
||||
{{< highlight aconf >}}
|
||||
<VirtualHost *:80>
|
||||
ServerName localhost
|
||||
DocumentRoot /path/to/vikunja/static/frontend/files
|
||||
RewriteEngine On
|
||||
RewriteRule ^\/?(config\.json|favicon\.ico|css|fonts|images|img|js) - [L]
|
||||
RewriteRule ^(.*)$ /index.html [QSA,L]
|
||||
</VirtualHost>
|
||||
{{< /highlight >}}
|
||||
|
||||
You probably want to adjust `ServerName` and `DocumentRoot`.
|
||||
|
||||
Once you've customized your config, you need to enable it:
|
||||
|
||||
{{< highlight bash >}}
|
||||
a2ensite vikunja
|
||||
service apache2 reload
|
||||
{{< /highlight >}}
|
||||
|
||||
## Updating
|
||||
|
||||
To update, it should be enough to download the new files and overwrite the old ones.
|
||||
The paths contain hashes, so all caches are invalidated automatically.
|
||||
40
docs/content/doc/setup/install.md
Normal file
40
docs/content/doc/setup/install.md
Normal file
@@ -0,0 +1,40 @@
|
||||
---
|
||||
date: "2019-02-12:00:00+02:00"
|
||||
title: "Installing"
|
||||
draft: false
|
||||
type: "doc"
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "setup"
|
||||
weight: 10
|
||||
---
|
||||
|
||||
# Installing
|
||||
|
||||
Vikunja consists of two parts: [Backend](https://code.vikunja.io/api) and [frontend](https://code.vikunja.io/frontend).
|
||||
While the backend is required, the frontend is not.
|
||||
You don't neccesarily need to have a web-frontend, using Vikunja via the [mobile app](https://code.vikunja.io/app) is totally fine.
|
||||
|
||||
However, using the web frontend is highly reccommended.
|
||||
|
||||
Vikunja can be installed in various forms.
|
||||
This document provides an overview and instructions for the different methods.
|
||||
|
||||
* [Backend]({{< ref "install-backend.md">}})
|
||||
* [Installing from binary]({{< ref "install-backend.md#install-from-binary">}})
|
||||
* [Verify the GPG signature]({{< ref "install-backend.md#verify-the-gpg-signature">}})
|
||||
* [Set it up]({{< ref "install-backend.md#set-it-up">}})
|
||||
* [Systemd service]({{< ref "install-backend.md#systemd-service">}})
|
||||
* [Updating]({{< ref "install-backend.md#updating">}})
|
||||
* [Build from source]({{< ref "install-backend.md#build-from-source">}})
|
||||
* [Docker]({{< ref "install-backend.md#docker">}})
|
||||
* [Debian packages]({{< ref "install-backend.md#debian-packages">}})
|
||||
* [Configuration]({{< ref "config.md">}})
|
||||
* [Frontend]({{< ref "install-frontend.md">}})
|
||||
* [Docker]({{< ref "install-frontend.md#docker">}})
|
||||
* [NGINX]({{< ref "install-frontend.md#nginx">}})
|
||||
* [Apache]({{< ref "install-frontend.md#apache">}})
|
||||
* [Updating]({{< ref "install-frontend.md#updating">}})
|
||||
* [Reverse proxies]({{< ref "reverse-proxies.md">}})
|
||||
* [Full docker example]({{< ref "full-docker-example.md">}})
|
||||
* [Backups]({{< ref "backups.md">}})
|
||||
95
docs/content/doc/setup/reverse-proxies.md
Normal file
95
docs/content/doc/setup/reverse-proxies.md
Normal file
@@ -0,0 +1,95 @@
|
||||
---
|
||||
date: "2019-02-12:00:00+02:00"
|
||||
title: "Reverse Proxy"
|
||||
draft: false
|
||||
type: "doc"
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "setup"
|
||||
---
|
||||
|
||||
# Setup behind a reverse proxy which also serves the frontend
|
||||
|
||||
These examples assume you have an instance of the backend running on your server listening on port `3456`.
|
||||
If you've changed this setting, you need to update the server configurations accordingly.
|
||||
|
||||
## NGINX
|
||||
|
||||
Below are two example configurations which you can put in your `nginx.conf`:
|
||||
|
||||
You may need to adjust `server_name` and `root` accordingly.
|
||||
|
||||
### with gzip enabled (recommended)
|
||||
|
||||
{{< highlight conf >}}
|
||||
gzip on;
|
||||
gzip_disable "msie6";
|
||||
|
||||
gzip_vary on;
|
||||
gzip_proxied any;
|
||||
gzip_comp_level 6;
|
||||
gzip_buffers 16 8k;
|
||||
gzip_http_version 1.1;
|
||||
gzip_min_length 256;
|
||||
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/vnd.ms-fontobject application/x-font-ttf font/opentype image/svg+xml;
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
|
||||
location / {
|
||||
root /path/to/vikunja/static/frontend/files;
|
||||
try_files $uri $uri/ /;
|
||||
index index.html index.htm;
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://localhost:3456;
|
||||
}
|
||||
}
|
||||
{{< /highlight >}}
|
||||
|
||||
### without gzip
|
||||
|
||||
{{< highlight conf >}}
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
|
||||
location / {
|
||||
root /path/to/vikunja/static/frontend/files;
|
||||
try_files $uri $uri/ /;
|
||||
index index.html index.htm;
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://localhost:3456;
|
||||
}
|
||||
}
|
||||
{{< /highlight >}}
|
||||
|
||||
## Apache
|
||||
|
||||
Put the following config in `cat /etc/apache2/sites-available/vikunja.conf`:
|
||||
|
||||
{{< highlight aconf >}}
|
||||
<VirtualHost *:80>
|
||||
ServerName localhost
|
||||
|
||||
<Proxy *>
|
||||
Order Deny,Allow
|
||||
Allow from all
|
||||
</Proxy>
|
||||
ProxyPass /api http://localhost:3456/api
|
||||
ProxyPassReverse /api http://localhost:3456/api
|
||||
|
||||
DocumentRoot /var/www/html
|
||||
RewriteEngine On
|
||||
RewriteRule ^\/?(config\.json|favicon\.ico|css|fonts|images|img|js|api) - [L]
|
||||
RewriteRule ^(.*)$ /index.html [QSA,L]
|
||||
</VirtualHost>
|
||||
{{< /highlight >}}
|
||||
|
||||
**Note:** The apache modules `proxy` and `proxy_http` must be enabled for this.
|
||||
|
||||
For more details see the [frontend apache configuration]({{< ref "install-frontend.md#apache">}}).
|
||||
@@ -1,3 +1,13 @@
|
||||
---
|
||||
date: "2019-02-12:00:00+02:00"
|
||||
title: "API Documentation"
|
||||
draft: false
|
||||
type: "doc"
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "usage"
|
||||
---
|
||||
|
||||
# API Documentation
|
||||
|
||||
You can find the api docs under `http://vikunja.tld/api/v1/docs` of your instance.
|
||||
140
docs/content/doc/usage/caldav.md
Normal file
140
docs/content/doc/usage/caldav.md
Normal file
@@ -0,0 +1,140 @@
|
||||
---
|
||||
date: "2019-05-12:00:00+01:00"
|
||||
title: "Caldav"
|
||||
draft: false
|
||||
type: "doc"
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "usage"
|
||||
---
|
||||
|
||||
# Caldav
|
||||
|
||||
> **Warning:** The caldav integration is in an early alpha stage and has bugs.
|
||||
> It works well with some clients while having issues with others.
|
||||
> If you encounter issues, please [report them](https://code.vikunja.io/api/issues/new?body=[caldav])
|
||||
|
||||
Vikunja supports managing tasks via the [caldav VTODO](https://tools.ietf.org/html/rfc5545#section-3.6.2) extension.
|
||||
|
||||
## URLs
|
||||
|
||||
All urls are located under the `/dav` subspace.
|
||||
|
||||
Urls are:
|
||||
|
||||
* `/principals/<username>/`: Returns urls for list discovery. *Use this url to initially make connections to new clients.*
|
||||
* `/lists/`: Used to manage lists
|
||||
* `/lists/<List ID>/`: Used to manage a single list
|
||||
* `/lists/<List ID>/<Task UID>`: Used to manage a task on a list
|
||||
|
||||
## Supported properties
|
||||
|
||||
Vikunja currently supports the following properties:
|
||||
|
||||
* `UID`
|
||||
* `SUMMARY`
|
||||
* `DESCRIPTION`
|
||||
* `PRIORITY`
|
||||
* `COMPLETED`
|
||||
* `DUE`
|
||||
* `DTSTART`
|
||||
* `DURATION`
|
||||
* `ORGANIZER`
|
||||
* `RELATED-TO`
|
||||
* `CREATED`
|
||||
* `DTSTAMP`
|
||||
* `LAST-MODIFIED`
|
||||
|
||||
Vikunja **currently does not** support these properties:
|
||||
|
||||
* `ATTACH`
|
||||
* `CATEGORIES`
|
||||
* `CLASS`
|
||||
* `COMMENT`
|
||||
* `GEO`
|
||||
* `LOCATION`
|
||||
* `PERCENT-COMPLETE`
|
||||
* `RESOURCES`
|
||||
* `STATUS`
|
||||
* `CONTACT`
|
||||
* `RECURRENCE-ID`
|
||||
* `URL`
|
||||
* Recurrence
|
||||
* `SEQUENCE`
|
||||
|
||||
## Tested Clients
|
||||
|
||||
#### Working
|
||||
|
||||
* [Evolution](https://wiki.gnome.org/Apps/Evolution/)
|
||||
|
||||
#### Not working
|
||||
|
||||
* [Tasks (Android)](https://tasks.org/)
|
||||
|
||||
## Dev logs
|
||||
|
||||
The whole thing is not optimized at all and probably pretty inefficent.
|
||||
|
||||
Request body and headers are logged if the debug output is enabled.
|
||||
|
||||
```
|
||||
Creating a new task:
|
||||
PUT /dav/lists/1/cd4dd0e1b3c19cc9d787829b6e08be536e3df3a4.ics
|
||||
|
||||
Body:
|
||||
|
||||
BEGIN:VCALENDAR
|
||||
CALSCALE:GREGORIAN
|
||||
PRODID:-//Ximian//NONSGML Evolution Calendar//EN
|
||||
VERSION:2.0
|
||||
BEGIN:VTODO
|
||||
UID:cd4dd0e1b3c19cc9d787829b6e08be536e3df3a4
|
||||
DTSTAMP:20190508T134538Z
|
||||
SUMMARY:test2000
|
||||
PRIORITY:0
|
||||
CLASS:PUBLIC
|
||||
CREATED:20190508T134710Z
|
||||
LAST-MODIFIED:20190508T134710Z
|
||||
END:VTODO
|
||||
END:VCALENDAR
|
||||
|
||||
|
||||
Marking a task as done:
|
||||
|
||||
BEGIN:VCALENDAR
|
||||
CALSCALE:GREGORIAN
|
||||
PRODID:-//Ximian//NONSGML Evolution Calendar//EN
|
||||
VERSION:2.0
|
||||
BEGIN:VTODO
|
||||
UID:3ada92f28b4ceda38562ebf047c6ff05400d4c572352a
|
||||
DTSTAMP:20190511T183631
|
||||
DTSTART:19700101T000000
|
||||
DTEND:19700101T000000
|
||||
SUMMARY:sdgs
|
||||
ORGANIZER;CN=:user
|
||||
CREATED:20190511T183631
|
||||
PRIORITY:0
|
||||
LAST-MODIFIED:20190512T193428Z
|
||||
COMPLETED:20190512T193428Z
|
||||
PERCENT-COMPLETE:100
|
||||
STATUS:COMPLETED
|
||||
END:VTODO
|
||||
END:VCALENDAR
|
||||
|
||||
Requests from the app:::
|
||||
|
||||
[CALDAV] Request Body: <?xml version="1.0" encoding="UTF-8" ?><propfind xmlns="DAV:" xmlns:CAL="urn:ietf:params:xml:ns:caldav" xmlns:CARD="urn:ietf:params:xml:ns:carddav"><prop><current-user-principal /></prop></propfind>
|
||||
[CALDAV] GetResources: rpath: /dav/
|
||||
2019-05-18T23:25:49.971140654+02:00: WEB ▶ 192.168.1.134 PROPFIND 207 /dav/ 1.021705664s - okhttp/3.12.2
|
||||
|
||||
[CALDAV] Request Body: <?xml version="1.0" encoding="UTF-8" ?><propfind xmlns="DAV:" xmlns:CAL="urn:ietf:params:xml:ns:caldav" xmlns:CARD="urn:ietf:params:xml:ns:carddav"><prop><CAL:calendar-home-set /></prop></propfind>
|
||||
[CALDAV] GetResources: rpath: /dav/
|
||||
2019-05-18T23:25:52.166996113+02:00: WEB ▶ 192.168.1.134 PROPFIND 207 /dav/ 1.042834467s - okhttp/3.12.2
|
||||
|
||||
And then it just stops.
|
||||
... and complains about not being able to find the home set
|
||||
... without even requesting it...
|
||||
|
||||
|
||||
```
|
||||
84
docs/content/doc/usage/cli.md
Normal file
84
docs/content/doc/usage/cli.md
Normal file
@@ -0,0 +1,84 @@
|
||||
---
|
||||
date: "2019-03-31:00:00+01:00"
|
||||
title: "CLI"
|
||||
draft: false
|
||||
type: "doc"
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "usage"
|
||||
---
|
||||
|
||||
# Command line interface
|
||||
|
||||
You can interact with Vikunja using its `cli` interface.
|
||||
The following commands are available:
|
||||
|
||||
* [help](#help)
|
||||
* [migrate](#migrate)
|
||||
* [version](#version)
|
||||
* [web](#web)
|
||||
|
||||
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">}}).
|
||||
|
||||
### `help`
|
||||
|
||||
Shows more detailed help about any command.
|
||||
|
||||
Usage:
|
||||
|
||||
{{< highlight bash >}}
|
||||
$ vikunja help [command]
|
||||
{{< /highlight >}}
|
||||
|
||||
### `migrate`
|
||||
|
||||
Run all database migrations which didn't already run.
|
||||
|
||||
Usage:
|
||||
{{< highlight bash >}}
|
||||
$ vikunja migrate [flags]
|
||||
$ vikunja migrate [command]
|
||||
{{< /highlight >}}
|
||||
|
||||
#### `migrate list`
|
||||
|
||||
Shows a list with all database migrations.
|
||||
|
||||
Usage:
|
||||
{{< highlight bash >}}
|
||||
$ vikunja migrate list
|
||||
{{< /highlight >}}
|
||||
|
||||
#### `migrate rollback`
|
||||
|
||||
Roll migrations back until a certain point.
|
||||
|
||||
Usage:
|
||||
{{< highlight bash >}}
|
||||
$ vikunja migrate rollback [flags]
|
||||
{{< /highlight >}}
|
||||
|
||||
Flags:
|
||||
* `-n`, `--name` string: The id of the migration you want to roll back until.
|
||||
|
||||
|
||||
### `version`
|
||||
|
||||
Prints the version of Vikunja.
|
||||
This is either the semantic version (something like `0.7`) or version + git commit hash.
|
||||
|
||||
Usage:
|
||||
{{< highlight bash >}}
|
||||
$ vikunja version
|
||||
{{< /highlight >}}
|
||||
|
||||
### `web`
|
||||
|
||||
Starts Vikunja's REST api server.
|
||||
|
||||
Usage:
|
||||
{{< highlight bash >}}
|
||||
$ vikunja web
|
||||
{{< /highlight >}}
|
||||
@@ -1,9 +1,20 @@
|
||||
---
|
||||
date: "2019-02-12:00:00+02:00"
|
||||
title: "Errors"
|
||||
draft: false
|
||||
type: "doc"
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "usage"
|
||||
---
|
||||
|
||||
# Errors
|
||||
|
||||
This document describes the different errors Vikunja can return.
|
||||
|
||||
| ErrorCode | HTTP Status Code | Description |
|
||||
|-----------|------------------|-------------|
|
||||
| 0001 | 403 | Generic forbidden error. |
|
||||
| 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. |
|
||||
@@ -14,16 +25,26 @@ This document describes the different errors Vikunja can return.
|
||||
| 1010 | 412 | Invalid email confirm token. |
|
||||
| 1011 | 412 | Wrong username or password. |
|
||||
| 1012 | 412 | Email address of the user not confirmed. |
|
||||
| 1013 | 412 | New password is empty. |
|
||||
| 1014 | 412 | Old password is empty. |
|
||||
| 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. |
|
||||
| 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. |
|
||||
| 4001 | 400 | The list task text cannot be empty. |
|
||||
| 4002 | 404 | The list task does not exist. |
|
||||
| 4003 | 403 | All bulk editing tasks must belong to the same list. |
|
||||
| 4004 | 403 | Need at least one task when bulk editing tasks. |
|
||||
| 4005 | 403 | The user does not have the right to see the task. |
|
||||
| 4006 | 403 | The user tried to set a parent task as the task itself. |
|
||||
| 4007 | 400 | The user tried to create a task relation with an invalid kind of relation. |
|
||||
| 4008 | 409 | The user tried to create a task relation which already exists. |
|
||||
| 4009 | 404 | The task relation does not exist. |
|
||||
| 4010 | 400 | Cannot relate a task with itself. |
|
||||
| 4011 | 404 | The task attachment does not exist. |
|
||||
| 4012 | 400 | The task attachment is too large. |
|
||||
| 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. |
|
||||
@@ -32,14 +53,13 @@ This document describes the different errors Vikunja can return.
|
||||
| 5011 | 409 | This user has already access to that namespace. |
|
||||
| 6001 | 400 | The team name cannot be emtpy. |
|
||||
| 6002 | 404 | The team does not exist. |
|
||||
| 6003 | 400 | The provided team right is invalid. |
|
||||
| 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. |
|
||||
| 7001 | 400 | The user right is invalid. |
|
||||
| 7002 | 409 | The user already has access to that list. |
|
||||
| 7003 | 403 | The user does not have access to that list. |
|
||||
| 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. |
|
||||
| 8003 | 403 | The user does not have access to this label. |
|
||||
| 9001 | 403 | The right is invalid. |
|
||||
25
docs/content/doc/usage/relation_kinds.md
Normal file
25
docs/content/doc/usage/relation_kinds.md
Normal file
@@ -0,0 +1,25 @@
|
||||
---
|
||||
date: "2019-09-25:00:00+02:00"
|
||||
title: "Task Relation kinds"
|
||||
draft: false
|
||||
type: "doc"
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "usage"
|
||||
---
|
||||
|
||||
# Available task relation kinds
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| subtask | Task is a subtask of the other task. This is the opposite of `parenttask`. |
|
||||
| parenttask | Task is a parent task of the other task. This is the opposite of `subtask`. |
|
||||
| related | Both tasks are related to each other. How is not more specified. |
|
||||
| duplicateof | Task is a duplicate of the other task. This is the opposite of `duplicates`. |
|
||||
| duplicates | Task duplicates the other task. This is the opposite of `duplicateof`. |
|
||||
| blocking | Task is blocking the other task. This is the opposite of `blocked`. |
|
||||
| blocked | Task is blocked by the other task. This is the opposite of `blocking`. |
|
||||
| precedes | Task precedes the other task. This is the opposite of `follows`. |
|
||||
| follows | Task follows the other task. This is the opposite of `precedes`. |
|
||||
| copiedfrom | Task is copied from the other task. This is the opposite of `copiedto`. |
|
||||
| copiedto | Task is copied to the other task. This is the opposite of `copiedfrom`. |
|
||||
@@ -1,3 +1,13 @@
|
||||
---
|
||||
date: "2019-02-12:00:00+02:00"
|
||||
title: "Rights"
|
||||
draft: false
|
||||
type: "doc"
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "usage"
|
||||
---
|
||||
|
||||
# List and namespace rights for teams and users
|
||||
|
||||
Whenever you share a list or namespace with a user or team, you can specify a `rights` parameter.
|
||||
21
docs/nginx.conf
Normal file
21
docs/nginx.conf
Normal file
@@ -0,0 +1,21 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
|
||||
charset utf-8;
|
||||
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html index.htm;
|
||||
}
|
||||
|
||||
error_page 404 /docs/404.html;
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root /usr/share/nginx/html;
|
||||
}
|
||||
|
||||
location /docs/contact {
|
||||
return 301 $scheme://vikunja.io/en/contact;
|
||||
}
|
||||
}
|
||||
17
docs/test.md
17
docs/test.md
@@ -1,17 +0,0 @@
|
||||
# Testing
|
||||
|
||||
You can run unit tests with our `Makefile` with
|
||||
|
||||
```bash
|
||||
make test
|
||||
```
|
||||
|
||||
### 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
|
||||
|
||||
When `UNIT_TESTS_VERBOSE=1` is set, all sql queries will be shown when tests are run.
|
||||
1
docs/themes/vikunja
vendored
Submodule
1
docs/themes/vikunja
vendored
Submodule
Submodule docs/themes/vikunja added at 99f9cbf56b
89
go.mod
89
go.mod
@@ -18,53 +18,80 @@ module code.vikunja.io/api
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.34.0 // indirect
|
||||
code.vikunja.io/web v0.0.0-20181130231148-b061c20192fb
|
||||
github.com/BurntSushi/toml v0.3.1 // indirect
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc
|
||||
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf
|
||||
code.vikunja.io/web v0.0.0-20191023202526-f337750c3573
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a
|
||||
github.com/beevik/etree v1.1.0 // indirect
|
||||
github.com/c2h5oh/datasize v0.0.0-20171227191756-4eba002a5eae
|
||||
github.com/client9/misspell v0.3.4
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
|
||||
github.com/creack/pty v1.1.9 // indirect
|
||||
github.com/d4l3k/messagediff v1.2.1 // indirect
|
||||
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/ghodss/yaml v1.0.0
|
||||
github.com/go-openapi/spec v0.17.2 // indirect
|
||||
github.com/go-openapi/swag v0.17.2 // indirect
|
||||
github.com/go-redis/redis v6.14.2+incompatible
|
||||
github.com/go-openapi/jsonreference v0.19.3 // indirect
|
||||
github.com/go-openapi/spec v0.19.3 // indirect
|
||||
github.com/go-redis/redis v6.15.2+incompatible
|
||||
github.com/go-sql-driver/mysql v1.4.1
|
||||
github.com/go-xorm/builder v0.0.0-20170519032130-c8871c857d25
|
||||
github.com/go-xorm/core v0.5.8
|
||||
github.com/go-xorm/builder v0.3.4
|
||||
github.com/go-xorm/core v0.6.2
|
||||
github.com/go-xorm/tests v0.5.6 // indirect
|
||||
github.com/go-xorm/xorm v0.0.0-20170930012613-29d4a0330a00
|
||||
github.com/go-xorm/xorm v0.7.1
|
||||
github.com/go-xorm/xorm-redis-cache v0.0.0-20180727005610-859b313566b2
|
||||
github.com/golang/protobuf v1.3.2 // indirect
|
||||
github.com/gordonklaus/ineffassign v0.0.0-20180909121442-1003c8bd00dc
|
||||
github.com/imdario/mergo v0.3.6
|
||||
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/karalabe/xgo v0.0.0-20181007145344-72da7d1d3970
|
||||
github.com/kisielk/gotool v1.0.0 // indirect
|
||||
github.com/kr/pretty v0.1.0 // indirect
|
||||
github.com/labstack/echo v3.3.5+incompatible
|
||||
github.com/labstack/gommon v0.2.8
|
||||
github.com/json-iterator/go v1.1.7 // indirect
|
||||
github.com/kr/pty v1.1.8 // indirect
|
||||
github.com/labstack/echo/v4 v4.1.11
|
||||
github.com/labstack/gommon v0.3.0
|
||||
github.com/laurent22/ical-go v0.1.1-0.20181107184520-7e5d6ade8eef
|
||||
github.com/mailru/easyjson v0.7.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.4 // indirect
|
||||
github.com/mattn/go-isatty v0.0.10 // indirect
|
||||
github.com/mattn/go-oci8 v0.0.0-20181130072307-052f5d97b9b6 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.4 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.10.0
|
||||
github.com/mitchellh/mapstructure v1.1.2 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.1
|
||||
github.com/onsi/ginkgo v1.7.0 // indirect
|
||||
github.com/onsi/gomega v1.4.3 // indirect
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||
github.com/pkg/errors v0.8.0 // indirect
|
||||
github.com/pelletier/go-toml v1.4.0 // indirect
|
||||
github.com/pkg/errors v0.8.1 // indirect
|
||||
github.com/prometheus/client_golang v0.9.2
|
||||
github.com/spf13/viper v1.2.0
|
||||
github.com/stretchr/testify v1.2.2
|
||||
github.com/swaggo/swag v1.4.1-0.20181210033626-0e12fd5eb026
|
||||
github.com/urfave/cli v1.20.0 // indirect
|
||||
github.com/ziutek/mymysql v1.5.4 // indirect
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3
|
||||
golang.org/x/net v0.0.0-20181217023233-e147a9138326 // indirect
|
||||
golang.org/x/tools v0.0.0-20181026183834-f60e5f99f081 // indirect
|
||||
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/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/ugorji/go v1.1.7 // indirect
|
||||
github.com/ulule/limiter/v3 v3.3.0
|
||||
github.com/urfave/cli v1.22.1 // indirect
|
||||
github.com/valyala/fasttemplate v1.1.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422
|
||||
golang.org/x/net v0.0.0-20191021144547-ec77196f6094 // indirect
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect
|
||||
golang.org/x/sys v0.0.0-20191023151326-f89234f9a2c2 // indirect
|
||||
golang.org/x/tools v0.0.0-20191023202404-2b779830f9d3 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 // indirect
|
||||
google.golang.org/appengine v1.5.0 // indirect
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||
gopkg.in/d4l3k/messagediff.v1 v1.2.1
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||
gopkg.in/testfixtures.v2 v2.5.3
|
||||
gopkg.in/yaml.v2 v2.2.2 // indirect
|
||||
honnef.co/go/tools v0.0.0-20180920025451-e3ad64cb4ed3
|
||||
gopkg.in/yaml.v2 v2.2.4 // indirect
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a
|
||||
src.techknowlogick.com/xgo v0.0.0-20190507142556-a5b29ecb0ff4
|
||||
src.techknowlogick.com/xormigrate v0.0.0-20190321151057-24497c23c09c
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
388
go.sum
388
go.sum
@@ -1,23 +1,66 @@
|
||||
cloud.google.com/go v0.33.1/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0 h1:eOI3/cP2VTU6uZLDYAoic+eyzzB9YyGmJ7eIjl8rOPg=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
code.vikunja.io/web v0.0.0-20181130231148-b061c20192fb h1:LROmRUOGTxOpOxKy9S6XONDnT+t0v0j8+MZCedssTCc=
|
||||
code.vikunja.io/web v0.0.0-20181130231148-b061c20192fb/go.mod h1:PmGEu9qI7nbEKDn38H0SWgCoGO4GLdbjdlnWSzFi2PA=
|
||||
code.vikunja.io/web v0.0.0-20190507193736-edb39812af9c h1:L8aPCsaLQe9qytRavkRqipse64EbDK8mFijm+9SKf7I=
|
||||
code.vikunja.io/web v0.0.0-20190507193736-edb39812af9c/go.mod h1:9dOotUqYZJhDhimNh4Xo4e2i+8cR+qPFEQNCUzaplsI=
|
||||
code.vikunja.io/web v0.0.0-20190628071027-b5c16e24b0a7 h1:P9ncMaJE7RbYqBXF9lwT0hab7EPwuHOPslz3k1VxFs8=
|
||||
code.vikunja.io/web v0.0.0-20190628071027-b5c16e24b0a7/go.mod h1:cuP1/ieGWAZzgQGw+QPt6Y5F0fVb/8Ol5NV4QSezGdo=
|
||||
code.vikunja.io/web v0.0.0-20190628075253-b457b5a1a332 h1:gXxyLkjhgN+vqrLvPyqyScyG5fbu44FJp61TvntWM24=
|
||||
code.vikunja.io/web v0.0.0-20190628075253-b457b5a1a332/go.mod h1:cuP1/ieGWAZzgQGw+QPt6Y5F0fVb/8Ol5NV4QSezGdo=
|
||||
code.vikunja.io/web v0.0.0-20191021211916-f7834b02a174 h1:hBY+r6bzGEfHxolaXbiVoz2LBNNnyHZK7d7Ga4Jowu8=
|
||||
code.vikunja.io/web v0.0.0-20191021211916-f7834b02a174/go.mod h1:cuP1/ieGWAZzgQGw+QPt6Y5F0fVb/8Ol5NV4QSezGdo=
|
||||
code.vikunja.io/web v0.0.0-20191022193355-23a3d145177a h1:exDC9eZ+SK0GT3zB/5f3OBahWzbTZlvX9OfZWgqlbeI=
|
||||
code.vikunja.io/web v0.0.0-20191022193355-23a3d145177a/go.mod h1:cuP1/ieGWAZzgQGw+QPt6Y5F0fVb/8Ol5NV4QSezGdo=
|
||||
code.vikunja.io/web v0.0.0-20191022195605-8edfc5d33c79 h1:U2px27G/b082nUu8vO21wFNKF9BM+5YQJj4XRZiyn2I=
|
||||
code.vikunja.io/web v0.0.0-20191022195605-8edfc5d33c79/go.mod h1:cuP1/ieGWAZzgQGw+QPt6Y5F0fVb/8Ol5NV4QSezGdo=
|
||||
code.vikunja.io/web v0.0.0-20191023144416-3ee093147b6d h1:zhNidbAwqJSnkql03i7aHDUMyQo1vM8yR1Ks495FKvc=
|
||||
code.vikunja.io/web v0.0.0-20191023144416-3ee093147b6d/go.mod h1:cuP1/ieGWAZzgQGw+QPt6Y5F0fVb/8Ol5NV4QSezGdo=
|
||||
code.vikunja.io/web v0.0.0-20191023145656-bce8b505205d h1:Fw5eiTr4p82l4PLaML1ARgx3fjyebxVNvPsCz727brk=
|
||||
code.vikunja.io/web v0.0.0-20191023145656-bce8b505205d/go.mod h1:cuP1/ieGWAZzgQGw+QPt6Y5F0fVb/8Ol5NV4QSezGdo=
|
||||
code.vikunja.io/web v0.0.0-20191023190415-502bbbbd9dfa h1:rtYKpdT/6wGgxGNFUzl9Q/AHgS778+rSC20AcBPNu/I=
|
||||
code.vikunja.io/web v0.0.0-20191023190415-502bbbbd9dfa/go.mod h1:cuP1/ieGWAZzgQGw+QPt6Y5F0fVb/8Ol5NV4QSezGdo=
|
||||
code.vikunja.io/web v0.0.0-20191023202526-f337750c3573 h1:q+nf3ao4vLpoAaksuk6lkRAMAcD2grOPNj/HwjejLl4=
|
||||
code.vikunja.io/web v0.0.0-20191023202526-f337750c3573/go.mod h1:cuP1/ieGWAZzgQGw+QPt6Y5F0fVb/8Ol5NV4QSezGdo=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||
github.com/PuerkitoBio/purell v1.1.0 h1:rmGxhojJlM0tuKtfdvliR84CFHljx9ag64t2xmVkjK4=
|
||||
github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
|
||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf h1:eg0MeVzsP1G42dRafH3vf+al2vQIJU0YHX+1Tw87oco=
|
||||
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA=
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||
github.com/astaxie/beego v1.10.0/go.mod h1:0R4++1tUqERR0WYFWdfkcrsyoVBCG4DgpDGokT3yb+U=
|
||||
github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs=
|
||||
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/c2h5oh/datasize v0.0.0-20171227191756-4eba002a5eae h1:2Zmk+8cNvAGuY8AyvZuWpUdpQUAXwfom4ReVMe/CTIo=
|
||||
github.com/c2h5oh/datasize v0.0.0-20171227191756-4eba002a5eae/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M=
|
||||
github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cweill/gotests v1.5.2 h1:kKqmKmS2wCV3tuLnfpbiuN8OlkosQZTpCfiqmiuNAsA=
|
||||
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
|
||||
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/cweill/gotests v1.5.3 h1:k3t4wW/x/YNixWZJhUIn+mivmK5iV1tJVOwVYkx0UcU=
|
||||
github.com/d4l3k/messagediff v1.2.1 h1:ZcAIMYsUg0EAp9X+tt8/enBE/Q8Yd5kzPynLyKptt9U=
|
||||
github.com/d4l3k/messagediff v1.2.1/go.mod h1:Oozbb1TVXFac9FtSIxHBMnBCq2qeH/2KkEQxENCrlLo=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20181014144952-4e0d7dc8888f h1:WH0w/R4Yoey+04HhFxqZ6VX6I0d7RMyw5aXQ9UTvQPs=
|
||||
@@ -32,90 +75,188 @@ github.com/garyburd/redigo v1.6.0 h1:0VruCpn7yAIIu7pWVClQC8wxCJEcG3nyzpMSHKi1PQc
|
||||
github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
|
||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gin-contrib/gzip v0.0.1/go.mod h1:fGBJBCdt6qCZuCAOwWuFhBB4OOq9EFqlo5dEaFhhu5w=
|
||||
github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
|
||||
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y=
|
||||
github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
|
||||
github.com/go-chi/chi v3.3.3+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||
github.com/go-openapi/jsonpointer v0.17.0 h1:nH6xp8XdXHx8dqveo0ZuJBluCO2qGrPbDNZ0dwoRHP0=
|
||||
github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
|
||||
github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
|
||||
github.com/go-openapi/jsonpointer v0.19.0 h1:FTUMcX77w5rQkClIzDtTxvn6Bsa894CcrzNj2MMfeg8=
|
||||
github.com/go-openapi/jsonpointer v0.19.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
|
||||
github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
|
||||
github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w=
|
||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
github.com/go-openapi/jsonreference v0.17.0 h1:yJW3HCkTHg7NOA+gZ83IPHzUSnUzGXhGmsdiCcMexbA=
|
||||
github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
|
||||
github.com/go-openapi/spec v0.17.2 h1:eb2NbuCnoe8cWAxhtK6CfMWUYmiFEZJ9Hx3Z2WRwJ5M=
|
||||
github.com/go-openapi/spec v0.17.2/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
|
||||
github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
|
||||
github.com/go-openapi/jsonreference v0.19.0 h1:BqWKpV1dFd+AuiKlgtddwVIFQsuMpxfBDBHGfM2yNpk=
|
||||
github.com/go-openapi/jsonreference v0.19.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
|
||||
github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
|
||||
github.com/go-openapi/jsonreference v0.19.3 h1:5cxNfTy0UVC3X8JL5ymxzyoUZmo8iZb+jeTWn7tUa8o=
|
||||
github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
|
||||
github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
|
||||
github.com/go-openapi/spec v0.19.0 h1:A4SZ6IWh3lnjH0rG0Z5lkxazMGBECtrZcbyYQi+64k4=
|
||||
github.com/go-openapi/spec v0.19.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
|
||||
github.com/go-openapi/spec v0.19.3 h1:0XRyw8kguri6Yw4SxhsQA/atC88yqrk0+G4YhI2wabc=
|
||||
github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
|
||||
github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
|
||||
github.com/go-openapi/swag v0.17.2 h1:K/ycE/XTUDFltNHSO32cGRUhrVGJD64o8WgAIZNyc3k=
|
||||
github.com/go-openapi/swag v0.17.2/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
|
||||
github.com/go-redis/redis v6.14.2+incompatible h1:UE9pLhzmWf+xHNmZsoccjXosPicuiNaInPgym8nzfg0=
|
||||
github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
||||
github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
|
||||
github.com/go-openapi/swag v0.19.0 h1:Kg7Wl7LkTPlmc393QZQ/5rQadPhi7pBVEMZxyTi0Ii8=
|
||||
github.com/go-openapi/swag v0.19.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
|
||||
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY=
|
||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||
github.com/go-redis/redis v6.14.0+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
||||
github.com/go-redis/redis v6.15.2+incompatible h1:9SpNVG76gr6InJGxoZ6IuuxaCOQwDAhzyXg+Bs+0Sb4=
|
||||
github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
||||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
|
||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-xorm/builder v0.0.0-20170519032130-c8871c857d25 h1:jUX9yw6+iKrs/WuysV2M6ap/ObK/07SE/a7I2uxitwM=
|
||||
github.com/go-xorm/builder v0.0.0-20170519032130-c8871c857d25/go.mod h1:M+P3wv0K2C+ynucGDEqJCeOTc+6DcAtiiqU8GrCksXY=
|
||||
github.com/go-xorm/core v0.5.8 h1:vQ0ghlVGnlnFmm4SpHY+xNnPlH810paMcw+Hwz9BCqE=
|
||||
github.com/go-xorm/core v0.5.8/go.mod h1:d8FJ9Br8OGyQl12MCclmYBuBqqxsyeedpXciV5Myih8=
|
||||
github.com/go-xorm/builder v0.3.2 h1:pSsZQRRzJNapKEAEhigw3xLmiLPeAYv5GFlpYZ8+a5I=
|
||||
github.com/go-xorm/builder v0.3.2/go.mod h1:v8mE3MFBgtL+RGFNfUnAMUqqfk/Y4W5KuwCFQIEpQLk=
|
||||
github.com/go-xorm/builder v0.3.4 h1:FxkeGB4Cggdw3tPwutLCpfjng2jugfkg6LDMrd/KsoY=
|
||||
github.com/go-xorm/builder v0.3.4/go.mod h1:KxkQkNN1DpPKTedxXyTQcmH+rXfvk4LZ9SOOBoZBAxw=
|
||||
github.com/go-xorm/core v0.6.0 h1:tp6hX+ku4OD9khFZS8VGBDRY3kfVCtelPfmkgCyHxL0=
|
||||
github.com/go-xorm/core v0.6.0/go.mod h1:d8FJ9Br8OGyQl12MCclmYBuBqqxsyeedpXciV5Myih8=
|
||||
github.com/go-xorm/core v0.6.2 h1:EJLcSxf336POJr670wKB55Mah9f93xzvGYzNRgnT8/Y=
|
||||
github.com/go-xorm/core v0.6.2/go.mod h1:bwPIfLdm/FzWgVUH8WPVlr+uJhscvNGFcaZKXsI3n2c=
|
||||
github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:9wScpmSP5A3Bk8V3XHWUcJmYTh+ZnlHVyc+A4oZYS3Y=
|
||||
github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:56xuuqnHyryaerycW3BfssRdxQstACi0Epw/yC5E2xM=
|
||||
github.com/go-xorm/tests v0.5.6 h1:E4nmVkKfHQAm+i2/pmOJ5JUej6sORVcvwl6/LQybif4=
|
||||
github.com/go-xorm/tests v0.5.6/go.mod h1:s8J/EnVBcXQR93dN7Jy6Dwlo92HUP5nTgKWF1wGeCDg=
|
||||
github.com/go-xorm/xorm v0.0.0-20170930012613-29d4a0330a00 h1:jlA1XEj8QHl6my6FUkHwRCGu/J5hQ1zkW7RqULZ2XGc=
|
||||
github.com/go-xorm/xorm v0.0.0-20170930012613-29d4a0330a00/go.mod h1:i7qRPD38xj/v75UV+a9pEzr5tfRaH2ndJfwt/fGbQhs=
|
||||
github.com/go-xorm/xorm v0.7.1 h1:Kj7mfuqctPdX60zuxP6EoEut0f3E6K66H6hcoxiHUMc=
|
||||
github.com/go-xorm/xorm v0.7.1/go.mod h1:EHS1htMQFptzMaIHKyzqpHGw6C9Rtug75nsq6DA9unI=
|
||||
github.com/go-xorm/xorm-redis-cache v0.0.0-20180727005610-859b313566b2 h1:57QbyUkFcFjipHJQstYR5owRxsQzgD8/OAO/hr4yl/E=
|
||||
github.com/go-xorm/xorm-redis-cache v0.0.0-20180727005610-859b313566b2/go.mod h1:xxK9FGkFXrau9/vGdDYSOyQfSgKXBV7iHXpQfNuv6B0=
|
||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/gordonklaus/ineffassign v0.0.0-20180909121442-1003c8bd00dc h1:cJlkeAx1QYgO5N80aF5xRGstVsRQwgLR7uA2FnP1ZjY=
|
||||
github.com/gordonklaus/ineffassign v0.0.0-20180909121442-1003c8bd00dc/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28=
|
||||
github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI=
|
||||
github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 h1:vr3AYkKovP8uR8AvSGGUK1IDqRa5lAAvEkZG1LKaCRc=
|
||||
github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ=
|
||||
github.com/jackc/pgx v3.2.0+incompatible h1:0Vihzu20St42/UDsvZGdNE6jak7oi/UOeMzwMPHkgFY=
|
||||
github.com/jackc/pgx v3.2.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I=
|
||||
github.com/jgautheron/goconst v0.0.0-20170703170152-9740945f5dcb h1:D5s1HIu80AcMGcqmk7fNIVptmAubVHHaj3v5Upex6Zs=
|
||||
github.com/jgautheron/goconst v0.0.0-20170703170152-9740945f5dcb/go.mod h1:82TxjOpWQiPmywlbIaB2ZkqJoSYJdLGPgAJDvM3PbKc=
|
||||
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
|
||||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||
github.com/karalabe/xgo v0.0.0-20181007145344-72da7d1d3970 h1:0+1ZURVRim6FxA/jhWhJklsgoWc69q1sxlIu2Ztnhy0=
|
||||
github.com/karalabe/xgo v0.0.0-20181007145344-72da7d1d3970/go.mod h1:iYGcTYIPUvEWhFo6aKUuLchs+AV4ssYdyuBbQJZGcBk=
|
||||
github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/json-iterator/go v0.0.0-20180806060727-1624edc4454b/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/kolaente/caldav-go v3.0.1-0.20190524174923-9e5cd1688227+incompatible h1:PkEEpmbrFXlMul8cOplR8nkcIM/NDbx+H6fq2+vaKAA=
|
||||
github.com/kolaente/caldav-go v3.0.1-0.20190524174923-9e5cd1688227+incompatible/go.mod h1:y1UhTNI4g0hVymJrI6yJ5/ohy09hNBeU8iJEZjgdDOw=
|
||||
github.com/kolaente/echo/v4 v4.0.0-20190507190305-3725a216d803/go.mod h1:3LbYC6VkwmUnmLPZ8WFdHdQHG77e9GQbjyhWdb1QvC4=
|
||||
github.com/kolaente/echo/v4 v4.0.0-20190621113036-3b0700f6d073 h1:vOueKBVhcaAusqPDK/g7lRYOwqfQ0YI3oLrBV+iGZV8=
|
||||
github.com/kolaente/echo/v4 v4.0.0-20190621113036-3b0700f6d073/go.mod h1:kU/7PwzgNxZH4das4XNsSpBSOD09XIF5YEPzjpkGnGE=
|
||||
github.com/kolaente/echo/v4 v4.0.0-20190622213746-34000ea0f7d3 h1:5RyXbSrJtkFl1EGFgyDYwiBQMbj8sMtP33aFHnT+5YE=
|
||||
github.com/kolaente/echo/v4 v4.0.0-20190622213746-34000ea0f7d3/go.mod h1:kU/7PwzgNxZH4das4XNsSpBSOD09XIF5YEPzjpkGnGE=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
|
||||
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/labstack/echo v3.3.5+incompatible h1:9PfxPUmasKzeJor9uQTaXLT6WUG/r+vSTmvXxvv3JO4=
|
||||
github.com/labstack/echo v3.3.5+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s=
|
||||
github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg=
|
||||
github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s=
|
||||
github.com/labstack/echo/v4 v4.1.5/go.mod h1:3LbYC6VkwmUnmLPZ8WFdHdQHG77e9GQbjyhWdb1QvC4=
|
||||
github.com/labstack/echo/v4 v4.1.6 h1:WOvLa4T1KzWCRpANwz0HGgWDelXSSGwIKtKBbFdHTv4=
|
||||
github.com/labstack/echo/v4 v4.1.6/go.mod h1:kU/7PwzgNxZH4das4XNsSpBSOD09XIF5YEPzjpkGnGE=
|
||||
github.com/labstack/echo/v4 v4.1.7-0.20190627175217-8fb7b5be270f h1:fNJtR+TNyxTdYCZU40fc8Or8RyBqMOKYNv+Zay5gjvk=
|
||||
github.com/labstack/echo/v4 v4.1.7-0.20190627175217-8fb7b5be270f/go.mod h1:kU/7PwzgNxZH4das4XNsSpBSOD09XIF5YEPzjpkGnGE=
|
||||
github.com/labstack/echo/v4 v4.1.11 h1:z0BZoArY4FqdpUEl+wlHp4hnr/oSR6MTmQmv8OHSoww=
|
||||
github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g=
|
||||
github.com/labstack/gommon v0.2.8 h1:JvRqmeZcfrHC5u6uVleB4NxxNbzx6gpbJiQknDbKQu0=
|
||||
github.com/labstack/gommon v0.2.8/go.mod h1:/tj9csK2iPSBvn+3NLM9e52usepMtrd5ilFYA+wQNJ4=
|
||||
github.com/labstack/gommon v0.2.9 h1:heVeuAYtevIQVYkGj6A41dtfT91LrvFG220lavpWhrU=
|
||||
github.com/labstack/gommon v0.2.9/go.mod h1:E8ZTmW9vw5az5/ZyHWCp0Lw4OH2ecsaBP1C/NKavGG4=
|
||||
github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0=
|
||||
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
|
||||
github.com/laurent22/ical-go v0.1.1-0.20181107184520-7e5d6ade8eef h1:RZnRnSID1skF35j/15KJ6hKZkdIC/teQClJK5wP5LU4=
|
||||
github.com/laurent22/ical-go v0.1.1-0.20181107184520-7e5d6ade8eef/go.mod h1:4LATl0uhhtytR6p9n1AlktDyIz4u2iUnWEdI3L/hXiw=
|
||||
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329 h1:2gxZ0XQIU/5z3Z3bUBu+FXuk2pFbkN6tcwi/pjyaDic=
|
||||
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190403194419-1ea4449da983 h1:wL11wNW7dhKIcRCHSm4sHKPWz0tt4mwBsVodG7+Xyqg=
|
||||
github.com/mailru/easyjson v0.0.0-20190403194419-1ea4449da983/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM=
|
||||
github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
|
||||
github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg=
|
||||
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
||||
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
|
||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
|
||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.7 h1:UvyT9uN+3r7yLEYSlJsbQGdsaB/a0DlgWP3pql6iwOc=
|
||||
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
||||
github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10=
|
||||
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
|
||||
github.com/mattn/go-oci8 v0.0.0-20181115070430-6eefff3c767c/go.mod h1:/M9VLO+lUPmxvoOK2PfWRZ8mTtB4q1Hy9lEGijv9Nr8=
|
||||
github.com/mattn/go-oci8 v0.0.0-20181130072307-052f5d97b9b6 h1:gheNi9lnffYyVyqQzJqY7lo+M3bCDVw5fLU/jSuCMhc=
|
||||
github.com/mattn/go-oci8 v0.0.0-20181130072307-052f5d97b9b6/go.mod h1:/M9VLO+lUPmxvoOK2PfWRZ8mTtB4q1Hy9lEGijv9Nr8=
|
||||
github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y=
|
||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o=
|
||||
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mitchellh/mapstructure v1.0.0 h1:vVpGvMXJPqSDh2VYHF7gsfQj8Ncx+Xw5Y1KHeTRY+7I=
|
||||
github.com/mitchellh/mapstructure v1.0.0/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/olekukonko/tablewriter v0.0.1 h1:b3iUnf1v+ppJiOfNX4yxxqfWKMQPZR5yoh8urCTFX88=
|
||||
github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
||||
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pelletier/go-toml v1.4.0 h1:u3Z1r+oOXJIkxqw34zVhyPgjBsm6X2wn21NWs/HfSeg=
|
||||
github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo=
|
||||
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v0.9.2 h1:awm861/B8OKDd2I/6o1dy3ra4BamzKhYOiGItCeZ740=
|
||||
@@ -126,67 +267,200 @@ github.com/prometheus/common v0.0.0-20181126121408-4724e9255275 h1:PnBWHBf+6L0jO
|
||||
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a h1:9a8MnZMP0X2nLJdBg+pBmGgkJlSaKC2KaQmTCk1XDtE=
|
||||
github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24 h1:pntxY8Ary0t43dCZ5dqY4YTJCObLY1kIXl0uzMv+7DE=
|
||||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
||||
github.com/shurcooL/httpfs v0.0.0-20190527155220-6a4d4a70508b h1:4kg1wyftSKxLtnPAvcRWakIPpokB9w780/KwrNLnfPA=
|
||||
github.com/shurcooL/httpfs v0.0.0-20190527155220-6a4d4a70508b/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd h1:ug7PpSOB5RBPK1Kg6qskGBoP3Vnj/aNYFTznWvlkGo0=
|
||||
github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
|
||||
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/cast v1.2.0 h1:HHl1DSRbEQN2i8tJmtS6ViPyHx35+p51amrdsiTCrkg=
|
||||
github.com/spf13/cast v1.2.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg=
|
||||
github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
|
||||
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
||||
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
|
||||
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v1.0.2 h1:Fy0orTDgHdbnzHcsOgfCN4LtHf0ec3wwtiwJqwvf3Gc=
|
||||
github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/viper v1.2.0 h1:M4Rzxlu+RgU4pyBRKhKaVN1VeYOm8h2jgyXnAseDgCc=
|
||||
github.com/spf13/viper v1.2.0/go.mod h1:P4AexN0a+C9tGAnUFNwDMYYZv3pjFuvmeiMyKRaNVlI=
|
||||
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
||||
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
|
||||
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M=
|
||||
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/swaggo/swag v1.4.1-0.20181210033626-0e12fd5eb026 h1:XAOjF3QgjDUkVrPMO4rYvNptSHQgUlHwQsEdJOTxHQ8=
|
||||
github.com/swaggo/swag v1.4.1-0.20181210033626-0e12fd5eb026/go.mod h1:hog2WgeMOrQ/LvQ+o1YGTeT+vWVrbi0SiIslBtxKTyM=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14/go.mod h1:gxQT6pBGRuIGunNf/+tSOB5OHvguWi8Tbt82WOkf35E=
|
||||
github.com/swaggo/gin-swagger v1.1.0/go.mod h1:FQlm07YuT1glfN3hQiO11UQ2m39vOCZ/aa3WWr5E+XU=
|
||||
github.com/swaggo/gin-swagger v1.2.0/go.mod h1:qlH2+W7zXGZkczuL+r2nEBR2JTT+/lX05Nn6vPhc7OI=
|
||||
github.com/swaggo/swag v1.4.0/go.mod h1:hog2WgeMOrQ/LvQ+o1YGTeT+vWVrbi0SiIslBtxKTyM=
|
||||
github.com/swaggo/swag v1.5.0 h1:haK8VG3hj+v/c8hQ4f3U+oYpkdI/26m9LAUTXHOv+2U=
|
||||
github.com/swaggo/swag v1.5.0/go.mod h1:+xZrnu5Ut3GcUkKAJm9spnOooIS1WB1cUOkLNPrvrE0=
|
||||
github.com/swaggo/swag v1.5.1/go.mod h1:1Bl9F/ZBpVWh22nY0zmYyASPO1lI/zIwRDrpZU+tv8Y=
|
||||
github.com/swaggo/swag v1.6.3 h1:N+uVPGP4H2hXoss2pt5dctoSUPKKRInr6qcTMOm0usI=
|
||||
github.com/swaggo/swag v1.6.3/go.mod h1:wcc83tB4Mb2aNiL/HP4MFeQdpHUrca+Rp/DRNgWAUio=
|
||||
github.com/ugorji/go v1.1.1/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=
|
||||
github.com/ugorji/go v1.1.2/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=
|
||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||
github.com/ugorji/go v1.1.5-pre/go.mod h1:FwP/aQVg39TXzItUBMwnWp9T9gPQnXw4Poh4/oBQZ/0=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
github.com/ugorji/go/codec v0.0.0-20181022190402-e5e69e061d4f/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/ugorji/go/codec v0.0.0-20181209151446-772ced7fd4c2/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/ugorji/go/codec v0.0.0-20190320090025-2dc34c0b8780/go.mod h1:iT03XoTwV7xq/+UGwKO3UbC1nNNlopQiY61beSdrtOA=
|
||||
github.com/ugorji/go/codec v1.1.5-pre/go.mod h1:tULtS6Gy1AE1yCENaw4Vb//HLH5njI2tfCQDUqRd8fI=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/ulule/limiter/v3 v3.3.0 h1:DuMRthpkl1wW9Em6xOVw5HMHnbDumSIDydiMqP0PTXs=
|
||||
github.com/ulule/limiter/v3 v3.3.0/go.mod h1:E6sfg3hfRgW+yFvkE/rZf6YLqXYFMWTmZaZKvdEiQsA=
|
||||
github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/urfave/cli v1.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY=
|
||||
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasttemplate v0.0.0-20170224212429-dcecefd839c4 h1:gKMu1Bf6QINDnvyZuTaACm9ofY+PRh+5vFz4oxBZeF8=
|
||||
github.com/valyala/fasttemplate v0.0.0-20170224212429-dcecefd839c4/go.mod h1:50wTf68f99/Zt14pr046Tgt3Lp2vLyFZKzbFXTOabXw=
|
||||
github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8=
|
||||
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||
github.com/valyala/fasttemplate v1.1.0 h1:RZqt0yGBsps8NGvLSGW804QQqCUYYLsaOjTVHy1Ocw4=
|
||||
github.com/valyala/fasttemplate v1.1.0/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
|
||||
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
|
||||
golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85 h1:et7+NAX3lLIk5qUCTA9QelBjGE/NkhzYw/mhnr0s7nI=
|
||||
golang.org/x/crypto v0.0.0-20181127143415-eb0de9b17e85/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3 h1:x/bBzNauLQAlE3fLku/xy92Y8QwKX5HZymrMz2IiKFc=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190506204251-e1dfcc566284 h1:rlLehGeYg6jfoyz/eDqDU1iRXLKfR42nnNh57ytKEWo=
|
||||
golang.org/x/crypto v0.0.0-20190506204251-e1dfcc566284/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4 h1:ydJNl0ENAG67pFbB+9tfhiL2pYqLhfoaZFw/cjLhY4A=
|
||||
golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422 h1:QzoH/1pFpZguR8NrRHLcO6jKqfv2zpuSqZLgdm7ZmjI=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181005035420-146acd28ed58 h1:otZG8yDCO4LVps5+9bxOeNiCvgmOyt96J3roHTYs7oE=
|
||||
golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc h1:a3CU5tJYVj92DY2LaA1kUkrsqD5/3mLDhx2NcNqyW+0=
|
||||
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181217023233-e147a9138326 h1:iCzOf0xz39Tstp+Tu/WwyGjUXCk34QhQORRxBeXXTA4=
|
||||
golang.org/x/net v0.0.0-20181217023233-e147a9138326/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190322120337-addf6b3196f6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c h1:uOCk1iQW6Vc18bnC13MfzScl+wdKBmM9Y9kU7Z83/lw=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190607181551-461777fb6f67/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190611141213-3f473d35a33a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191011234655-491137f69257 h1:ry8e2D+cwaV6hk7lb3aRTjjZo24shrbK0e11QEOkTIg=
|
||||
golang.org/x/net v0.0.0-20191011234655-491137f69257/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191021144547-ec77196f6094 h1:5O4U9trLjNpuhpynaDsqwCk+Tw6seqJz1EbqbnzHrc8=
|
||||
golang.org/x/net v0.0.0-20191021144547-ec77196f6094/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180906133057-8cf3aee42992 h1:BH3eQWeGbwRU2+wxxuuPOdFBmaiBH81O8BugSjHeTFg=
|
||||
golang.org/x/sys v0.0.0-20180906133057-8cf3aee42992/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35 h1:YAFjXN64LMvktoUZH9zgY4lGc/msGN7HQfoSuKCgaDU=
|
||||
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b h1:ag/x1USPSsqHud38I9BAC88qdNLDHHtQ4mlgQIZPPNA=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190609082536-301114b31cce/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190621203818-d432491b9138 h1:t8BZD9RDjkm9/h7yYN6kE8oaeov5r9aztkB7zKA5Tkg=
|
||||
golang.org/x/sys v0.0.0-20190621203818-d432491b9138/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb h1:fgwFCsaw9buMuxNd6+DQfAuSFqbNiQZpcgJQAgJsK6k=
|
||||
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY=
|
||||
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7 h1:HmbHVPwrPEKPGLAcHSrMe6+hqSUlvZU0rab6x5EXfGU=
|
||||
golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191023145028-b69606af412f h1:HNixo/W24k2W4EliZfUFl5ApIz/dMDShw52wmWfJ8/s=
|
||||
golang.org/x/sys v0.0.0-20191023145028-b69606af412f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191023151326-f89234f9a2c2 h1:I7efaDQAsIQmkTF+WSdcydwVWzK07Yuz8IFF8rNkDe0=
|
||||
golang.org/x/sys v0.0.0-20191023151326-f89234f9a2c2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/tools v0.0.0-20181026183834-f60e5f99f081 h1:QJP9sxq2/KbTxFnGduVryxJOt6r/UVGyom3tLaqu7tc=
|
||||
golang.org/x/tools v0.0.0-20181026183834-f60e5f99f081/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190110015856-aa033095749b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190322203728-c1a832b0ad89/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190606050223-4d9ae51c2468/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190608022120-eacb66d2a7c3/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190611222205-d73e1c7e250b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac h1:MQEvx39qSf8vyrx3XRaOe+j1UDIzKwkYOVObRgGPVqI=
|
||||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628034336-212fb13d595e h1:ZlQjfVdpDxeqxRfmO30CdqWWzTvgRCj0MxaUVfxEG1k=
|
||||
golang.org/x/tools v0.0.0-20190628034336-212fb13d595e/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a h1:TwMENskLwU2NnWBzrJGEWHqSiGUkO/B4rfyhwqDxDYQ=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191022174149-ab6dbf99d100 h1:OT2Y8iVtXGHPODZd6iwpndJmAYRiZc75IYxlufvlkLg=
|
||||
golang.org/x/tools v0.0.0-20191022174149-ab6dbf99d100/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191023143423-ff611c50cd12 h1:s9/f9YHBWfC3jIKMbJElk5+EwgC58Khn6t1EdLnQ9+k=
|
||||
golang.org/x/tools v0.0.0-20191023143423-ff611c50cd12/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191023163450-98e333b8b3a3 h1:4haCIJia9wHJUU7z9f7PTC8Nf599Ok93njSCHb5gJas=
|
||||
golang.org/x/tools v0.0.0-20191023163450-98e333b8b3a3/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191023202404-2b779830f9d3 h1:0vQisIa3mUFShxg7Xyq8WFt/ArQ1soDk5A5uF62IJCc=
|
||||
golang.org/x/tools v0.0.0-20191023202404-2b779830f9d3/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.3.0 h1:FBSsiFRMz3LBeXIomRnVzrQwSDj4ibvcRexLG0LZGQk=
|
||||
google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/d4l3k/messagediff.v1 v1.2.1 h1:70AthpjunwzUiarMHyED52mj9UwtAnE89l1Gmrt3EU0=
|
||||
gopkg.in/d4l3k/messagediff.v1 v1.2.1/go.mod h1:EUzikiKadqXWcD1AzJLagx0j/BeeWGtn++04Xniyg44=
|
||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
|
||||
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
|
||||
gopkg.in/stretchr/testify.v1 v1.2.2 h1:yhQC6Uy5CqibAIlk1wlusa/MJ3iAN49/BsR/dCCKz3M=
|
||||
gopkg.in/stretchr/testify.v1 v1.2.2/go.mod h1:QI5V/q6UbPmuhtm10CaFZxED9NreB8PnFYN9JcR6TxU=
|
||||
gopkg.in/testfixtures.v2 v2.5.3 h1:P8gDACSLJGxutzBqbzvfiXYgmQ2s00LIr4uAvWBCPAg=
|
||||
gopkg.in/testfixtures.v2 v2.5.3/go.mod h1:rGPtsOtPcZhs7AsHYf1WmufW1hEsM6DXdLrYz60nrQQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
@@ -195,5 +469,11 @@ gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
honnef.co/go/tools v0.0.0-20180920025451-e3ad64cb4ed3 h1:LyX67rVB0kBUFoROrQfzKwdrYLH1cRzHibxdJW85J1c=
|
||||
honnef.co/go/tools v0.0.0-20180920025451-e3ad64cb4ed3/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a h1:LJwr7TCTghdatWv40WobzlKXc9c4s8oGa7QKJUtHhWA=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
src.techknowlogick.com/xgo v0.0.0-20190507142556-a5b29ecb0ff4 h1:DZKMg4qnT7UIyB5ZaC6ZqltF2K5KhA1oQ2PdxOLZ3jg=
|
||||
src.techknowlogick.com/xgo v0.0.0-20190507142556-a5b29ecb0ff4/go.mod h1:Ood88figJtEukTnU20P1IrXhyAkbOIGi4YzmeHVtGH0=
|
||||
src.techknowlogick.com/xormigrate v0.0.0-20190321151057-24497c23c09c h1:fTwL7EZ3ouk3xeiPiRBYEjSPWTREb9T57bjzpRBNOpQ=
|
||||
src.techknowlogick.com/xormigrate v0.0.0-20190321151057-24497c23c09c/go.mod h1:B2NutmcRaDDw4EGe7DoCwyWCELA8W+KxXPhLtgqFUaU=
|
||||
|
||||
58
main.go
58
main.go
@@ -16,62 +16,8 @@
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/docs"
|
||||
_ "code.vikunja.io/api/pkg/config" // To trigger its init() which initializes the config
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/mail"
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/api/pkg/routes"
|
||||
"context"
|
||||
"github.com/spf13/viper"
|
||||
"os"
|
||||
"os/signal"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Version sets the version to be printed to the user. Gets overwritten by "make release" or "make build" with last git commit or tag.
|
||||
var Version = "0.1"
|
||||
import "code.vikunja.io/api/pkg/cmd"
|
||||
|
||||
func main() {
|
||||
|
||||
// Init logging
|
||||
log.InitLogger()
|
||||
|
||||
// Set Engine
|
||||
err := models.SetEngine()
|
||||
if err != nil {
|
||||
log.Log.Fatal(err.Error())
|
||||
}
|
||||
|
||||
// Start the mail daemon
|
||||
mail.StartMailDaemon()
|
||||
|
||||
// Version notification
|
||||
log.Log.Infof("Vikunja version %s", Version)
|
||||
|
||||
// Additional swagger information
|
||||
docs.SwaggerInfo.Version = Version
|
||||
|
||||
// Start the webserver
|
||||
e := routes.NewEcho()
|
||||
routes.RegisterRoutes(e)
|
||||
// Start server
|
||||
go func() {
|
||||
if err := e.Start(viper.GetString("service.interface")); err != nil {
|
||||
e.Logger.Info("shutting down...")
|
||||
}
|
||||
}()
|
||||
|
||||
// Wait for interrupt signal to gracefully shutdown the server with
|
||||
// a timeout of 10 seconds.
|
||||
quit := make(chan os.Signal)
|
||||
signal.Notify(quit, os.Interrupt)
|
||||
<-quit
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
log.Log.Infof("Shutting down...")
|
||||
if err := e.Shutdown(ctx); err != nil {
|
||||
e.Logger.Fatal(err)
|
||||
}
|
||||
cmd.Execute()
|
||||
}
|
||||
|
||||
@@ -17,11 +17,16 @@
|
||||
package caldav
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/api/pkg/utils"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// DateFormat ist the caldav date format
|
||||
const DateFormat = `20060102T150405`
|
||||
|
||||
// Event holds a single caldav event
|
||||
type Event struct {
|
||||
Summary string
|
||||
@@ -34,6 +39,29 @@ type Event struct {
|
||||
EndUnix int64
|
||||
}
|
||||
|
||||
// Todo holds a single VTODO
|
||||
type Todo struct {
|
||||
// Required
|
||||
TimestampUnix int64
|
||||
UID string
|
||||
|
||||
// Optional
|
||||
Summary string
|
||||
Description string
|
||||
CompletedUnix int64
|
||||
Organizer *models.User
|
||||
Priority int64 // 0-9, 1 is highest
|
||||
RelatedToUID string
|
||||
|
||||
StartUnix int64
|
||||
EndUnix int64
|
||||
DueDateUnix int64
|
||||
Duration time.Duration
|
||||
|
||||
CreatedUnix int64
|
||||
UpdatedUnix int64 // last-mod
|
||||
}
|
||||
|
||||
// Alarm holds infos about an alarm from a caldav event
|
||||
type Alarm struct {
|
||||
TimeUnix int64
|
||||
@@ -92,10 +120,89 @@ END:VCALENDAR` // Need a line break
|
||||
return
|
||||
}
|
||||
|
||||
// ParseTodos returns a caldav vcalendar string with todos
|
||||
func ParseTodos(config *Config, todos []*Todo) (caldavtodos string) {
|
||||
caldavtodos = `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
X-PUBLISHED-TTL:PT4H
|
||||
X-WR-CALNAME:` + config.Name + `
|
||||
PRODID:-//` + config.ProdID + `//EN`
|
||||
|
||||
for _, t := range todos {
|
||||
if t.UID == "" {
|
||||
t.UID = makeCalDavTimeFromUnixTime(t.TimestampUnix) + utils.Sha256(t.Summary)
|
||||
}
|
||||
|
||||
caldavtodos += `
|
||||
BEGIN:VTODO
|
||||
UID:` + t.UID + `
|
||||
DTSTAMP:` + makeCalDavTimeFromUnixTime(t.TimestampUnix) + `
|
||||
SUMMARY:` + t.Summary
|
||||
|
||||
if t.StartUnix != 0 {
|
||||
caldavtodos += `
|
||||
DTSTART: ` + makeCalDavTimeFromUnixTime(t.StartUnix)
|
||||
}
|
||||
if t.EndUnix != 0 {
|
||||
caldavtodos += `
|
||||
DTEND: ` + makeCalDavTimeFromUnixTime(t.EndUnix)
|
||||
}
|
||||
if t.Description != "" {
|
||||
caldavtodos += `
|
||||
DESCRIPTION:` + t.Description
|
||||
}
|
||||
if t.CompletedUnix != 0 {
|
||||
caldavtodos += `
|
||||
COMPLETED: ` + makeCalDavTimeFromUnixTime(t.CompletedUnix)
|
||||
}
|
||||
if t.Organizer != nil {
|
||||
caldavtodos += `
|
||||
ORGANIZER;CN=:` + t.Organizer.Username
|
||||
}
|
||||
|
||||
if t.RelatedToUID != "" {
|
||||
caldavtodos += `
|
||||
RELATED-TO:` + t.RelatedToUID
|
||||
}
|
||||
|
||||
if t.DueDateUnix != 0 {
|
||||
caldavtodos += `
|
||||
DUE:` + makeCalDavTimeFromUnixTime(t.DueDateUnix)
|
||||
}
|
||||
|
||||
if t.CreatedUnix != 0 {
|
||||
caldavtodos += `
|
||||
CREATED:` + makeCalDavTimeFromUnixTime(t.CreatedUnix)
|
||||
}
|
||||
|
||||
if t.Duration != 0 {
|
||||
caldavtodos += `
|
||||
DURATION:PT` + fmt.Sprintf("%.6f", t.Duration.Hours()) + `H` + fmt.Sprintf("%.6f", t.Duration.Minutes()) + `M` + fmt.Sprintf("%.6f", t.Duration.Seconds()) + `S`
|
||||
}
|
||||
|
||||
if t.Priority != 0 {
|
||||
caldavtodos += `
|
||||
PRIORITY:` + strconv.Itoa(int(t.Priority))
|
||||
}
|
||||
|
||||
caldavtodos += `
|
||||
LAST-MODIFIED:` + makeCalDavTimeFromUnixTime(t.UpdatedUnix)
|
||||
|
||||
caldavtodos += `
|
||||
END:VTODO`
|
||||
}
|
||||
|
||||
caldavtodos += `
|
||||
END:VCALENDAR` // Need a line break
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func makeCalDavTimeFromUnixTime(unixtime int64) (caldavtime string) {
|
||||
tz, _ := time.LoadLocation("UTC")
|
||||
tm := time.Unix(unixtime, 0).In(tz)
|
||||
return tm.Format("20060102T150405")
|
||||
return tm.Format(DateFormat)
|
||||
}
|
||||
|
||||
func calcAlarmDateFromReminder(eventStartUnix, reminderUnix int64) (alarmTime string) {
|
||||
|
||||
88
pkg/cmd/cmd.go
Normal file
88
pkg/cmd/cmd.go
Normal file
@@ -0,0 +1,88 @@
|
||||
// Vikunja is a todo-list application to facilitate your life.
|
||||
// Copyright 2019 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/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"
|
||||
"code.vikunja.io/api/pkg/red"
|
||||
"fmt"
|
||||
"github.com/spf13/cobra"
|
||||
"os"
|
||||
)
|
||||
|
||||
func init() {
|
||||
cobra.OnInitialize(initialize)
|
||||
}
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "vikunja",
|
||||
Short: "Vikunja is the to-do app to organize your life.",
|
||||
Long: `Vikunja (/vɪˈkuːnjə/)
|
||||
The to-do app to organize your life.
|
||||
|
||||
Also one of the two wild South American camelids which live in the high
|
||||
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,
|
||||
}
|
||||
|
||||
// Execute starts the application
|
||||
func Execute() {
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
fmt.Println(err)
|
||||
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 = files.SetEngine()
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
|
||||
// Initialize the files handler
|
||||
files.InitFileHandler()
|
||||
|
||||
// Start the mail daemon
|
||||
mail.StartMailDaemon()
|
||||
}
|
||||
59
pkg/cmd/migrate.go
Normal file
59
pkg/cmd/migrate.go
Normal file
@@ -0,0 +1,59 @@
|
||||
// Vikunja is a todo-list application to facilitate your life.
|
||||
// Copyright 2019 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/migration"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
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")
|
||||
migrateCmd.AddCommand(migrationRollbackCmd)
|
||||
rootCmd.AddCommand(migrateCmd)
|
||||
}
|
||||
|
||||
// TODO: add args to run migrations up or down, until a certain point etc
|
||||
// Rollback until
|
||||
// list -> Essentially just show the table, maybe with an extra column if the migration did run or not
|
||||
var migrateCmd = &cobra.Command{
|
||||
Use: "migrate",
|
||||
Short: "Run all database migrations which didn't already run.",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
migration.Migrate(nil)
|
||||
},
|
||||
}
|
||||
|
||||
var migrateListCmd = &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "Show a list with all database migrations.",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
migration.ListMigrations()
|
||||
},
|
||||
}
|
||||
|
||||
var rollbackUntilFlag string
|
||||
|
||||
var migrationRollbackCmd = &cobra.Command{
|
||||
Use: "rollback",
|
||||
Short: "Roll migrations back until a certain point.",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
migration.Rollback(rollbackUntilFlag)
|
||||
},
|
||||
}
|
||||
35
pkg/cmd/version.go
Normal file
35
pkg/cmd/version.go
Normal file
@@ -0,0 +1,35 @@
|
||||
// Vikunja is a todo-list application to facilitate your life.
|
||||
// Copyright 2019 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/version"
|
||||
"fmt"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(versionCmd)
|
||||
}
|
||||
|
||||
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)
|
||||
},
|
||||
}
|
||||
69
pkg/cmd/web.go
Normal file
69
pkg/cmd/web.go
Normal file
@@ -0,0 +1,69 @@
|
||||
// Vikunja is a todo-list application to facilitate your life.
|
||||
// Copyright 2019 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"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() {
|
||||
rootCmd.AddCommand(webCmd)
|
||||
}
|
||||
|
||||
var webCmd = &cobra.Command{
|
||||
Use: "web",
|
||||
Short: "Starts the rest api web server",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
|
||||
// Version notification
|
||||
log.Infof("Vikunja version %s", version.Version)
|
||||
|
||||
// Additional swagger information
|
||||
swagger.SwaggerInfo.Version = version.Version
|
||||
|
||||
// Start the webserver
|
||||
e := routes.NewEcho()
|
||||
routes.RegisterRoutes(e)
|
||||
// Start server
|
||||
go func() {
|
||||
if err := e.Start(config.ServiceInterface.GetString()); err != nil {
|
||||
e.Logger.Info("shutting down...")
|
||||
}
|
||||
}()
|
||||
|
||||
// Wait for interrupt signal to gracefully shutdown the server with
|
||||
// a timeout of 10 seconds.
|
||||
quit := make(chan os.Signal, 1)
|
||||
signal.Notify(quit, os.Interrupt)
|
||||
<-quit
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
log.Infof("Shutting down...")
|
||||
if err := e.Shutdown(ctx); err != nil {
|
||||
e.Logger.Fatal(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -17,66 +17,194 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"github.com/spf13/viper"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// InitConfig initializes the config, sets defaults etc.
|
||||
func init() {
|
||||
// Key is used as a config key
|
||||
type Key string
|
||||
|
||||
// Set defaults
|
||||
// These constants hold all config value keys
|
||||
const (
|
||||
ServiceJWTSecret Key = `service.JWTSecret`
|
||||
ServiceInterface Key = `service.interface`
|
||||
ServiceFrontendurl Key = `service.frontendurl`
|
||||
ServiceEnableCaldav Key = `service.enablecaldav`
|
||||
ServiceRootpath Key = `service.rootpath`
|
||||
ServiceMaxItemsPerPage Key = `service.maxitemsperpage`
|
||||
ServiceEnableMetrics Key = `service.enablemetrics`
|
||||
ServiceMotd Key = `service.motd`
|
||||
ServiceEnableLinkSharing Key = `service.enablelinksharing`
|
||||
|
||||
DatabaseType Key = `database.type`
|
||||
DatabaseHost Key = `database.host`
|
||||
DatabaseUser Key = `database.user`
|
||||
DatabasePassword Key = `database.password`
|
||||
DatabaseDatabase Key = `database.database`
|
||||
DatabasePath Key = `database.path`
|
||||
DatabaseMaxOpenConnections Key = `database.maxopenconnections`
|
||||
DatabaseMaxIdleConnections Key = `database.maxidleconnections`
|
||||
DatabaseMaxConnectionLifetime Key = `database.maxconnectionlifetime`
|
||||
|
||||
CacheEnabled Key = `cache.enabled`
|
||||
CacheType Key = `cache.type`
|
||||
CacheMaxElementSize Key = `cache.maxelementsize`
|
||||
|
||||
MailerEnabled Key = `mailer.enabled`
|
||||
MailerHost Key = `mailer.host`
|
||||
MailerPort Key = `mailer.port`
|
||||
MailerUsername Key = `mailer.username`
|
||||
MailerPassword Key = `mailer.password`
|
||||
MailerSkipTLSVerify Key = `mailer.skiptlsverify`
|
||||
MailerFromEmail Key = `mailer.fromemail`
|
||||
MailerQueuelength Key = `mailer.queuelength`
|
||||
MailerQueueTimeout Key = `mailer.queuetimeout`
|
||||
|
||||
RedisEnabled Key = `redis.enabled`
|
||||
RedisHost Key = `redis.host`
|
||||
RedisPassword Key = `redis.password`
|
||||
RedisDB Key = `redis.db`
|
||||
|
||||
LogEnabled Key = `log.enabled`
|
||||
LogErrors Key = `log.errors`
|
||||
LogStandard Key = `log.standard`
|
||||
LogDatabase Key = `log.database`
|
||||
LogHTTP Key = `log.http`
|
||||
LogEcho Key = `log.echo`
|
||||
LogPath Key = `log.path`
|
||||
|
||||
RateLimitEnabled Key = `ratelimit.enabled`
|
||||
RateLimitKind Key = `ratelimit.kind`
|
||||
RateLimitPeriod Key = `ratelimit.period`
|
||||
RateLimitLimit Key = `ratelimit.limit`
|
||||
RateLimitStore Key = `ratelimit.store`
|
||||
|
||||
FilesBasePath Key = `files.basepath`
|
||||
FilesMaxSize Key = `files.maxsize`
|
||||
)
|
||||
|
||||
// GetString returns a string config value
|
||||
func (k Key) GetString() string {
|
||||
return viper.GetString(string(k))
|
||||
}
|
||||
|
||||
// GetBool returns a bool config value
|
||||
func (k Key) GetBool() bool {
|
||||
return viper.GetBool(string(k))
|
||||
}
|
||||
|
||||
// GetInt returns an int config value
|
||||
func (k Key) GetInt() int {
|
||||
return viper.GetInt(string(k))
|
||||
}
|
||||
|
||||
// GetInt64 returns an int64 config value
|
||||
func (k Key) GetInt64() int64 {
|
||||
return viper.GetInt64(string(k))
|
||||
}
|
||||
|
||||
// GetDuration returns a duration config value
|
||||
func (k Key) GetDuration() time.Duration {
|
||||
return viper.GetDuration(string(k))
|
||||
}
|
||||
|
||||
// Set sets a value
|
||||
func (k Key) Set(i interface{}) {
|
||||
viper.Set(string(k), i)
|
||||
}
|
||||
|
||||
// sets the default config value
|
||||
func (k Key) setDefault(i interface{}) {
|
||||
viper.SetDefault(string(k), i)
|
||||
}
|
||||
|
||||
// InitDefaultConfig sets default config values
|
||||
// This is an extra function so we can call it when initializing tests without initializing the full config
|
||||
func InitDefaultConfig() {
|
||||
// Service config
|
||||
random, err := random(32)
|
||||
if err != nil {
|
||||
log.Log.Fatal(err.Error())
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
|
||||
// Service
|
||||
viper.SetDefault("service.JWTSecret", random)
|
||||
viper.SetDefault("service.interface", ":3456")
|
||||
viper.SetDefault("service.frontendurl", "")
|
||||
ServiceJWTSecret.setDefault(random)
|
||||
ServiceInterface.setDefault(":3456")
|
||||
ServiceFrontendurl.setDefault("")
|
||||
ServiceEnableCaldav.setDefault(true)
|
||||
|
||||
ex, err := os.Executable()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
exPath := filepath.Dir(ex)
|
||||
viper.SetDefault("service.rootpath", exPath)
|
||||
viper.SetDefault("service.pagecount", 50)
|
||||
viper.SetDefault("service.enablemetrics", false)
|
||||
ServiceRootpath.setDefault(exPath)
|
||||
ServiceMaxItemsPerPage.setDefault(50)
|
||||
ServiceEnableMetrics.setDefault(false)
|
||||
ServiceMotd.setDefault("")
|
||||
ServiceEnableLinkSharing.setDefault(true)
|
||||
|
||||
// Database
|
||||
viper.SetDefault("database.type", "sqlite")
|
||||
viper.SetDefault("database.host", "localhost")
|
||||
viper.SetDefault("database.user", "vikunja")
|
||||
viper.SetDefault("database.password", "")
|
||||
viper.SetDefault("database.database", "vikunja")
|
||||
viper.SetDefault("database.path", "./vikunja.db")
|
||||
viper.SetDefault("database.showqueries", false)
|
||||
viper.SetDefault("database.openconnections", 100)
|
||||
DatabaseType.setDefault("sqlite")
|
||||
DatabaseHost.setDefault("localhost")
|
||||
DatabaseUser.setDefault("vikunja")
|
||||
DatabasePassword.setDefault("")
|
||||
DatabaseDatabase.setDefault("vikunja")
|
||||
DatabasePath.setDefault("./vikunja.db")
|
||||
DatabaseMaxOpenConnections.setDefault(100)
|
||||
DatabaseMaxIdleConnections.setDefault(50)
|
||||
DatabaseMaxConnectionLifetime.setDefault(10000)
|
||||
|
||||
// Cacher
|
||||
viper.SetDefault("cache.enabled", false)
|
||||
viper.SetDefault("cache.type", "memory")
|
||||
viper.SetDefault("cache.maxelementsize", 1000)
|
||||
CacheEnabled.setDefault(false)
|
||||
CacheType.setDefault("memory")
|
||||
CacheMaxElementSize.setDefault(1000)
|
||||
// Mailer
|
||||
viper.SetDefault("mailer.enabled", false)
|
||||
viper.SetDefault("mailer.host", "")
|
||||
viper.SetDefault("mailer.port", "587")
|
||||
viper.SetDefault("mailer.user", "user")
|
||||
viper.SetDefault("mailer.password", "")
|
||||
viper.SetDefault("mailer.skiptlsverify", false)
|
||||
viper.SetDefault("mailer.fromemail", "mail@vikunja")
|
||||
viper.SetDefault("mailer.queuelength", 100)
|
||||
viper.SetDefault("mailer.queuetimeout", 30)
|
||||
MailerEnabled.setDefault(false)
|
||||
MailerHost.setDefault("")
|
||||
MailerPort.setDefault("587")
|
||||
MailerUsername.setDefault("user")
|
||||
MailerPassword.setDefault("")
|
||||
MailerSkipTLSVerify.setDefault(false)
|
||||
MailerFromEmail.setDefault("mail@vikunja")
|
||||
MailerQueuelength.setDefault(100)
|
||||
MailerQueueTimeout.setDefault(30)
|
||||
// Redis
|
||||
viper.SetDefault("redis.enabled", false)
|
||||
viper.SetDefault("redis.host", "localhost:6379")
|
||||
viper.SetDefault("redis.password", "")
|
||||
viper.SetDefault("redis.db", 0)
|
||||
RedisEnabled.setDefault(false)
|
||||
RedisHost.setDefault("localhost:6379")
|
||||
RedisPassword.setDefault("")
|
||||
RedisDB.setDefault(0)
|
||||
// Logger
|
||||
LogEnabled.setDefault(true)
|
||||
LogErrors.setDefault("stdout")
|
||||
LogStandard.setDefault("stdout")
|
||||
LogDatabase.setDefault("off")
|
||||
LogHTTP.setDefault("stdout")
|
||||
LogEcho.setDefault("off")
|
||||
LogPath.setDefault(ServiceRootpath.GetString() + "/logs")
|
||||
// Rate Limit
|
||||
RateLimitEnabled.setDefault(false)
|
||||
RateLimitKind.setDefault("user")
|
||||
RateLimitLimit.setDefault(100)
|
||||
RateLimitPeriod.setDefault(60)
|
||||
RateLimitStore.setDefault("memory")
|
||||
// Files
|
||||
FilesBasePath.setDefault("files")
|
||||
FilesMaxSize.setDefault("20MB")
|
||||
}
|
||||
|
||||
// InitConfig initializes the config, sets defaults etc.
|
||||
func InitConfig() {
|
||||
|
||||
// Set defaults
|
||||
InitDefaultConfig()
|
||||
|
||||
// Init checking for environment variables
|
||||
viper.SetEnvPrefix("vikunja")
|
||||
@@ -84,14 +212,15 @@ func init() {
|
||||
viper.AutomaticEnv()
|
||||
|
||||
// Load the config file
|
||||
viper.AddConfigPath(exPath)
|
||||
viper.AddConfigPath(viper.GetString("service.rootpath"))
|
||||
viper.AddConfigPath(ServiceRootpath.GetString())
|
||||
viper.AddConfigPath("/etc/vikunja/")
|
||||
viper.AddConfigPath("~/.config/vikunja")
|
||||
viper.AddConfigPath(".")
|
||||
viper.SetConfigName("config")
|
||||
err = viper.ReadInConfig()
|
||||
err := viper.ReadInConfig()
|
||||
if err != nil {
|
||||
log.Log.Info(err)
|
||||
log.Log.Info("Using defaults.")
|
||||
log.Println(err.Error())
|
||||
log.Println("Using defaults.")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
148
pkg/db/db.go
Normal file
148
pkg/db/db.go
Normal file
@@ -0,0 +1,148 @@
|
||||
// Vikunja is a todo-list application to facilitate your life.
|
||||
// Copyright 2019 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"github.com/go-xorm/core"
|
||||
"github.com/go-xorm/xorm"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
xrc "github.com/go-xorm/xorm-redis-cache"
|
||||
|
||||
_ "github.com/go-sql-driver/mysql" // Because.
|
||||
_ "github.com/mattn/go-sqlite3" // Because.
|
||||
)
|
||||
|
||||
// We only want one instance of the engine, so we can reate it once and reuse it
|
||||
var x *xorm.Engine
|
||||
|
||||
// CreateDBEngine initializes a db engine from the config
|
||||
func CreateDBEngine() (engine *xorm.Engine, err error) {
|
||||
|
||||
if x != nil {
|
||||
return x, nil
|
||||
}
|
||||
|
||||
// If the database type is not set, this likely means we need to initialize the config first
|
||||
if config.DatabaseType.GetString() == "" {
|
||||
config.InitConfig()
|
||||
}
|
||||
|
||||
// Use Mysql if set
|
||||
if config.DatabaseType.GetString() == "mysql" {
|
||||
engine, err = initMysqlEngine()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// Otherwise use sqlite
|
||||
engine, err = initSqliteEngine()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
engine.SetMapper(core.GonicMapper{})
|
||||
engine.ShowSQL(config.LogDatabase.GetString() != "off")
|
||||
engine.SetLogger(xorm.NewSimpleLogger(log.GetLogWriter("database")))
|
||||
|
||||
// Cache
|
||||
// We have to initialize the cache here to avoid import cycles
|
||||
if config.CacheEnabled.GetBool() {
|
||||
switch config.CacheType.GetString() {
|
||||
case "memory":
|
||||
cacher := xorm.NewLRUCacher(xorm.NewMemoryStore(), config.CacheMaxElementSize.GetInt())
|
||||
engine.SetDefaultCacher(cacher)
|
||||
case "redis":
|
||||
cacher := xrc.NewRedisCacher(config.RedisEnabled.GetString(), config.RedisPassword.GetString(), xrc.DEFAULT_EXPIRATION, engine.Logger())
|
||||
engine.SetDefaultCacher(cacher)
|
||||
default:
|
||||
log.Info("Did not find a valid cache type. Caching disabled. Please refer to the docs for poosible cache types.")
|
||||
}
|
||||
}
|
||||
|
||||
x = engine
|
||||
return
|
||||
}
|
||||
|
||||
// CreateTestEngine creates an instance of the db engine which lives in memory
|
||||
func CreateTestEngine() (engine *xorm.Engine, err error) {
|
||||
|
||||
if x != nil {
|
||||
return x, nil
|
||||
}
|
||||
|
||||
if os.Getenv("VIKUNJA_TESTS_USE_CONFIG") == "1" {
|
||||
config.InitConfig()
|
||||
engine, err = CreateDBEngine()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
engine, err = xorm.NewEngine("sqlite3", "file::memory:?cache=shared")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
engine.SetMapper(core.GonicMapper{})
|
||||
engine.ShowSQL(os.Getenv("UNIT_TESTS_VERBOSE") == "1")
|
||||
engine.SetLogger(xorm.NewSimpleLogger(log.GetLogWriter("database")))
|
||||
x = engine
|
||||
return
|
||||
}
|
||||
|
||||
// RegisterTableStructsForCache registers tables in gob encoding for redis cache
|
||||
func RegisterTableStructsForCache(val interface{}) {
|
||||
gob.Register(val)
|
||||
}
|
||||
|
||||
func initMysqlEngine() (engine *xorm.Engine, err error) {
|
||||
connStr := fmt.Sprintf(
|
||||
"%s:%s@tcp(%s)/%s?charset=utf8&parseTime=true",
|
||||
config.DatabaseUser.GetString(),
|
||||
config.DatabasePassword.GetString(),
|
||||
config.DatabaseHost.GetString(),
|
||||
config.DatabaseDatabase.GetString())
|
||||
engine, err = xorm.NewEngine("mysql", connStr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
engine.SetMaxOpenConns(config.DatabaseMaxOpenConnections.GetInt())
|
||||
engine.SetMaxIdleConns(config.DatabaseMaxIdleConnections.GetInt())
|
||||
max, err := time.ParseDuration(strconv.Itoa(config.DatabaseMaxConnectionLifetime.GetInt()) + `ms`)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
engine.SetConnMaxLifetime(max)
|
||||
return
|
||||
}
|
||||
|
||||
func initSqliteEngine() (engine *xorm.Engine, err error) {
|
||||
path := config.DatabasePath.GetString()
|
||||
if path == "" {
|
||||
path = "./db.db"
|
||||
}
|
||||
|
||||
return xorm.NewEngine("sqlite3", path)
|
||||
}
|
||||
36
pkg/db/test_fixtures.go
Normal file
36
pkg/db/test_fixtures.go
Normal file
@@ -0,0 +1,36 @@
|
||||
// Copyright 2019 Vikunja and contriubtors. All rights reserved.
|
||||
//
|
||||
// This file is part of Vikunja.
|
||||
//
|
||||
// Vikunja is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Vikunja is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Vikunja. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"gopkg.in/testfixtures.v2"
|
||||
)
|
||||
|
||||
var fixtures *testfixtures.Context
|
||||
|
||||
// InitFixtures initialize test fixtures for a test database
|
||||
func InitFixtures(helper testfixtures.Helper, dir string) (err error) {
|
||||
testfixtures.SkipDatabaseNameCheck(true)
|
||||
fixtures, err = testfixtures.NewFolder(x.DB().DB, helper, dir)
|
||||
return err
|
||||
}
|
||||
|
||||
// LoadFixtures load fixtures for a test database
|
||||
func LoadFixtures() error {
|
||||
return fixtures.Load()
|
||||
}
|
||||
49
pkg/files/db.go
Normal file
49
pkg/files/db.go
Normal file
@@ -0,0 +1,49 @@
|
||||
// Vikunja is a todo-list application to facilitate your life.
|
||||
// Copyright 2019 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package files
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/db"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"github.com/go-xorm/xorm"
|
||||
)
|
||||
|
||||
var x *xorm.Engine
|
||||
|
||||
// SetEngine sets the xorm.Engine
|
||||
func SetEngine() (err error) {
|
||||
x, err = db.CreateDBEngine()
|
||||
if err != nil {
|
||||
log.Criticalf("Could not connect to db: %v", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Cache
|
||||
if config.CacheEnabled.GetBool() && config.CacheType.GetString() == "redis" {
|
||||
db.RegisterTableStructsForCache(GetTables())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetTables returns all structs which are also a table.
|
||||
func GetTables() []interface{} {
|
||||
return []interface{}{
|
||||
&File{},
|
||||
}
|
||||
}
|
||||
52
pkg/files/error.go
Normal file
52
pkg/files/error.go
Normal file
@@ -0,0 +1,52 @@
|
||||
// Copyright 2019 Vikunja and contriubtors. All rights reserved.
|
||||
//
|
||||
// This file is part of Vikunja.
|
||||
//
|
||||
// Vikunja is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Vikunja is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Vikunja. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package files
|
||||
|
||||
import "fmt"
|
||||
|
||||
// ErrFileDoesNotExist defines an error where a file does not exist in the db
|
||||
type ErrFileDoesNotExist struct {
|
||||
FileID int64
|
||||
}
|
||||
|
||||
// Error is the error implementation of ErrFileDoesNotExist
|
||||
func (err ErrFileDoesNotExist) Error() string {
|
||||
return fmt.Sprintf("file %d does not exist", err.FileID)
|
||||
}
|
||||
|
||||
//IsErrFileDoesNotExist checks if an error is ErrFileDoesNotExist
|
||||
func IsErrFileDoesNotExist(err error) bool {
|
||||
_, ok := err.(ErrFileDoesNotExist)
|
||||
return ok
|
||||
}
|
||||
|
||||
// ErrFileIsTooLarge defines an error where a file is larger than the configured limit
|
||||
type ErrFileIsTooLarge struct {
|
||||
Size uint64
|
||||
}
|
||||
|
||||
// Error is the error implementation of ErrFileIsTooLarge
|
||||
func (err ErrFileIsTooLarge) Error() string {
|
||||
return fmt.Sprintf("file is too large [Size: %d]", err.Size)
|
||||
}
|
||||
|
||||
//IsErrFileIsTooLarge checks if an error is ErrFileIsTooLarge
|
||||
func IsErrFileIsTooLarge(err error) bool {
|
||||
_, ok := err.(ErrFileIsTooLarge)
|
||||
return ok
|
||||
}
|
||||
97
pkg/files/filehandling.go
Normal file
97
pkg/files/filehandling.go
Normal file
@@ -0,0 +1,97 @@
|
||||
// Vikunja is a todo-list application to facilitate your life.
|
||||
// Copyright 2019 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package files
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/db"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gopkg.in/testfixtures.v2"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// This file handles storing and retrieving a file for different backends
|
||||
var fs afero.Fs
|
||||
var afs *afero.Afero
|
||||
|
||||
// InitFileHandler creates a new file handler for the file backend we want to use
|
||||
func InitFileHandler() {
|
||||
fs = afero.NewOsFs()
|
||||
afs = &afero.Afero{Fs: fs}
|
||||
}
|
||||
|
||||
// InitTestFileHandler initializes a new memory file system for testing
|
||||
func InitTestFileHandler() {
|
||||
fs = afero.NewMemMapFs()
|
||||
afs = &afero.Afero{Fs: fs}
|
||||
}
|
||||
|
||||
func initFixtures(t *testing.T) {
|
||||
// Init db fixtures
|
||||
err := db.LoadFixtures()
|
||||
assert.NoError(t, err)
|
||||
|
||||
InitTestFileFixtures(t)
|
||||
}
|
||||
|
||||
//InitTestFileFixtures initializes file fixtures
|
||||
func InitTestFileFixtures(t *testing.T) {
|
||||
// Init fixture files
|
||||
filename := config.FilesBasePath.GetString() + "/1"
|
||||
err := afero.WriteFile(afs, filename, []byte("testfile1"), 0644)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
// InitTests handles the actual bootstrapping of the test env
|
||||
func InitTests() {
|
||||
var err error
|
||||
x, err = db.CreateTestEngine()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = x.Sync2(GetTables()...)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
config.InitDefaultConfig()
|
||||
// We need to set the root path even if we're not using the config, otherwise fixtures are not loaded correctly
|
||||
config.ServiceRootpath.Set(os.Getenv("VIKUNJA_SERVICE_ROOTPATH"))
|
||||
|
||||
// Sync fixtures
|
||||
var fixturesHelper testfixtures.Helper = &testfixtures.SQLite{}
|
||||
if config.DatabaseType.GetString() == "mysql" {
|
||||
fixturesHelper = &testfixtures.MySQL{}
|
||||
}
|
||||
fixturesDir := filepath.Join(config.ServiceRootpath.GetString(), "pkg", "files", "fixtures")
|
||||
err = db.InitFixtures(fixturesHelper, fixturesDir)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
InitTestFileHandler()
|
||||
}
|
||||
|
||||
// FileStat stats a file. This is an exported function to be able to test this from outide of the package
|
||||
func FileStat(filename string) (os.FileInfo, error) {
|
||||
return afs.Stat(filename)
|
||||
}
|
||||
111
pkg/files/files.go
Normal file
111
pkg/files/files.go
Normal file
@@ -0,0 +1,111 @@
|
||||
// Vikunja is a todo-list application to facilitate your life.
|
||||
// Copyright 2019 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package files
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/web"
|
||||
"github.com/c2h5oh/datasize"
|
||||
"github.com/spf13/afero"
|
||||
"io"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// File holds all information about a file
|
||||
type File struct {
|
||||
ID int64 `xorm:"int(11) autoincr not null unique pk" json:"id"`
|
||||
Name string `xorm:"text not null" json:"name"`
|
||||
Mime string `xorm:"text null" json:"mime"`
|
||||
Size uint64 `xorm:"int(11) not null" json:"size"`
|
||||
|
||||
Created time.Time `xorm:"-" json:"created"`
|
||||
|
||||
CreatedUnix int64 `xorm:"created" json:"-"`
|
||||
CreatedByID int64 `xorm:"int(11) not null" json:"-"`
|
||||
|
||||
File afero.File `xorm:"-" json:"-"`
|
||||
}
|
||||
|
||||
// TableName is the table name for the files table
|
||||
func (File) TableName() string {
|
||||
return "files"
|
||||
}
|
||||
|
||||
func (f *File) getFileName() string {
|
||||
return config.FilesBasePath.GetString() + "/" + strconv.FormatInt(f.ID, 10)
|
||||
}
|
||||
|
||||
// LoadFileByID returns a file by its ID
|
||||
func (f *File) LoadFileByID() (err error) {
|
||||
f.File, err = afs.Open(f.getFileName())
|
||||
return
|
||||
}
|
||||
|
||||
// LoadFileMetaByID loads everything about a file without loading the actual file
|
||||
func (f *File) LoadFileMetaByID() (err error) {
|
||||
exists, err := x.Where("id = ?", f.ID).Get(f)
|
||||
if !exists {
|
||||
return ErrFileDoesNotExist{FileID: f.ID}
|
||||
}
|
||||
f.Created = time.Unix(f.CreatedUnix, 0)
|
||||
return
|
||||
}
|
||||
|
||||
// Create creates a new file from an FileHeader
|
||||
func Create(f io.ReadCloser, realname string, realsize uint64, a web.Auth) (file *File, err error) {
|
||||
|
||||
// Get and parse the configured file size
|
||||
var maxSize datasize.ByteSize
|
||||
err = maxSize.UnmarshalText([]byte(config.FilesMaxSize.GetString()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if realsize > maxSize.Bytes() {
|
||||
return nil, ErrFileIsTooLarge{Size: realsize}
|
||||
}
|
||||
|
||||
// We first insert the file into the db to get it's ID
|
||||
file = &File{
|
||||
Name: realname,
|
||||
Size: realsize,
|
||||
CreatedByID: a.GetID(),
|
||||
}
|
||||
|
||||
_, err = x.Insert(file)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Save the file to storage with its new ID as path
|
||||
err = afs.WriteReader(file.getFileName(), f)
|
||||
return
|
||||
}
|
||||
|
||||
// Delete removes a file from the DB and the file system
|
||||
func (f *File) Delete() (err error) {
|
||||
deleted, err := x.Where("id = ?", f.ID).Delete(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if deleted == 0 {
|
||||
return ErrFileDoesNotExist{FileID: f.ID}
|
||||
}
|
||||
|
||||
err = afs.Remove(f.getFileName())
|
||||
return
|
||||
}
|
||||
131
pkg/files/files_test.go
Normal file
131
pkg/files/files_test.go
Normal file
@@ -0,0 +1,131 @@
|
||||
// Copyright 2019 Vikunja and contriubtors. All rights reserved.
|
||||
//
|
||||
// This file is part of Vikunja.
|
||||
//
|
||||
// Vikunja is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Vikunja is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Vikunja. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package files
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"io"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type testfile struct {
|
||||
content []byte
|
||||
done bool
|
||||
}
|
||||
|
||||
func (t *testfile) Read(p []byte) (n int, err error) {
|
||||
if t.done {
|
||||
return 0, io.EOF
|
||||
}
|
||||
copy(p, t.content)
|
||||
t.done = true
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (t *testfile) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type testauth struct {
|
||||
id int64
|
||||
}
|
||||
|
||||
func (a *testauth) GetID() int64 {
|
||||
return a.id
|
||||
}
|
||||
|
||||
func TestCreate(t *testing.T) {
|
||||
t.Run("Normal", func(t *testing.T) {
|
||||
initFixtures(t)
|
||||
tf := &testfile{
|
||||
content: []byte("testfile"),
|
||||
}
|
||||
ta := &testauth{id: 1}
|
||||
_, err := Create(tf, "testfile", 100, ta)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Check the file was created correctly
|
||||
file := &File{ID: 2}
|
||||
err = file.LoadFileMetaByID()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(1), file.CreatedByID)
|
||||
assert.Equal(t, "testfile", file.Name)
|
||||
assert.Equal(t, uint64(100), file.Size)
|
||||
|
||||
})
|
||||
t.Run("Too Large", func(t *testing.T) {
|
||||
initFixtures(t)
|
||||
tf := &testfile{
|
||||
content: []byte("testfile"),
|
||||
}
|
||||
ta := &testauth{id: 1}
|
||||
_, err := Create(tf, "testfile", 99999999999, ta)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, IsErrFileIsTooLarge(err))
|
||||
})
|
||||
}
|
||||
|
||||
func TestFile_Delete(t *testing.T) {
|
||||
t.Run("Normal", func(t *testing.T) {
|
||||
initFixtures(t)
|
||||
f := &File{ID: 1}
|
||||
err := f.Delete()
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
t.Run("Nonexisting", func(t *testing.T) {
|
||||
initFixtures(t)
|
||||
f := &File{ID: 9999}
|
||||
err := f.Delete()
|
||||
assert.Error(t, err)
|
||||
assert.True(t, IsErrFileDoesNotExist(err))
|
||||
})
|
||||
}
|
||||
|
||||
func TestFile_LoadFileByID(t *testing.T) {
|
||||
t.Run("Normal", func(t *testing.T) {
|
||||
initFixtures(t)
|
||||
f := &File{ID: 1}
|
||||
err := f.LoadFileByID()
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
t.Run("Nonexisting", func(t *testing.T) {
|
||||
initFixtures(t)
|
||||
f := &File{ID: 9999}
|
||||
err := f.LoadFileByID()
|
||||
assert.Error(t, err)
|
||||
assert.True(t, os.IsNotExist(err))
|
||||
})
|
||||
}
|
||||
|
||||
func TestFile_LoadFileMetaByID(t *testing.T) {
|
||||
t.Run("Normal", func(t *testing.T) {
|
||||
initFixtures(t)
|
||||
f := &File{ID: 1}
|
||||
err := f.LoadFileMetaByID()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "test", f.Name)
|
||||
})
|
||||
t.Run("Nonexisting", func(t *testing.T) {
|
||||
initFixtures(t)
|
||||
f := &File{ID: 9999}
|
||||
err := f.LoadFileMetaByID()
|
||||
assert.Error(t, err)
|
||||
assert.True(t, IsErrFileDoesNotExist(err))
|
||||
})
|
||||
}
|
||||
5
pkg/files/fixtures/files.yml
Normal file
5
pkg/files/fixtures/files.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
- id: 1
|
||||
name: test
|
||||
size: 100
|
||||
created_unix: 1570998791
|
||||
created_by_id: 1
|
||||
29
pkg/files/main_test.go
Normal file
29
pkg/files/main_test.go
Normal file
@@ -0,0 +1,29 @@
|
||||
// Copyright 2019 Vikunja and contriubtors. All rights reserved.
|
||||
//
|
||||
// This file is part of Vikunja.
|
||||
//
|
||||
// Vikunja is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Vikunja is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Vikunja. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package files
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestMain is the main test function used to bootstrap the test env
|
||||
func TestMain(m *testing.M) {
|
||||
InitTests()
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
350
pkg/integrations/_test.go.tpl
Normal file
350
pkg/integrations/_test.go.tpl
Normal file
@@ -0,0 +1,350 @@
|
||||
// Vikunja is a todo-list application to facilitate your life.
|
||||
// Copyright 2019 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package integrations
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/web/handler"
|
||||
"github.com/labstack/echo"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/url"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test${MODEL}(t *testing.T) {
|
||||
testHandler := webHandlerTest{
|
||||
user: &testuser1,
|
||||
strFunc: func() handler.CObject {
|
||||
return &models.${MODEL}{}
|
||||
},
|
||||
t: t,
|
||||
}
|
||||
t.Run("ReadAll", func(t *testing.T) {
|
||||
t.Run("Normal", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAll(nil, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), ``)
|
||||
assert.NotContains(t, rec.Body.String(), ``)
|
||||
})
|
||||
t.Run("Search", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAll(url.Values{"s": []string{""}}, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), ``)
|
||||
assert.NotContains(t, rec.Body.String(), ``)
|
||||
})
|
||||
})
|
||||
t.Run("ReadOne", func(t *testing.T) {
|
||||
t.Run("Normal", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadOne(nil, map[string]string{"${URL_PLACEHOLDER}": ""})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), ``)
|
||||
assert.NotContains(t, rec.Body.String(), ``)
|
||||
})
|
||||
t.Run("Nonexisting", func(t *testing.T) {
|
||||
_, err := testHandler.testReadOne(nil, map[string]string{"${URL_PLACEHOLDER}": ""})
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCode)
|
||||
})
|
||||
t.Run("Rights check", func(t *testing.T) {
|
||||
t.Run("Forbidden", func(t *testing.T) {
|
||||
// Owned by user3
|
||||
_, err := testHandler.testReadOne(nil, map[string]string{"${URL_PLACEHOLDER}": ""})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `You don't have the right to see this`)
|
||||
})
|
||||
t.Run("Shared Via Team readonly", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadOne(nil, map[string]string{"${URL_PLACEHOLDER}": ""})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), ``)
|
||||
})
|
||||
t.Run("Shared Via Team write", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadOne(nil, map[string]string{"${URL_PLACEHOLDER}": ""})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), ``)
|
||||
})
|
||||
t.Run("Shared Via Team admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadOne(nil, map[string]string{"${URL_PLACEHOLDER}": ""})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), ``)
|
||||
})
|
||||
|
||||
t.Run("Shared Via User readonly", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadOne(nil, map[string]string{"${URL_PLACEHOLDER}": ""})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), ``)
|
||||
})
|
||||
t.Run("Shared Via User write", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadOne(nil, map[string]string{"${URL_PLACEHOLDER}": ""})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), ``)
|
||||
})
|
||||
t.Run("Shared Via User admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadOne(nil, map[string]string{"${URL_PLACEHOLDER}": ""})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), ``)
|
||||
})
|
||||
|
||||
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadOne(nil, map[string]string{"${URL_PLACEHOLDER}": ""})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), ``)
|
||||
})
|
||||
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadOne(nil, map[string]string{"${URL_PLACEHOLDER}": ""})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), ``)
|
||||
})
|
||||
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadOne(nil, map[string]string{"${URL_PLACEHOLDER}": ""})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), ``)
|
||||
})
|
||||
|
||||
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadOne(nil, map[string]string{"${URL_PLACEHOLDER}": ""})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), ``)
|
||||
})
|
||||
t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadOne(nil, map[string]string{"${URL_PLACEHOLDER}": ""})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), ``)
|
||||
})
|
||||
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadOne(nil, map[string]string{"${URL_PLACEHOLDER}": ""})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), ``)
|
||||
})
|
||||
})
|
||||
})
|
||||
t.Run("Update", func(t *testing.T) {
|
||||
t.Run("Normal", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdate(nil, map[string]string{"${URL_PLACEHOLDER}": ""}, `{}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), ``)
|
||||
})
|
||||
t.Run("Nonexisting", func(t *testing.T) {
|
||||
_, err := testHandler.testUpdate(nil, map[string]string{"${URL_PLACEHOLDER}": ""}, `{}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCode)
|
||||
})
|
||||
t.Run("Rights check", func(t *testing.T) {
|
||||
t.Run("Forbidden", func(t *testing.T) {
|
||||
_, err := testHandler.testUpdate(nil, map[string]string{"${URL_PLACEHOLDER}": ""}, `{}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via Team readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testUpdate(nil, map[string]string{"${URL_PLACEHOLDER}": ""}, `{}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via Team write", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdate(nil, map[string]string{"${URL_PLACEHOLDER}": ""}, `{}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), ``)
|
||||
})
|
||||
t.Run("Shared Via Team admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdate(nil, map[string]string{"${URL_PLACEHOLDER}": ""}, `{}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), ``)
|
||||
})
|
||||
|
||||
t.Run("Shared Via User readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testUpdate(nil, map[string]string{"${URL_PLACEHOLDER}": ""}, `{}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via User write", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdate(nil, map[string]string{"${URL_PLACEHOLDER}": ""}, `{}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), ``)
|
||||
})
|
||||
t.Run("Shared Via User admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdate(nil, map[string]string{"${URL_PLACEHOLDER}": ""}, `{}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), ``)
|
||||
})
|
||||
|
||||
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testUpdate(nil, map[string]string{"${URL_PLACEHOLDER}": ""}, `{}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdate(nil, map[string]string{"${URL_PLACEHOLDER}": ""}, `{}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), ``)
|
||||
})
|
||||
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdate(nil, map[string]string{"${URL_PLACEHOLDER}": ""}, `{}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), ``)
|
||||
})
|
||||
|
||||
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testUpdate(nil, map[string]string{"${URL_PLACEHOLDER}": ""}, `{}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdate(nil, map[string]string{"${URL_PLACEHOLDER}": ""}, `{}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), ``)
|
||||
})
|
||||
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdate(nil, map[string]string{"${URL_PLACEHOLDER}": ""}, `{}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), ``)
|
||||
})
|
||||
})
|
||||
})
|
||||
t.Run("Delete", func(t *testing.T) {
|
||||
t.Run("Normal", func(t *testing.T) {
|
||||
rec, err := testHandler.testDelete(nil, map[string]string{"${URL_PLACEHOLDER}": ""})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), ``)
|
||||
})
|
||||
t.Run("Nonexisting", func(t *testing.T) {
|
||||
_, err := testHandler.testDelete(nil, map[string]string{"${URL_PLACEHOLDER}": ""})
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCode)
|
||||
})
|
||||
t.Run("Rights check", func(t *testing.T) {
|
||||
t.Run("Forbidden", func(t *testing.T) {
|
||||
_, err := testHandler.testDelete(nil, map[string]string{"${URL_PLACEHOLDER}": ""})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via Team readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testDelete(nil, map[string]string{"${URL_PLACEHOLDER}": ""})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via Team write", func(t *testing.T) {
|
||||
_, err := testHandler.testDelete(nil, map[string]string{"${URL_PLACEHOLDER}": ""})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via Team admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testDelete(nil, map[string]string{"${URL_PLACEHOLDER}": ""})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), ``)
|
||||
})
|
||||
|
||||
t.Run("Shared Via User readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testDelete(nil, map[string]string{"${URL_PLACEHOLDER}": ""})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via User write", func(t *testing.T) {
|
||||
_, err := testHandler.testDelete(nil, map[string]string{"${URL_PLACEHOLDER}": ""})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via User admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testDelete(nil, map[string]string{"${URL_PLACEHOLDER}": ""})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), ``)
|
||||
})
|
||||
|
||||
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testDelete(nil, map[string]string{"${URL_PLACEHOLDER}": ""})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
|
||||
_, err := testHandler.testDelete(nil, map[string]string{"${URL_PLACEHOLDER}": ""})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testDelete(nil, map[string]string{"${URL_PLACEHOLDER}": ""})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), ``)
|
||||
})
|
||||
|
||||
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testDelete(nil, map[string]string{"${URL_PLACEHOLDER}": ""})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
|
||||
_, err := testHandler.testDelete(nil, map[string]string{"${URL_PLACEHOLDER}": ""})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testDelete(nil, map[string]string{"${URL_PLACEHOLDER}": ""})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), ``)
|
||||
})
|
||||
})
|
||||
})
|
||||
t.Run("Create", func(t *testing.T) {
|
||||
t.Run("Normal", func(t *testing.T) {
|
||||
rec, err := testHandler.testCreate(nil, map[string]string{"${URL_PLACEHOLDER}": ""}, `{}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), ``)
|
||||
})
|
||||
t.Run("Nonexisting", func(t *testing.T) {
|
||||
_, err := testHandler.testCreate(nil, map[string]string{"${URL_PLACEHOLDER}": ""}, `{}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCode)
|
||||
})
|
||||
t.Run("Rights check", func(t *testing.T) {
|
||||
|
||||
t.Run("Forbidden", func(t *testing.T) {
|
||||
_, err := testHandler.testCreate(nil, map[string]string{"${URL_PLACEHOLDER}": ""}, `{}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
|
||||
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testCreate(nil, map[string]string{"${URL_PLACEHOLDER}": ""}, `{}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
|
||||
rec, err := testHandler.testCreate(nil, map[string]string{"${URL_PLACEHOLDER}": ""}, `{}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), ``)
|
||||
})
|
||||
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testCreate(nil, map[string]string{"${URL_PLACEHOLDER}": ""}, `{}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), ``)
|
||||
})
|
||||
|
||||
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testCreate(nil, map[string]string{"${URL_PLACEHOLDER}": ""}, `{}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
|
||||
rec, err := testHandler.testCreate(nil, map[string]string{"${URL_PLACEHOLDER}": ""}, `{}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), ``)
|
||||
})
|
||||
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testCreate(nil, map[string]string{"${URL_PLACEHOLDER}": ""}, `{}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), ``)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
250
pkg/integrations/integrations.go
Normal file
250
pkg/integrations/integrations.go
Normal file
@@ -0,0 +1,250 @@
|
||||
// Vikunja is a todo-list application to facilitate your life.
|
||||
// Copyright 2019 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package integrations
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/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/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"
|
||||
)
|
||||
|
||||
// These are the test users, the same way they are in the test database
|
||||
var (
|
||||
testuser1 = models.User{
|
||||
ID: 1,
|
||||
Username: "user1",
|
||||
Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.",
|
||||
Email: "user1@example.com",
|
||||
IsActive: true,
|
||||
}
|
||||
testuser2 = models.User{
|
||||
ID: 2,
|
||||
Username: "user2",
|
||||
Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.",
|
||||
Email: "user2@example.com",
|
||||
}
|
||||
testuser3 = models.User{
|
||||
ID: 3,
|
||||
Username: "user3",
|
||||
Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.",
|
||||
Email: "user3@example.com",
|
||||
PasswordResetToken: "passwordresettesttoken",
|
||||
}
|
||||
testuser4 = models.User{
|
||||
ID: 4,
|
||||
Username: "user4",
|
||||
Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.",
|
||||
Email: "user4@example.com",
|
||||
EmailConfirmToken: "tiepiQueed8ahc7zeeFe1eveiy4Ein8osooxegiephauph2Ael",
|
||||
}
|
||||
testuser5 = models.User{
|
||||
ID: 4,
|
||||
Username: "user5",
|
||||
Password: "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.",
|
||||
Email: "user5@example.com",
|
||||
EmailConfirmToken: "tiepiQueed8ahc7zeeFe1eveiy4Ein8osooxegiephauph2Ael",
|
||||
IsActive: false,
|
||||
}
|
||||
)
|
||||
|
||||
func setupTestEnv() (e *echo.Echo, err error) {
|
||||
config.InitDefaultConfig()
|
||||
// We need to set the root path even if we're not using the config, otherwise fixtures are not loaded correctly
|
||||
config.ServiceRootpath.Set(os.Getenv("VIKUNJA_SERVICE_ROOTPATH"))
|
||||
// Some tests use the file engine, so we'll need to initialize that
|
||||
files.InitTests()
|
||||
models.SetupTests(config.ServiceRootpath.GetString())
|
||||
|
||||
err = db.LoadFixtures()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
e = routes.NewEcho()
|
||||
routes.RegisterRoutes(e)
|
||||
return
|
||||
}
|
||||
|
||||
func bootstrapTestRequest(t *testing.T, method string, payload string, queryParam url.Values) (c echo.Context, rec *httptest.ResponseRecorder) {
|
||||
// Setup
|
||||
e, err := setupTestEnv()
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Do the actual request
|
||||
req := httptest.NewRequest(method, "/", strings.NewReader(payload))
|
||||
req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
|
||||
req.URL.RawQuery = queryParam.Encode()
|
||||
rec = httptest.NewRecorder()
|
||||
|
||||
c = e.NewContext(req, rec)
|
||||
return
|
||||
}
|
||||
|
||||
func newTestRequest(t *testing.T, method string, handler func(ctx echo.Context) error, payload string) (rec *httptest.ResponseRecorder, err error) {
|
||||
c, rec := bootstrapTestRequest(t, method, payload, nil)
|
||||
err = handler(c)
|
||||
return
|
||||
}
|
||||
|
||||
func addUserTokenToContext(t *testing.T, user *models.User, c echo.Context) {
|
||||
// Get the token as a string
|
||||
token, err := v1.NewUserJWTAuthtoken(user)
|
||||
assert.NoError(t, err)
|
||||
// We send the string token through the parsing function to get a valid jwt.Token
|
||||
tken, err := jwt.Parse(token, func(t *jwt.Token) (interface{}, error) {
|
||||
return []byte(config.ServiceJWTSecret.GetString()), nil
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
c.Set("user", tken)
|
||||
}
|
||||
|
||||
func addLinkShareTokenToContext(t *testing.T, share *models.LinkSharing, c echo.Context) {
|
||||
// Get the token as a string
|
||||
token, err := v1.NewLinkShareJWTAuthtoken(share)
|
||||
assert.NoError(t, err)
|
||||
// We send the string token through the parsing function to get a valid jwt.Token
|
||||
tken, err := jwt.Parse(token, func(t *jwt.Token) (interface{}, error) {
|
||||
return []byte(config.ServiceJWTSecret.GetString()), nil
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
c.Set("user", tken)
|
||||
}
|
||||
|
||||
func testRequestSetup(t *testing.T, method string, payload string, queryParams url.Values, urlParams map[string]string) (rec *httptest.ResponseRecorder, c echo.Context) {
|
||||
c, rec = bootstrapTestRequest(t, method, payload, queryParams)
|
||||
|
||||
var paramNames []string
|
||||
var paramValues []string
|
||||
for name, value := range urlParams {
|
||||
paramNames = append(paramNames, name)
|
||||
paramValues = append(paramValues, value)
|
||||
}
|
||||
c.SetParamNames(paramNames...)
|
||||
c.SetParamValues(paramValues...)
|
||||
return
|
||||
}
|
||||
|
||||
func newTestRequestWithUser(t *testing.T, method string, handler echo.HandlerFunc, user *models.User, payload string, queryParams url.Values, urlParams map[string]string) (rec *httptest.ResponseRecorder, err error) {
|
||||
rec, c := testRequestSetup(t, method, payload, queryParams, urlParams)
|
||||
addUserTokenToContext(t, user, c)
|
||||
err = handler(c)
|
||||
return
|
||||
}
|
||||
|
||||
func newTestRequestWithLinkShare(t *testing.T, method string, handler echo.HandlerFunc, share *models.LinkSharing, payload string, queryParams url.Values, urlParams map[string]string) (rec *httptest.ResponseRecorder, err error) {
|
||||
rec, c := testRequestSetup(t, method, payload, queryParams, urlParams)
|
||||
addLinkShareTokenToContext(t, share, c)
|
||||
err = handler(c)
|
||||
return
|
||||
}
|
||||
|
||||
func assertHandlerErrorCode(t *testing.T, err error, expectedErrorCode int) {
|
||||
if err == nil {
|
||||
t.Error("Error is nil")
|
||||
t.FailNow()
|
||||
}
|
||||
httperr, ok := err.(*echo.HTTPError)
|
||||
if !ok {
|
||||
t.Error("Error is not *echo.HTTPError")
|
||||
t.FailNow()
|
||||
}
|
||||
webhttperr, ok := httperr.Message.(web.HTTPError)
|
||||
if !ok {
|
||||
t.Error("Error is not *web.HTTPError")
|
||||
t.FailNow()
|
||||
}
|
||||
assert.Equal(t, expectedErrorCode, webhttperr.Code)
|
||||
}
|
||||
|
||||
type webHandlerTest struct {
|
||||
user *models.User
|
||||
linkShare *models.LinkSharing
|
||||
strFunc func() handler.CObject
|
||||
t *testing.T
|
||||
}
|
||||
|
||||
func (h *webHandlerTest) getHandler() handler.WebHandler {
|
||||
return handler.WebHandler{
|
||||
EmptyStruct: func() handler.CObject {
|
||||
return h.strFunc()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (h *webHandlerTest) testReadAllWithUser(queryParams url.Values, urlParams map[string]string) (rec *httptest.ResponseRecorder, err error) {
|
||||
hndl := h.getHandler()
|
||||
return newTestRequestWithUser(h.t, http.MethodGet, hndl.ReadAllWeb, h.user, "", queryParams, urlParams)
|
||||
}
|
||||
|
||||
func (h *webHandlerTest) testReadOneWithUser(queryParams url.Values, urlParams map[string]string) (rec *httptest.ResponseRecorder, err error) {
|
||||
hndl := h.getHandler()
|
||||
return newTestRequestWithUser(h.t, http.MethodGet, hndl.ReadOneWeb, h.user, "", queryParams, urlParams)
|
||||
}
|
||||
|
||||
func (h *webHandlerTest) testCreateWithUser(queryParams url.Values, urlParams map[string]string, payload string) (rec *httptest.ResponseRecorder, err error) {
|
||||
hndl := h.getHandler()
|
||||
return newTestRequestWithUser(h.t, http.MethodPut, hndl.CreateWeb, h.user, payload, queryParams, urlParams)
|
||||
}
|
||||
|
||||
func (h *webHandlerTest) testUpdateWithUser(queryParams url.Values, urlParams map[string]string, payload string) (rec *httptest.ResponseRecorder, err error) {
|
||||
hndl := h.getHandler()
|
||||
return newTestRequestWithUser(h.t, http.MethodPost, hndl.UpdateWeb, h.user, payload, queryParams, urlParams)
|
||||
}
|
||||
|
||||
func (h *webHandlerTest) testDeleteWithUser(queryParams url.Values, urlParams map[string]string) (rec *httptest.ResponseRecorder, err error) {
|
||||
hndl := h.getHandler()
|
||||
return newTestRequestWithUser(h.t, http.MethodDelete, hndl.DeleteWeb, h.user, "", queryParams, urlParams)
|
||||
}
|
||||
|
||||
func (h *webHandlerTest) testReadAllWithLinkShare(queryParams url.Values, urlParams map[string]string) (rec *httptest.ResponseRecorder, err error) {
|
||||
hndl := h.getHandler()
|
||||
return newTestRequestWithLinkShare(h.t, http.MethodGet, hndl.ReadAllWeb, h.linkShare, "", queryParams, urlParams)
|
||||
}
|
||||
|
||||
func (h *webHandlerTest) testReadOneWithLinkShare(queryParams url.Values, urlParams map[string]string) (rec *httptest.ResponseRecorder, err error) {
|
||||
hndl := h.getHandler()
|
||||
return newTestRequestWithLinkShare(h.t, http.MethodGet, hndl.ReadOneWeb, h.linkShare, "", queryParams, urlParams)
|
||||
}
|
||||
|
||||
func (h *webHandlerTest) testCreateWithLinkShare(queryParams url.Values, urlParams map[string]string, payload string) (rec *httptest.ResponseRecorder, err error) {
|
||||
hndl := h.getHandler()
|
||||
return newTestRequestWithLinkShare(h.t, http.MethodPut, hndl.CreateWeb, h.linkShare, payload, queryParams, urlParams)
|
||||
}
|
||||
|
||||
func (h *webHandlerTest) testUpdateWithLinkShare(queryParams url.Values, urlParams map[string]string, payload string) (rec *httptest.ResponseRecorder, err error) {
|
||||
hndl := h.getHandler()
|
||||
return newTestRequestWithLinkShare(h.t, http.MethodPost, hndl.UpdateWeb, h.linkShare, payload, queryParams, urlParams)
|
||||
}
|
||||
|
||||
func (h *webHandlerTest) testDeleteWithLinkShare(queryParams url.Values, urlParams map[string]string) (rec *httptest.ResponseRecorder, err error) {
|
||||
hndl := h.getHandler()
|
||||
return newTestRequestWithLinkShare(h.t, http.MethodDelete, hndl.DeleteWeb, h.linkShare, "", queryParams, urlParams)
|
||||
}
|
||||
989
pkg/integrations/link_sharing_test.go
Normal file
989
pkg/integrations/link_sharing_test.go
Normal file
@@ -0,0 +1,989 @@
|
||||
// Vikunja is a todo-list application to facilitate your life.
|
||||
// Copyright 2019 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package integrations
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/web/handler"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/url"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLinkSharing(t *testing.T) {
|
||||
|
||||
linkshareRead := &models.LinkSharing{
|
||||
ID: 1,
|
||||
Hash: "test1",
|
||||
ListID: 1,
|
||||
Right: models.RightRead,
|
||||
SharingType: models.SharingTypeWithoutPassword,
|
||||
SharedByID: 1,
|
||||
}
|
||||
|
||||
linkShareWrite := &models.LinkSharing{
|
||||
ID: 2,
|
||||
Hash: "test2",
|
||||
ListID: 2,
|
||||
Right: models.RightWrite,
|
||||
SharingType: models.SharingTypeWithoutPassword,
|
||||
SharedByID: 1,
|
||||
}
|
||||
|
||||
linkShareAdmin := &models.LinkSharing{
|
||||
ID: 3,
|
||||
Hash: "test3",
|
||||
ListID: 3,
|
||||
Right: models.RightAdmin,
|
||||
SharingType: models.SharingTypeWithoutPassword,
|
||||
SharedByID: 1,
|
||||
}
|
||||
|
||||
t.Run("Lists", func(t *testing.T) {
|
||||
testHandlerListReadOnly := webHandlerTest{
|
||||
linkShare: linkshareRead,
|
||||
strFunc: func() handler.CObject {
|
||||
return &models.List{}
|
||||
},
|
||||
t: t,
|
||||
}
|
||||
testHandlerListWrite := webHandlerTest{
|
||||
linkShare: linkShareWrite,
|
||||
strFunc: func() handler.CObject {
|
||||
return &models.List{}
|
||||
},
|
||||
t: t,
|
||||
}
|
||||
testHandlerListAdmin := webHandlerTest{
|
||||
linkShare: linkShareAdmin,
|
||||
strFunc: func() handler.CObject {
|
||||
return &models.List{}
|
||||
},
|
||||
t: t,
|
||||
}
|
||||
|
||||
t.Run("ReadAll", func(t *testing.T) {
|
||||
t.Run("Normal", func(t *testing.T) {
|
||||
rec, err := testHandlerListReadOnly.testReadAllWithLinkShare(nil, nil)
|
||||
assert.NoError(t, err)
|
||||
// Should only return the shared list, nothing else
|
||||
assert.Contains(t, rec.Body.String(), `Test1`)
|
||||
assert.NotContains(t, rec.Body.String(), `Test2`)
|
||||
assert.NotContains(t, rec.Body.String(), `Test3`)
|
||||
assert.NotContains(t, rec.Body.String(), `Test4`)
|
||||
assert.NotContains(t, rec.Body.String(), `Test5`)
|
||||
})
|
||||
t.Run("Search", func(t *testing.T) {
|
||||
rec, err := testHandlerListReadOnly.testReadAllWithLinkShare(url.Values{"s": []string{"est1"}}, nil)
|
||||
assert.NoError(t, err)
|
||||
// Should only return the shared list, nothing else
|
||||
assert.Contains(t, rec.Body.String(), `Test1`)
|
||||
assert.NotContains(t, rec.Body.String(), `Test2`)
|
||||
assert.NotContains(t, rec.Body.String(), `Test3`)
|
||||
assert.NotContains(t, rec.Body.String(), `Test4`)
|
||||
assert.NotContains(t, rec.Body.String(), `Test5`)
|
||||
})
|
||||
})
|
||||
t.Run("ReadOne", func(t *testing.T) {
|
||||
t.Run("Normal", func(t *testing.T) {
|
||||
rec, err := testHandlerListReadOnly.testReadOneWithLinkShare(nil, map[string]string{"list": "1"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Test1"`)
|
||||
assert.NotContains(t, rec.Body.String(), `"title":"Test2"`)
|
||||
})
|
||||
t.Run("Nonexisting", func(t *testing.T) {
|
||||
_, err := testHandlerListReadOnly.testReadOneWithLinkShare(nil, map[string]string{"list": "9999999"})
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeListDoesNotExist)
|
||||
})
|
||||
t.Run("Rights check", func(t *testing.T) {
|
||||
t.Run("Forbidden", func(t *testing.T) {
|
||||
// List 2, not shared with this token
|
||||
_, err := testHandlerListReadOnly.testReadOneWithLinkShare(nil, map[string]string{"list": "2"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `You don't have the right to see this`)
|
||||
})
|
||||
t.Run("Shared readonly", func(t *testing.T) {
|
||||
rec, err := testHandlerListReadOnly.testReadOneWithLinkShare(nil, map[string]string{"list": "1"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Test1"`)
|
||||
})
|
||||
t.Run("Shared write", func(t *testing.T) {
|
||||
rec, err := testHandlerListWrite.testReadOneWithLinkShare(nil, map[string]string{"list": "2"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Test2"`)
|
||||
})
|
||||
t.Run("Shared admin", func(t *testing.T) {
|
||||
rec, err := testHandlerListAdmin.testReadOneWithLinkShare(nil, map[string]string{"list": "3"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Test3"`)
|
||||
})
|
||||
})
|
||||
})
|
||||
t.Run("Update", func(t *testing.T) {
|
||||
t.Run("Nonexisting", func(t *testing.T) {
|
||||
_, err := testHandlerListReadOnly.testUpdateWithLinkShare(nil, map[string]string{"list": "9999999"}, `{"title":"TestLoremIpsum"}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeListDoesNotExist)
|
||||
})
|
||||
t.Run("Rights check", func(t *testing.T) {
|
||||
t.Run("Forbidden", func(t *testing.T) {
|
||||
_, err := testHandlerListReadOnly.testUpdateWithLinkShare(nil, map[string]string{"list": "2"}, `{"title":"TestLoremIpsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared readonly", func(t *testing.T) {
|
||||
_, err := testHandlerListReadOnly.testUpdateWithLinkShare(nil, map[string]string{"list": "1"}, `{"title":"TestLoremIpsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared write", func(t *testing.T) {
|
||||
rec, err := testHandlerListWrite.testUpdateWithLinkShare(nil, map[string]string{"list": "2"}, `{"title":"TestLoremIpsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
|
||||
})
|
||||
t.Run("Shared admin", func(t *testing.T) {
|
||||
rec, err := testHandlerListAdmin.testUpdateWithLinkShare(nil, map[string]string{"list": "3"}, `{"title":"TestLoremIpsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
|
||||
})
|
||||
})
|
||||
})
|
||||
t.Run("Delete", func(t *testing.T) {
|
||||
t.Run("Nonexisting", func(t *testing.T) {
|
||||
_, err := testHandlerListReadOnly.testDeleteWithLinkShare(nil, map[string]string{"list": "9999999"})
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeListDoesNotExist)
|
||||
})
|
||||
t.Run("Rights check", func(t *testing.T) {
|
||||
t.Run("Forbidden", func(t *testing.T) {
|
||||
_, err := testHandlerListReadOnly.testDeleteWithLinkShare(nil, map[string]string{"list": "1"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared readonly", func(t *testing.T) {
|
||||
_, err := testHandlerListReadOnly.testDeleteWithLinkShare(nil, map[string]string{"list": "1"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared write", func(t *testing.T) {
|
||||
_, err := testHandlerListWrite.testDeleteWithLinkShare(nil, map[string]string{"list": "2"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared admin", func(t *testing.T) {
|
||||
rec, err := testHandlerListAdmin.testDeleteWithLinkShare(nil, map[string]string{"list": "3"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// Creating a list should always be forbidden, since users need access to a namespace to create a list
|
||||
t.Run("Create", func(t *testing.T) {
|
||||
t.Run("Nonexisting", func(t *testing.T) {
|
||||
_, err := testHandlerListReadOnly.testCreateWithLinkShare(nil, map[string]string{"namespace": "999999"}, `{"title":"Lorem"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Rights check", func(t *testing.T) {
|
||||
t.Run("Shared readonly", func(t *testing.T) {
|
||||
_, err := testHandlerListReadOnly.testCreateWithLinkShare(nil, map[string]string{"namespace": "1"}, `{"title":"Lorem","description":"Lorem Ipsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared write", func(t *testing.T) {
|
||||
_, err := testHandlerListWrite.testCreateWithLinkShare(nil, map[string]string{"namespace": "2"}, `{"title":"Lorem","description":"Lorem Ipsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared admin", func(t *testing.T) {
|
||||
_, err := testHandlerListAdmin.testCreateWithLinkShare(nil, map[string]string{"namespace": "3"}, `{"title":"Lorem","description":"Lorem Ipsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Right Management", func(t *testing.T) {
|
||||
t.Run("Users", func(t *testing.T) {
|
||||
testHandlerListUserReadOnly := webHandlerTest{
|
||||
linkShare: linkshareRead,
|
||||
strFunc: func() handler.CObject {
|
||||
return &models.ListUser{}
|
||||
},
|
||||
t: t,
|
||||
}
|
||||
testHandlerListUserWrite := webHandlerTest{
|
||||
linkShare: linkShareWrite,
|
||||
strFunc: func() handler.CObject {
|
||||
return &models.ListUser{}
|
||||
},
|
||||
t: t,
|
||||
}
|
||||
testHandlerListUserAdmin := webHandlerTest{
|
||||
linkShare: linkShareAdmin,
|
||||
strFunc: func() handler.CObject {
|
||||
return &models.ListUser{}
|
||||
},
|
||||
t: t,
|
||||
}
|
||||
t.Run("ReadAll", func(t *testing.T) {
|
||||
t.Run("Shared readonly", func(t *testing.T) {
|
||||
rec, err := testHandlerListUserReadOnly.testReadAllWithLinkShare(nil, map[string]string{"list": "1"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `[]`)
|
||||
})
|
||||
t.Run("Shared write", func(t *testing.T) {
|
||||
rec, err := testHandlerListUserWrite.testReadAllWithLinkShare(nil, map[string]string{"list": "2"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `[]`)
|
||||
})
|
||||
t.Run("Shared admin", func(t *testing.T) {
|
||||
rec, err := testHandlerListUserAdmin.testReadAllWithLinkShare(nil, map[string]string{"list": "3"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"username":"user1"`)
|
||||
})
|
||||
})
|
||||
t.Run("Create", func(t *testing.T) {
|
||||
t.Run("Shared readonly", func(t *testing.T) {
|
||||
_, err := testHandlerListUserReadOnly.testCreateWithLinkShare(nil, map[string]string{"list": "1"}, `{"userID":"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"}`)
|
||||
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"}`)
|
||||
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"}`)
|
||||
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"}`)
|
||||
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"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
|
||||
})
|
||||
t.Run("Delete", func(t *testing.T) {
|
||||
t.Run("Shared readonly", func(t *testing.T) {
|
||||
_, err := testHandlerListUserReadOnly.testDeleteWithLinkShare(nil, map[string]string{"list": "1"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared write", func(t *testing.T) {
|
||||
_, err := testHandlerListUserWrite.testDeleteWithLinkShare(nil, map[string]string{"list": "2"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared admin", func(t *testing.T) {
|
||||
_, err := testHandlerListUserAdmin.testDeleteWithLinkShare(nil, map[string]string{"list": "3"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
})
|
||||
})
|
||||
t.Run("Teams", func(t *testing.T) {
|
||||
testHandlerListTeamReadOnly := webHandlerTest{
|
||||
linkShare: linkshareRead,
|
||||
strFunc: func() handler.CObject {
|
||||
return &models.TeamList{}
|
||||
},
|
||||
t: t,
|
||||
}
|
||||
testHandlerListTeamWrite := webHandlerTest{
|
||||
linkShare: linkShareWrite,
|
||||
strFunc: func() handler.CObject {
|
||||
return &models.TeamList{}
|
||||
},
|
||||
t: t,
|
||||
}
|
||||
testHandlerListTeamAdmin := webHandlerTest{
|
||||
linkShare: linkShareAdmin,
|
||||
strFunc: func() handler.CObject {
|
||||
return &models.TeamList{}
|
||||
},
|
||||
t: t,
|
||||
}
|
||||
t.Run("ReadAll", func(t *testing.T) {
|
||||
t.Run("Shared readonly", func(t *testing.T) {
|
||||
rec, err := testHandlerListTeamReadOnly.testReadAllWithLinkShare(nil, map[string]string{"list": "1"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `[]`)
|
||||
})
|
||||
t.Run("Shared write", func(t *testing.T) {
|
||||
rec, err := testHandlerListTeamWrite.testReadAllWithLinkShare(nil, map[string]string{"list": "2"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `[]`)
|
||||
})
|
||||
t.Run("Shared admin", func(t *testing.T) {
|
||||
rec, err := testHandlerListTeamAdmin.testReadAllWithLinkShare(nil, map[string]string{"list": "3"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"name":"testteam1"`)
|
||||
})
|
||||
})
|
||||
t.Run("Create", func(t *testing.T) {
|
||||
t.Run("Shared readonly", func(t *testing.T) {
|
||||
_, err := testHandlerListTeamReadOnly.testCreateWithLinkShare(nil, map[string]string{"list": "1"}, `{"teamID":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}`)
|
||||
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}`)
|
||||
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}`)
|
||||
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}`)
|
||||
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}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
|
||||
})
|
||||
t.Run("Delete", func(t *testing.T) {
|
||||
t.Run("Shared readonly", func(t *testing.T) {
|
||||
_, err := testHandlerListTeamReadOnly.testDeleteWithLinkShare(nil, map[string]string{"list": "1"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared write", func(t *testing.T) {
|
||||
_, err := testHandlerListTeamWrite.testDeleteWithLinkShare(nil, map[string]string{"list": "2"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared admin", func(t *testing.T) {
|
||||
_, err := testHandlerListTeamAdmin.testDeleteWithLinkShare(nil, map[string]string{"list": "3"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Tasks", func(t *testing.T) {
|
||||
testHandlerTaskReadOnly := webHandlerTest{
|
||||
linkShare: linkshareRead,
|
||||
strFunc: func() handler.CObject {
|
||||
return &models.Task{}
|
||||
},
|
||||
t: t,
|
||||
}
|
||||
testHandlerTaskWrite := webHandlerTest{
|
||||
linkShare: linkShareWrite,
|
||||
strFunc: func() handler.CObject {
|
||||
return &models.Task{}
|
||||
},
|
||||
t: t,
|
||||
}
|
||||
testHandlerTaskAdmin := webHandlerTest{
|
||||
linkShare: linkShareAdmin,
|
||||
strFunc: func() handler.CObject {
|
||||
return &models.Task{}
|
||||
},
|
||||
t: t,
|
||||
}
|
||||
t.Run("ReadAll", func(t *testing.T) {
|
||||
t.Run("Shared readonly", func(t *testing.T) {
|
||||
rec, err := testHandlerTaskReadOnly.testReadAllWithLinkShare(nil, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `task #1`)
|
||||
assert.Contains(t, rec.Body.String(), `task #2`)
|
||||
assert.Contains(t, rec.Body.String(), `task #3`)
|
||||
assert.Contains(t, rec.Body.String(), `task #4`)
|
||||
assert.Contains(t, rec.Body.String(), `task #5`)
|
||||
assert.Contains(t, rec.Body.String(), `task #6`)
|
||||
assert.Contains(t, rec.Body.String(), `task #7`)
|
||||
assert.Contains(t, rec.Body.String(), `task #8`)
|
||||
assert.Contains(t, rec.Body.String(), `task #9`)
|
||||
assert.Contains(t, rec.Body.String(), `task #10`)
|
||||
assert.Contains(t, rec.Body.String(), `task #11`)
|
||||
assert.Contains(t, rec.Body.String(), `task #12`)
|
||||
assert.NotContains(t, rec.Body.String(), `task #13`)
|
||||
assert.NotContains(t, rec.Body.String(), `task #14`)
|
||||
})
|
||||
t.Run("Shared write", func(t *testing.T) {
|
||||
rec, err := testHandlerTaskWrite.testReadAllWithLinkShare(nil, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.NotContains(t, rec.Body.String(), `task #2`)
|
||||
assert.NotContains(t, rec.Body.String(), `task #3`)
|
||||
assert.NotContains(t, rec.Body.String(), `task #4`)
|
||||
assert.NotContains(t, rec.Body.String(), `task #5`)
|
||||
assert.NotContains(t, rec.Body.String(), `task #6`)
|
||||
assert.NotContains(t, rec.Body.String(), `task #7`)
|
||||
assert.NotContains(t, rec.Body.String(), `task #8`)
|
||||
assert.NotContains(t, rec.Body.String(), `task #9`)
|
||||
assert.NotContains(t, rec.Body.String(), `task #10`)
|
||||
assert.NotContains(t, rec.Body.String(), `task #11`)
|
||||
assert.NotContains(t, rec.Body.String(), `task #12`)
|
||||
assert.Contains(t, rec.Body.String(), `task #13`)
|
||||
assert.NotContains(t, rec.Body.String(), `task #14`)
|
||||
})
|
||||
t.Run("Shared admin", func(t *testing.T) {
|
||||
rec, err := testHandlerTaskAdmin.testReadAllWithLinkShare(nil, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.NotContains(t, rec.Body.String(), `task #2`)
|
||||
assert.NotContains(t, rec.Body.String(), `task #4`)
|
||||
assert.NotContains(t, rec.Body.String(), `task #5`)
|
||||
assert.NotContains(t, rec.Body.String(), `task #6`)
|
||||
assert.NotContains(t, rec.Body.String(), `task #7`)
|
||||
assert.NotContains(t, rec.Body.String(), `task #8`)
|
||||
assert.NotContains(t, rec.Body.String(), `task #9`)
|
||||
assert.NotContains(t, rec.Body.String(), `task #10`)
|
||||
assert.NotContains(t, rec.Body.String(), `task #11`)
|
||||
assert.NotContains(t, rec.Body.String(), `task #12`)
|
||||
assert.NotContains(t, rec.Body.String(), `task #13`)
|
||||
assert.NotContains(t, rec.Body.String(), `task #14`)
|
||||
assert.Contains(t, rec.Body.String(), `task #32`)
|
||||
})
|
||||
})
|
||||
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"}`)
|
||||
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"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"text":"Lorem Ipsum"`)
|
||||
})
|
||||
t.Run("Shared admin", func(t *testing.T) {
|
||||
rec, err := testHandlerTaskAdmin.testCreateWithLinkShare(nil, map[string]string{"list": "3"}, `{"text":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"text":"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"}`)
|
||||
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"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"text":"Lorem Ipsum"`)
|
||||
})
|
||||
t.Run("Shared admin", func(t *testing.T) {
|
||||
rec, err := testHandlerTaskAdmin.testUpdateWithLinkShare(nil, map[string]string{"listtask": "32"}, `{"text":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"text":"Lorem Ipsum"`)
|
||||
})
|
||||
|
||||
})
|
||||
t.Run("Delete", func(t *testing.T) {
|
||||
t.Run("Shared readonly", func(t *testing.T) {
|
||||
_, err := testHandlerTaskReadOnly.testDeleteWithLinkShare(nil, map[string]string{"listtask": "1"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared write", func(t *testing.T) {
|
||||
rec, err := testHandlerTaskWrite.testDeleteWithLinkShare(nil, map[string]string{"listtask": "13"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
|
||||
})
|
||||
t.Run("Shared admin", func(t *testing.T) {
|
||||
rec, err := testHandlerTaskAdmin.testDeleteWithLinkShare(nil, map[string]string{"listtask": "32"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Teams", func(t *testing.T) {
|
||||
testHandlerTeamReadOnly := webHandlerTest{
|
||||
linkShare: linkshareRead,
|
||||
strFunc: func() handler.CObject {
|
||||
return &models.Team{}
|
||||
},
|
||||
t: t,
|
||||
}
|
||||
testHandlerTeamWrite := webHandlerTest{
|
||||
linkShare: linkShareWrite,
|
||||
strFunc: func() handler.CObject {
|
||||
return &models.Team{}
|
||||
},
|
||||
t: t,
|
||||
}
|
||||
testHandlerTeamAdmin := webHandlerTest{
|
||||
linkShare: linkShareAdmin,
|
||||
strFunc: func() handler.CObject {
|
||||
return &models.Team{}
|
||||
},
|
||||
t: t,
|
||||
}
|
||||
t.Run("ReadAll", func(t *testing.T) {
|
||||
t.Run("Shared readonly", func(t *testing.T) {
|
||||
_, err := testHandlerTeamReadOnly.testReadAllWithLinkShare(nil, nil)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrorCodeGenericForbidden)
|
||||
})
|
||||
t.Run("Shared write", func(t *testing.T) {
|
||||
_, err := testHandlerTeamWrite.testReadAllWithLinkShare(nil, nil)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrorCodeGenericForbidden)
|
||||
})
|
||||
t.Run("Shared admin", func(t *testing.T) {
|
||||
_, err := testHandlerTeamAdmin.testReadAllWithLinkShare(nil, nil)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrorCodeGenericForbidden)
|
||||
})
|
||||
})
|
||||
t.Run("Update", func(t *testing.T) {
|
||||
t.Run("Shared readonly", func(t *testing.T) {
|
||||
_, err := testHandlerTeamReadOnly.testUpdateWithLinkShare(nil, map[string]string{"team": "1"}, `{"name":"Lorem Ipsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared write", func(t *testing.T) {
|
||||
_, err := testHandlerTeamWrite.testUpdateWithLinkShare(nil, map[string]string{"team": "2"}, `{"name":"Lorem Ipsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared admin", func(t *testing.T) {
|
||||
_, err := testHandlerTeamAdmin.testUpdateWithLinkShare(nil, map[string]string{"team": "3"}, `{"name":"Lorem Ipsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
|
||||
})
|
||||
t.Run("Delete", func(t *testing.T) {
|
||||
t.Run("Shared readonly", func(t *testing.T) {
|
||||
_, err := testHandlerTeamReadOnly.testDeleteWithLinkShare(nil, map[string]string{"team": "1"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared write", func(t *testing.T) {
|
||||
_, err := testHandlerTeamWrite.testDeleteWithLinkShare(nil, map[string]string{"team": "2"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared admin", func(t *testing.T) {
|
||||
_, err := testHandlerTeamAdmin.testDeleteWithLinkShare(nil, map[string]string{"team": "3"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Linkshare Management", func(t *testing.T) {
|
||||
testHandlerLinkShareReadOnly := webHandlerTest{
|
||||
linkShare: linkshareRead,
|
||||
strFunc: func() handler.CObject {
|
||||
return &models.LinkSharing{}
|
||||
},
|
||||
t: t,
|
||||
}
|
||||
testHandlerLinkShareWrite := webHandlerTest{
|
||||
linkShare: linkShareWrite,
|
||||
strFunc: func() handler.CObject {
|
||||
return &models.LinkSharing{}
|
||||
},
|
||||
t: t,
|
||||
}
|
||||
testHandlerLinkShareAdmin := webHandlerTest{
|
||||
linkShare: linkShareAdmin,
|
||||
strFunc: func() handler.CObject {
|
||||
return &models.LinkSharing{}
|
||||
},
|
||||
t: t,
|
||||
}
|
||||
t.Run("ReadAll", func(t *testing.T) {
|
||||
t.Run("Shared readonly", func(t *testing.T) {
|
||||
rec, err := testHandlerLinkShareReadOnly.testReadAllWithLinkShare(nil, map[string]string{"list": "1"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"hash":"test"`)
|
||||
})
|
||||
t.Run("Shared write", func(t *testing.T) {
|
||||
rec, err := testHandlerLinkShareWrite.testReadAllWithLinkShare(nil, map[string]string{"list": "2"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"hash":"test2"`)
|
||||
})
|
||||
t.Run("Shared admin", func(t *testing.T) {
|
||||
rec, err := testHandlerLinkShareAdmin.testReadAllWithLinkShare(nil, map[string]string{"list": "3"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"hash":"test3"`)
|
||||
})
|
||||
})
|
||||
t.Run("Create", func(t *testing.T) {
|
||||
t.Run("Shared readonly", func(t *testing.T) {
|
||||
_, err := testHandlerLinkShareReadOnly.testCreateWithLinkShare(nil, map[string]string{"list": "1"}, `{}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared write", func(t *testing.T) {
|
||||
_, err := testHandlerLinkShareWrite.testCreateWithLinkShare(nil, map[string]string{"list": "2"}, `{}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared admin", func(t *testing.T) {
|
||||
_, err := testHandlerLinkShareAdmin.testCreateWithLinkShare(nil, map[string]string{"list": "3"}, `{}`)
|
||||
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 := testHandlerLinkShareReadOnly.testUpdateWithLinkShare(nil, map[string]string{"share": "1"}, `{}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared write", func(t *testing.T) {
|
||||
_, err := testHandlerLinkShareWrite.testUpdateWithLinkShare(nil, map[string]string{"share": "2"}, `{}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared admin", func(t *testing.T) {
|
||||
_, err := testHandlerLinkShareAdmin.testUpdateWithLinkShare(nil, map[string]string{"share": "3"}, `{}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
|
||||
})
|
||||
t.Run("Delete", func(t *testing.T) {
|
||||
t.Run("Shared readonly", func(t *testing.T) {
|
||||
_, err := testHandlerLinkShareReadOnly.testDeleteWithLinkShare(nil, map[string]string{"share": "1"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared write", func(t *testing.T) {
|
||||
_, err := testHandlerLinkShareWrite.testDeleteWithLinkShare(nil, map[string]string{"share": "2"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared admin", func(t *testing.T) {
|
||||
_, err := testHandlerLinkShareAdmin.testDeleteWithLinkShare(nil, map[string]string{"share": "3"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Namespace", func(t *testing.T) {
|
||||
testHandlerNamespaceReadOnly := webHandlerTest{
|
||||
linkShare: linkshareRead,
|
||||
strFunc: func() handler.CObject {
|
||||
return &models.Namespace{}
|
||||
},
|
||||
t: t,
|
||||
}
|
||||
testHandlerNamespaceWrite := webHandlerTest{
|
||||
linkShare: linkShareWrite,
|
||||
strFunc: func() handler.CObject {
|
||||
return &models.Namespace{}
|
||||
},
|
||||
t: t,
|
||||
}
|
||||
testHandlerNamespaceAdmin := webHandlerTest{
|
||||
linkShare: linkShareAdmin,
|
||||
strFunc: func() handler.CObject {
|
||||
return &models.Namespace{}
|
||||
},
|
||||
t: t,
|
||||
}
|
||||
t.Run("ReadAll", func(t *testing.T) {
|
||||
t.Run("Shared readonly", func(t *testing.T) {
|
||||
_, err := testHandlerNamespaceReadOnly.testReadAllWithLinkShare(nil, map[string]string{"namespace": "1"})
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrorCodeGenericForbidden)
|
||||
})
|
||||
t.Run("Shared write", func(t *testing.T) {
|
||||
_, err := testHandlerNamespaceWrite.testReadAllWithLinkShare(nil, map[string]string{"namespace": "2"})
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrorCodeGenericForbidden)
|
||||
})
|
||||
t.Run("Shared admin", func(t *testing.T) {
|
||||
_, err := testHandlerNamespaceAdmin.testReadAllWithLinkShare(nil, map[string]string{"namespace": "3"})
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrorCodeGenericForbidden)
|
||||
})
|
||||
})
|
||||
t.Run("Create", func(t *testing.T) {
|
||||
t.Run("Shared readonly", func(t *testing.T) {
|
||||
_, err := testHandlerNamespaceReadOnly.testCreateWithLinkShare(nil, nil, `{"name":"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"}`)
|
||||
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"}`)
|
||||
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"}`)
|
||||
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"}`)
|
||||
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"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
|
||||
})
|
||||
t.Run("Delete", func(t *testing.T) {
|
||||
t.Run("Shared readonly", func(t *testing.T) {
|
||||
_, err := testHandlerNamespaceReadOnly.testDeleteWithLinkShare(nil, map[string]string{"namespace": "1"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared write", func(t *testing.T) {
|
||||
_, err := testHandlerNamespaceWrite.testDeleteWithLinkShare(nil, map[string]string{"namespace": "2"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared admin", func(t *testing.T) {
|
||||
_, err := testHandlerNamespaceAdmin.testDeleteWithLinkShare(nil, map[string]string{"namespace": "3"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Right Management", func(t *testing.T) {
|
||||
t.Run("Users", func(t *testing.T) {
|
||||
testHandlerNamespaceUserReadOnly := webHandlerTest{
|
||||
linkShare: linkshareRead,
|
||||
strFunc: func() handler.CObject {
|
||||
return &models.NamespaceUser{}
|
||||
},
|
||||
t: t,
|
||||
}
|
||||
testHandlerNamespaceUserWrite := webHandlerTest{
|
||||
linkShare: linkShareWrite,
|
||||
strFunc: func() handler.CObject {
|
||||
return &models.NamespaceUser{}
|
||||
},
|
||||
t: t,
|
||||
}
|
||||
testHandlerNamespaceUserAdmin := webHandlerTest{
|
||||
linkShare: linkShareAdmin,
|
||||
strFunc: func() handler.CObject {
|
||||
return &models.NamespaceUser{}
|
||||
},
|
||||
t: t,
|
||||
}
|
||||
t.Run("ReadAll", func(t *testing.T) {
|
||||
t.Run("Shared readonly", func(t *testing.T) {
|
||||
_, err := testHandlerNamespaceUserReadOnly.testReadAllWithLinkShare(nil, map[string]string{"namespace": "1"})
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeNeedToHaveNamespaceReadAccess)
|
||||
})
|
||||
t.Run("Shared write", func(t *testing.T) {
|
||||
_, err := testHandlerNamespaceUserWrite.testReadAllWithLinkShare(nil, map[string]string{"namespace": "2"})
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeNeedToHaveNamespaceReadAccess)
|
||||
})
|
||||
t.Run("Shared admin", func(t *testing.T) {
|
||||
_, err := testHandlerNamespaceUserAdmin.testReadAllWithLinkShare(nil, map[string]string{"namespace": "3"})
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeNeedToHaveNamespaceReadAccess)
|
||||
})
|
||||
})
|
||||
t.Run("Create", func(t *testing.T) {
|
||||
t.Run("Shared readonly", func(t *testing.T) {
|
||||
_, err := testHandlerNamespaceUserReadOnly.testCreateWithLinkShare(nil, nil, `{"userID":"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"}`)
|
||||
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"}`)
|
||||
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"}`)
|
||||
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"}`)
|
||||
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"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
|
||||
})
|
||||
t.Run("Delete", func(t *testing.T) {
|
||||
t.Run("Shared readonly", func(t *testing.T) {
|
||||
_, err := testHandlerNamespaceUserReadOnly.testDeleteWithLinkShare(nil, map[string]string{"namespace": "1"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared write", func(t *testing.T) {
|
||||
_, err := testHandlerNamespaceUserWrite.testDeleteWithLinkShare(nil, map[string]string{"namespace": "2"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared admin", func(t *testing.T) {
|
||||
_, err := testHandlerNamespaceUserAdmin.testDeleteWithLinkShare(nil, map[string]string{"namespace": "3"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
})
|
||||
})
|
||||
t.Run("Teams", func(t *testing.T) {
|
||||
testHandlerNamespaceTeamReadOnly := webHandlerTest{
|
||||
linkShare: linkshareRead,
|
||||
strFunc: func() handler.CObject {
|
||||
return &models.TeamNamespace{}
|
||||
},
|
||||
t: t,
|
||||
}
|
||||
testHandlerNamespaceTeamWrite := webHandlerTest{
|
||||
linkShare: linkShareWrite,
|
||||
strFunc: func() handler.CObject {
|
||||
return &models.TeamNamespace{}
|
||||
},
|
||||
t: t,
|
||||
}
|
||||
testHandlerNamespaceTeamAdmin := webHandlerTest{
|
||||
linkShare: linkShareAdmin,
|
||||
strFunc: func() handler.CObject {
|
||||
return &models.TeamNamespace{}
|
||||
},
|
||||
t: t,
|
||||
}
|
||||
t.Run("ReadAll", func(t *testing.T) {
|
||||
t.Run("Shared readonly", func(t *testing.T) {
|
||||
_, err := testHandlerNamespaceTeamReadOnly.testReadAllWithLinkShare(nil, map[string]string{"namespace": "1"})
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeNeedToHaveNamespaceReadAccess)
|
||||
})
|
||||
t.Run("Shared write", func(t *testing.T) {
|
||||
_, err := testHandlerNamespaceTeamWrite.testReadAllWithLinkShare(nil, map[string]string{"namespace": "2"})
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeNeedToHaveNamespaceReadAccess)
|
||||
})
|
||||
t.Run("Shared admin", func(t *testing.T) {
|
||||
_, err := testHandlerNamespaceTeamAdmin.testReadAllWithLinkShare(nil, map[string]string{"namespace": "3"})
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeNeedToHaveNamespaceReadAccess)
|
||||
})
|
||||
})
|
||||
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}`)
|
||||
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}`)
|
||||
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}`)
|
||||
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}`)
|
||||
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}`)
|
||||
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}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
|
||||
})
|
||||
t.Run("Delete", func(t *testing.T) {
|
||||
t.Run("Shared readonly", func(t *testing.T) {
|
||||
_, err := testHandlerNamespaceTeamReadOnly.testDeleteWithLinkShare(nil, map[string]string{"namespace": "1"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared write", func(t *testing.T) {
|
||||
_, err := testHandlerNamespaceTeamWrite.testDeleteWithLinkShare(nil, map[string]string{"namespace": "2"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared admin", func(t *testing.T) {
|
||||
_, err := testHandlerNamespaceTeamAdmin.testDeleteWithLinkShare(nil, map[string]string{"namespace": "3"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
425
pkg/integrations/list_test.go
Normal file
425
pkg/integrations/list_test.go
Normal file
@@ -0,0 +1,425 @@
|
||||
// Vikunja is a todo-list application to facilitate your life.
|
||||
// Copyright 2019 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package integrations
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/web/handler"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/url"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
testHandler := webHandlerTest{
|
||||
user: &testuser1,
|
||||
strFunc: func() handler.CObject {
|
||||
return &models.List{}
|
||||
},
|
||||
t: t,
|
||||
}
|
||||
t.Run("ReadAll", func(t *testing.T) {
|
||||
t.Run("Normal", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(nil, nil)
|
||||
assert.NoError(t, err)
|
||||
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`)
|
||||
})
|
||||
t.Run("Search", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(url.Values{"s": []string{"Test1"}}, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `Test1`)
|
||||
assert.NotContains(t, rec.Body.String(), `Test2`)
|
||||
assert.NotContains(t, rec.Body.String(), `Test3`)
|
||||
assert.NotContains(t, rec.Body.String(), `Test4`)
|
||||
assert.NotContains(t, rec.Body.String(), `Test5`)
|
||||
})
|
||||
})
|
||||
t.Run("ReadOne", func(t *testing.T) {
|
||||
t.Run("Normal", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "1"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Test1"`)
|
||||
assert.NotContains(t, rec.Body.String(), `"title":"Test2"`)
|
||||
assert.Contains(t, rec.Body.String(), `"owner":{"id":1,"username":"user1",`)
|
||||
assert.NotContains(t, rec.Body.String(), `"owner":{"id":2,"username":"user2",`)
|
||||
assert.Contains(t, rec.Body.String(), `"tasks":[{"id":1,"text":"task #1",`)
|
||||
})
|
||||
t.Run("Nonexisting", func(t *testing.T) {
|
||||
_, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "9999"})
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeListDoesNotExist)
|
||||
})
|
||||
t.Run("Rights check", func(t *testing.T) {
|
||||
t.Run("Forbidden", func(t *testing.T) {
|
||||
// Owned by user13
|
||||
_, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "20"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `You don't have the right to see this`)
|
||||
})
|
||||
t.Run("Shared Via Team readonly", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "6"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Test6"`)
|
||||
})
|
||||
t.Run("Shared Via Team write", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "7"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Test7"`)
|
||||
})
|
||||
t.Run("Shared Via Team admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "8"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Test8"`)
|
||||
})
|
||||
|
||||
t.Run("Shared Via User readonly", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "9"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Test9"`)
|
||||
})
|
||||
t.Run("Shared Via User write", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "10"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Test10"`)
|
||||
})
|
||||
t.Run("Shared Via User admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "11"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Test11"`)
|
||||
})
|
||||
|
||||
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "12"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Test12"`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "13"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Test13"`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "14"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Test14"`)
|
||||
})
|
||||
|
||||
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "15"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Test15"`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "16"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Test16"`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadOneWithUser(nil, map[string]string{"list": "17"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Test17"`)
|
||||
})
|
||||
})
|
||||
})
|
||||
t.Run("Update", func(t *testing.T) {
|
||||
t.Run("Normal", func(t *testing.T) {
|
||||
// Check the list was loaded successfully afterwards, see testReadOneWithUser
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "1"}, `{"title":"TestLoremIpsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
|
||||
// The description should not be updated but returned correctly
|
||||
assert.Contains(t, rec.Body.String(), `description":"Lorem Ipsum`)
|
||||
})
|
||||
t.Run("Nonexisting", func(t *testing.T) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "9999"}, `{"title":"TestLoremIpsum"}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeListDoesNotExist)
|
||||
})
|
||||
t.Run("Normal with updating the description", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "1"}, `{"title":"TestLoremIpsum","description":"Lorem Ipsum dolor sit amet"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
|
||||
assert.Contains(t, rec.Body.String(), `"description":"Lorem Ipsum dolor sit amet`)
|
||||
})
|
||||
t.Run("Empty title", func(t *testing.T) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "1"}, `{"title":""}`)
|
||||
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)")
|
||||
})
|
||||
t.Run("Rights check", func(t *testing.T) {
|
||||
t.Run("Forbidden", func(t *testing.T) {
|
||||
// Owned by user13
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "20"}, `{"title":"TestLoremIpsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via Team readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "6"}, `{"title":"TestLoremIpsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via Team write", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "7"}, `{"title":"TestLoremIpsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
|
||||
})
|
||||
t.Run("Shared Via Team admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "8"}, `{"title":"TestLoremIpsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
|
||||
})
|
||||
|
||||
t.Run("Shared Via User readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "9"}, `{"title":"TestLoremIpsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via User write", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "10"}, `{"title":"TestLoremIpsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
|
||||
})
|
||||
t.Run("Shared Via User admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "11"}, `{"title":"TestLoremIpsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
|
||||
})
|
||||
|
||||
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "12"}, `{"title":"TestLoremIpsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "13"}, `{"title":"TestLoremIpsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "14"}, `{"title":"TestLoremIpsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
|
||||
})
|
||||
|
||||
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "15"}, `{"title":"TestLoremIpsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "16"}, `{"title":"TestLoremIpsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"TestLoremIpsum"`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"list": "17"}, `{"title":"TestLoremIpsum"}`)
|
||||
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"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
|
||||
})
|
||||
t.Run("Nonexisting", func(t *testing.T) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "999"})
|
||||
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.testDeleteWithUser(nil, map[string]string{"list": "20"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via Team readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "6"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via Team write", func(t *testing.T) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "7"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via Team admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "8"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
|
||||
})
|
||||
|
||||
t.Run("Shared Via User readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "9"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via User write", func(t *testing.T) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "10"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via User admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "11"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
|
||||
})
|
||||
|
||||
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "12"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "13"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "14"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"message":"Successfully deleted."`)
|
||||
})
|
||||
|
||||
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "15"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "16"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"list": "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) {
|
||||
// Check the list was loaded successfully after update, see testReadOneWithUser
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"namespace": "1"}, `{"title":"Lorem"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Lorem"`)
|
||||
assert.Contains(t, rec.Body.String(), `"description":""`)
|
||||
assert.Contains(t, rec.Body.String(), `"owner":{"id":1`)
|
||||
assert.Contains(t, rec.Body.String(), `"tasks":null`)
|
||||
})
|
||||
t.Run("Normal with description", func(t *testing.T) {
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"namespace": "1"}, `{"title":"Lorem","description":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Lorem"`)
|
||||
assert.Contains(t, rec.Body.String(), `"description":"Lorem Ipsum"`)
|
||||
assert.Contains(t, rec.Body.String(), `"owner":{"id":1`)
|
||||
assert.Contains(t, rec.Body.String(), `"tasks":null`)
|
||||
})
|
||||
t.Run("Nonexisting Namespace", func(t *testing.T) {
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"namespace": "999999"}, `{"title":"Lorem"}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeNamespaceDoesNotExist)
|
||||
})
|
||||
t.Run("Empty title", func(t *testing.T) {
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"namespace": "1"}, `{"title":""}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message.(models.ValidationHTTPError).InvalidFields, "title: non zero value required")
|
||||
})
|
||||
t.Run("Almost empty title", func(t *testing.T) {
|
||||
_, err := testHandler.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)")
|
||||
})
|
||||
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{"namespace": "15"}, `{"title":"Lorem"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
|
||||
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"namespace": "7"}, `{"title":"Lorem"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"namespace": "8"}, `{"title":"Lorem"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Lorem"`)
|
||||
assert.Contains(t, rec.Body.String(), `"description":""`)
|
||||
assert.Contains(t, rec.Body.String(), `"owner":{"id":1`)
|
||||
assert.Contains(t, rec.Body.String(), `"tasks":null`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"namespace": "9"}, `{"title":"Lorem"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Lorem"`)
|
||||
assert.Contains(t, rec.Body.String(), `"description":""`)
|
||||
assert.Contains(t, rec.Body.String(), `"owner":{"id":1`)
|
||||
assert.Contains(t, rec.Body.String(), `"tasks":null`)
|
||||
})
|
||||
|
||||
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"namespace": "10"}, `{"title":"Lorem"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"namespace": "11"}, `{"title":"Lorem"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Lorem"`)
|
||||
assert.Contains(t, rec.Body.String(), `"description":""`)
|
||||
assert.Contains(t, rec.Body.String(), `"owner":{"id":1`)
|
||||
assert.Contains(t, rec.Body.String(), `"tasks":null`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"namespace": "12"}, `{"title":"Lorem"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Lorem"`)
|
||||
assert.Contains(t, rec.Body.String(), `"description":""`)
|
||||
assert.Contains(t, rec.Body.String(), `"owner":{"id":1`)
|
||||
assert.Contains(t, rec.Body.String(), `"tasks":null`)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
65
pkg/integrations/login_test.go
Normal file
65
pkg/integrations/login_test.go
Normal file
@@ -0,0 +1,65 @@
|
||||
// Vikunja is a todo-list application to facilitate your life.
|
||||
// Copyright 2019 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package integrations
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
apiv1 "code.vikunja.io/api/pkg/routes/api/v1"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLogin(t *testing.T) {
|
||||
t.Run("Normal login", func(t *testing.T) {
|
||||
rec, err := newTestRequest(t, http.MethodPost, apiv1.Login, `{
|
||||
"username": "user1",
|
||||
"password": "1234"
|
||||
}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), "token")
|
||||
})
|
||||
t.Run("Empty payload", func(t *testing.T) {
|
||||
_, err := newTestRequest(t, http.MethodPost, apiv1.Login, `{}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeNoUsernamePassword)
|
||||
})
|
||||
t.Run("Not existing user", func(t *testing.T) {
|
||||
_, err := newTestRequest(t, http.MethodPost, apiv1.Login, `{
|
||||
"username": "userWichDoesNotExist",
|
||||
"password": "1234"
|
||||
}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeWrongUsernameOrPassword)
|
||||
})
|
||||
t.Run("Wrong password", func(t *testing.T) {
|
||||
_, err := newTestRequest(t, http.MethodPost, apiv1.Login, `{
|
||||
"username": "user1",
|
||||
"password": "wrong"
|
||||
}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeWrongUsernameOrPassword)
|
||||
})
|
||||
t.Run("user with unconfirmed email", func(t *testing.T) {
|
||||
_, err := newTestRequest(t, http.MethodPost, apiv1.Login, `{
|
||||
"username": "user5",
|
||||
"password": "1234"
|
||||
}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeEmailNotConfirmed)
|
||||
})
|
||||
}
|
||||
87
pkg/integrations/register_test.go
Normal file
87
pkg/integrations/register_test.go
Normal file
@@ -0,0 +1,87 @@
|
||||
// Vikunja is a todo-list application to facilitate your life.
|
||||
// Copyright 2019 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package integrations
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
apiv1 "code.vikunja.io/api/pkg/routes/api/v1"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRegister(t *testing.T) {
|
||||
t.Run("normal register", func(t *testing.T) {
|
||||
rec, err := newTestRequest(t, http.MethodPost, apiv1.RegisterUser, `{
|
||||
"username": "newUser",
|
||||
"password": "1234",
|
||||
"email": "email@example.com"
|
||||
}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"username":"newUser"`)
|
||||
})
|
||||
t.Run("Empty payload", func(t *testing.T) {
|
||||
_, err := newTestRequest(t, http.MethodPost, apiv1.RegisterUser, `{}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeNoUsernamePassword)
|
||||
})
|
||||
t.Run("Empty username", func(t *testing.T) {
|
||||
_, err := newTestRequest(t, http.MethodPost, apiv1.RegisterUser, `{
|
||||
"username": "",
|
||||
"password": "1234",
|
||||
"email": "email@example.com"
|
||||
}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeNoUsernamePassword)
|
||||
})
|
||||
t.Run("Empty password", func(t *testing.T) {
|
||||
_, err := newTestRequest(t, http.MethodPost, apiv1.RegisterUser, `{
|
||||
"username": "newUser",
|
||||
"password": "",
|
||||
"email": "email@example.com"
|
||||
}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeNoUsernamePassword)
|
||||
})
|
||||
t.Run("Empty email", func(t *testing.T) {
|
||||
_, err := newTestRequest(t, http.MethodPost, apiv1.RegisterUser, `{
|
||||
"username": "newUser",
|
||||
"password": "1234",
|
||||
"email": ""
|
||||
}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeNoUsernamePassword)
|
||||
})
|
||||
t.Run("Already existing username", func(t *testing.T) {
|
||||
_, err := newTestRequest(t, http.MethodPost, apiv1.RegisterUser, `{
|
||||
"username": "user1",
|
||||
"password": "1234",
|
||||
"email": "email@example.com"
|
||||
}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrorCodeUsernameExists)
|
||||
})
|
||||
t.Run("Already existing email", func(t *testing.T) {
|
||||
_, err := newTestRequest(t, http.MethodPost, apiv1.RegisterUser, `{
|
||||
"username": "newUser",
|
||||
"password": "1234",
|
||||
"email": "user1@example.com"
|
||||
}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrorCodeUserEmailExists)
|
||||
})
|
||||
}
|
||||
569
pkg/integrations/task_test.go
Normal file
569
pkg/integrations/task_test.go
Normal file
@@ -0,0 +1,569 @@
|
||||
// Vikunja is a todo-list application to facilitate your life.
|
||||
// Copyright 2019 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package integrations
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/web/handler"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/url"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTask(t *testing.T) {
|
||||
testHandler := webHandlerTest{
|
||||
user: &testuser1,
|
||||
strFunc: func() handler.CObject {
|
||||
return &models.Task{}
|
||||
},
|
||||
t: t,
|
||||
}
|
||||
// Only run specific nested tests:
|
||||
// ^TestTask$/^Update$/^Update_task_items$/^Removing_Assignees_null$
|
||||
t.Run("ReadAll", func(t *testing.T) {
|
||||
t.Run("Normal", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(nil, nil)
|
||||
assert.NoError(t, err)
|
||||
// Not using assert.Equal to avoid having the tests break every time we add new fixtures
|
||||
assert.Contains(t, rec.Body.String(), `task #1`)
|
||||
assert.Contains(t, rec.Body.String(), `task #2`)
|
||||
assert.Contains(t, rec.Body.String(), `task #3`)
|
||||
assert.Contains(t, rec.Body.String(), `task #4`)
|
||||
assert.Contains(t, rec.Body.String(), `task #5`)
|
||||
assert.Contains(t, rec.Body.String(), `task #6`)
|
||||
assert.Contains(t, rec.Body.String(), `task #7`)
|
||||
assert.Contains(t, rec.Body.String(), `task #8`)
|
||||
assert.Contains(t, rec.Body.String(), `task #9`)
|
||||
assert.Contains(t, rec.Body.String(), `task #10`)
|
||||
assert.Contains(t, rec.Body.String(), `task #11`)
|
||||
assert.Contains(t, rec.Body.String(), `task #12`)
|
||||
assert.NotContains(t, rec.Body.String(), `task #13`)
|
||||
assert.NotContains(t, rec.Body.String(), `task #14`)
|
||||
// TODO: add more tasks, since the whole point of this is to get all tasks in all lists where the user
|
||||
// has at least read access
|
||||
})
|
||||
t.Run("Search", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(url.Values{"s": []string{"task #6"}}, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.NotContains(t, rec.Body.String(), `task #1`)
|
||||
assert.NotContains(t, rec.Body.String(), `task #2`)
|
||||
assert.NotContains(t, rec.Body.String(), `task #3`)
|
||||
assert.NotContains(t, rec.Body.String(), `task #4`)
|
||||
assert.NotContains(t, rec.Body.String(), `task #5`)
|
||||
assert.Contains(t, rec.Body.String(), `task #6`)
|
||||
assert.NotContains(t, rec.Body.String(), `task #7`)
|
||||
assert.NotContains(t, rec.Body.String(), `task #8`)
|
||||
assert.NotContains(t, rec.Body.String(), `task #9`)
|
||||
assert.NotContains(t, rec.Body.String(), `task #10`)
|
||||
assert.NotContains(t, rec.Body.String(), `task #11`)
|
||||
assert.NotContains(t, rec.Body.String(), `task #12`)
|
||||
assert.NotContains(t, rec.Body.String(), `task #13`)
|
||||
assert.NotContains(t, rec.Body.String(), `task #14`)
|
||||
})
|
||||
t.Run("Sort Order", func(t *testing.T) {
|
||||
// should equal priority desc
|
||||
t.Run("by priority", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(url.Values{"sort": []string{"priority"}}, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `[{"id":3,"text":"task #3 high prio","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":100,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":4,"text":"task #4 low prio","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":1,`)
|
||||
})
|
||||
t.Run("by priority desc", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(url.Values{"sort": []string{"prioritydesc"}}, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `[{"id":3,"text":"task #3 high prio","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":100,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":4,"text":"task #4 low prio","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":1,`)
|
||||
})
|
||||
t.Run("by priority asc", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(url.Values{"sort": []string{"priorityasc"}}, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `{"id":33,"text":"task #33 with percent done","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0.5,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":4,"text":"task #4 low prio","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":1,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":3,"text":"task #3 high prio","description":"","done":false,"doneAt":0,"dueDate":0,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":100,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}}]`)
|
||||
})
|
||||
// should equal duedate desc
|
||||
t.Run("by duedate", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(url.Values{"sort": []string{"duedate"}}, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `[{"id":5,"text":"task #5 higher due date","description":"","done":false,"doneAt":0,"dueDate":1543636724,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":6,"text":"task #6 lower due date"`)
|
||||
})
|
||||
t.Run("by duedate desc", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(url.Values{"sort": []string{"duedatedesc"}}, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `[{"id":5,"text":"task #5 higher due date","description":"","done":false,"doneAt":0,"dueDate":1543636724,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":6,"text":"task #6 lower due date"`)
|
||||
})
|
||||
t.Run("by duedate asc", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(url.Values{"sort": []string{"duedateasc"}}, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `{"id":6,"text":"task #6 lower due date","description":"","done":false,"doneAt":0,"dueDate":1543616724,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}},{"id":5,"text":"task #5 higher due date","description":"","done":false,"doneAt":0,"dueDate":1543636724,"reminderDates":null,"listID":1,"repeatAfter":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","percentDone":0,"related_tasks":{},"attachments":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":1,"username":"user1","avatarUrl":"111d68d06e2d317b5a59c2c6c5bad808","created":0,"updated":0}}]`)
|
||||
})
|
||||
t.Run("invalid parameter", func(t *testing.T) {
|
||||
// Invalid parameter should not sort at all
|
||||
rec, err := testHandler.testReadAllWithUser(url.Values{"sort": []string{"loremipsum"}}, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.NotContains(t, rec.Body.String(), `[{"id":3,"text":"task #3 high prio","description":"","done":false,"dueDate":0,"reminderDates":null,"repeatAfter":0,"priority":100,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","created":1543626724,"updated":1543626724,"createdBy":{"id":0,"username":"","email":"","created":0,"updated":0}},{"id":4,"text":"task #4 low prio","description":"","done":false,"dueDate":0,"reminderDates":null,"repeatAfter":0,"priority":1`)
|
||||
assert.NotContains(t, rec.Body.String(), `{"id":4,"text":"task #4 low prio","description":"","done":false,"dueDate":0,"reminderDates":null,"repeatAfter":0,"priority":1,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","created":1543626724,"updated":1543626724,"createdBy":{"id":0,"username":"","email":"","created":0,"updated":0}},{"id":3,"text":"task #3 high prio","description":"","done":false,"dueDate":0,"reminderDates":null,"repeatAfter":0,"priority":100,"startDate":0,"endDate":0,"assignees":null,"labels":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":0,"username":"","email":"","created":0,"updated":0}}]`)
|
||||
assert.NotContains(t, rec.Body.String(), `[{"id":5,"text":"task #5 higher due date","description":"","done":false,"dueDate":1543636724,"reminderDates":null,"repeatAfter":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","created":1543626724,"updated":1543626724,"createdBy":{"id":0,"username":"","email":"","created":0,"updated":0}},{"id":6,"text":"task #6 lower due date"`)
|
||||
assert.NotContains(t, rec.Body.String(), `{"id":6,"text":"task #6 lower due date","description":"","done":false,"dueDate":1543616724,"reminderDates":null,"repeatAfter":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"hexColor":"","created":1543626724,"updated":1543626724,"createdBy":{"id":0,"username":"","email":"","created":0,"updated":0}},{"id":5,"text":"task #5 higher due date","description":"","done":false,"dueDate":1543636724,"reminderDates":null,"repeatAfter":0,"priority":0,"startDate":0,"endDate":0,"assignees":null,"labels":null,"created":1543626724,"updated":1543626724,"createdBy":{"id":0,"username":"","email":"","created":0,"updated":0}}]`)
|
||||
})
|
||||
})
|
||||
t.Run("Date range", func(t *testing.T) {
|
||||
t.Run("start and end date", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(url.Values{"startdate": []string{"1540000000"}, "enddate": []string{"1544700001"}}, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.NotContains(t, rec.Body.String(), `task #1`)
|
||||
assert.NotContains(t, rec.Body.String(), `task #2`)
|
||||
assert.NotContains(t, rec.Body.String(), `task #3`)
|
||||
assert.NotContains(t, rec.Body.String(), `task #4`)
|
||||
assert.Contains(t, rec.Body.String(), `task #5`)
|
||||
assert.Contains(t, rec.Body.String(), `task #6`)
|
||||
assert.Contains(t, rec.Body.String(), `task #7`)
|
||||
assert.Contains(t, rec.Body.String(), `task #8`)
|
||||
assert.Contains(t, rec.Body.String(), `task #9`)
|
||||
assert.NotContains(t, rec.Body.String(), `task #10`)
|
||||
assert.NotContains(t, rec.Body.String(), `task #11`)
|
||||
assert.NotContains(t, rec.Body.String(), `task #12`)
|
||||
assert.NotContains(t, rec.Body.String(), `task #13`)
|
||||
assert.NotContains(t, rec.Body.String(), `task #14`)
|
||||
})
|
||||
t.Run("start date only", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(url.Values{"startdate": []string{"1540000000"}}, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.NotContains(t, rec.Body.String(), `task #1`)
|
||||
assert.NotContains(t, rec.Body.String(), `task #2`)
|
||||
assert.NotContains(t, rec.Body.String(), `task #3`)
|
||||
assert.NotContains(t, rec.Body.String(), `task #4`)
|
||||
assert.Contains(t, rec.Body.String(), `task #5`)
|
||||
assert.Contains(t, rec.Body.String(), `task #6`)
|
||||
assert.Contains(t, rec.Body.String(), `task #7`)
|
||||
assert.Contains(t, rec.Body.String(), `task #8`)
|
||||
assert.Contains(t, rec.Body.String(), `task #9`)
|
||||
assert.NotContains(t, rec.Body.String(), `task #10`)
|
||||
assert.NotContains(t, rec.Body.String(), `task #11`)
|
||||
assert.NotContains(t, rec.Body.String(), `task #12`)
|
||||
assert.NotContains(t, rec.Body.String(), `task #13`)
|
||||
assert.NotContains(t, rec.Body.String(), `task #14`)
|
||||
})
|
||||
t.Run("end date only", func(t *testing.T) {
|
||||
rec, err := testHandler.testReadAllWithUser(url.Values{"enddate": []string{"1544700001"}}, nil)
|
||||
assert.NoError(t, err)
|
||||
// If no start date but an end date is specified, this should be null
|
||||
// since we don't have any tasks in the fixtures with an end date >
|
||||
// the current date.
|
||||
assert.Equal(t, "null\n", rec.Body.String())
|
||||
})
|
||||
})
|
||||
})
|
||||
t.Run("Update", func(t *testing.T) {
|
||||
t.Run("Update task items", func(t *testing.T) {
|
||||
t.Run("Text", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"text":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"text":"Lorem Ipsum"`)
|
||||
assert.NotContains(t, rec.Body.String(), `"text":"task #1"`)
|
||||
})
|
||||
t.Run("Description", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"description":"Dolor sit amet"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"description":"Dolor sit amet"`)
|
||||
assert.NotContains(t, rec.Body.String(), `"description":"Lorem Ipsum"`)
|
||||
})
|
||||
t.Run("Description to empty", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"description":""}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"description":""`)
|
||||
assert.NotContains(t, rec.Body.String(), `"description":"Lorem Ipsum"`)
|
||||
})
|
||||
t.Run("Done", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"done":true}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"done":true`)
|
||||
assert.NotContains(t, rec.Body.String(), `"done":false`)
|
||||
})
|
||||
t.Run("Undone", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "2"}, `{"done":false}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"done":false`)
|
||||
assert.NotContains(t, rec.Body.String(), `"done":true`)
|
||||
})
|
||||
t.Run("Due date", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"dueDate": 123456}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"dueDate":123456`)
|
||||
assert.NotContains(t, rec.Body.String(), `"dueDate":0`)
|
||||
})
|
||||
t.Run("Due date unset", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "5"}, `{"dueDate": 0}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"dueDate":0`)
|
||||
assert.NotContains(t, rec.Body.String(), `"dueDate":1543636724`)
|
||||
})
|
||||
t.Run("Reminders", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"reminderDates": [1555508227,1555511000]}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"reminderDates":[1555508227,1555511000]`)
|
||||
assert.NotContains(t, rec.Body.String(), `"reminderDates": null`)
|
||||
})
|
||||
t.Run("Reminders unset to empty array", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "27"}, `{"reminderDates": []}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"reminderDates":null`)
|
||||
assert.NotContains(t, rec.Body.String(), `"reminderDates":[1543626724,1543626824]`)
|
||||
})
|
||||
t.Run("Reminders unset to null", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "27"}, `{"reminderDates": null}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"reminderDates":null`)
|
||||
assert.NotContains(t, rec.Body.String(), `"reminderDates":[1543626724,1543626824]`)
|
||||
})
|
||||
t.Run("Repeat after", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"repeatAfter":3600}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"repeatAfter":3600`)
|
||||
assert.NotContains(t, rec.Body.String(), `"repeatAfter":0`)
|
||||
})
|
||||
t.Run("Repeat after unset", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "28"}, `{"repeatAfter":0}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"repeatAfter":0`)
|
||||
assert.NotContains(t, rec.Body.String(), `"repeatAfter":3600`)
|
||||
})
|
||||
t.Run("Repeat after update done", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "28"}, `{"done":true}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"done":false`)
|
||||
assert.NotContains(t, rec.Body.String(), `"done":true`)
|
||||
})
|
||||
t.Run("Assignees", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"assignees":[{"id":1}]}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"assignees":[{"id":1`)
|
||||
assert.NotContains(t, rec.Body.String(), `"assignees":[]`)
|
||||
})
|
||||
t.Run("Removing Assignees empty array", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "30"}, `{"assignees":[]}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"assignees":null`)
|
||||
assert.NotContains(t, rec.Body.String(), `"assignees":[{"id":1`)
|
||||
})
|
||||
t.Run("Removing Assignees null", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "30"}, `{"assignees":null}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"assignees":null`)
|
||||
assert.NotContains(t, rec.Body.String(), `"assignees":[{"id":1`)
|
||||
})
|
||||
t.Run("Priority", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"priority":100}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"priority":100`)
|
||||
assert.NotContains(t, rec.Body.String(), `"priority":0`)
|
||||
})
|
||||
t.Run("Priority to 0", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "3"}, `{"priority":0}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"priority":0`)
|
||||
assert.NotContains(t, rec.Body.String(), `"priority":100`)
|
||||
})
|
||||
t.Run("Start date", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"startDate":1234567}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"startDate":1234567`)
|
||||
assert.NotContains(t, rec.Body.String(), `"startDate":0`)
|
||||
})
|
||||
t.Run("Start date unset", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "7"}, `{"startDate":0}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"startDate":0`)
|
||||
assert.NotContains(t, rec.Body.String(), `"startDate":1544600000`)
|
||||
})
|
||||
t.Run("End date", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"endDate":123456}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"endDate":123456`)
|
||||
assert.NotContains(t, rec.Body.String(), `"endDate":0`)
|
||||
})
|
||||
t.Run("End date unset", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "8"}, `{"endDate":0}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"endDate":0`)
|
||||
assert.NotContains(t, rec.Body.String(), `"endDate":1544700000`)
|
||||
})
|
||||
t.Run("Color", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"hexColor":"f0f0f0"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"hexColor":"f0f0f0"`)
|
||||
assert.NotContains(t, rec.Body.String(), `"hexColor":""`)
|
||||
})
|
||||
t.Run("Color unset", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "31"}, `{"hexColor":""}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"hexColor":""`)
|
||||
assert.NotContains(t, rec.Body.String(), `"hexColor":"f0f0f0"`)
|
||||
})
|
||||
t.Run("Percent Done", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "1"}, `{"percentDone":0.1}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"percentDone":0.1`)
|
||||
assert.NotContains(t, rec.Body.String(), `"percentDone":0,`)
|
||||
})
|
||||
t.Run("Percent Done unset", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "33"}, `{"percentDone":0}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"percentDone":0,`)
|
||||
assert.NotContains(t, rec.Body.String(), `"percentDone":0.1`)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Nonexisting", func(t *testing.T) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "99999"}, `{"text":"Lorem Ipsum"}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeTaskDoesNotExist)
|
||||
})
|
||||
t.Run("Rights check", func(t *testing.T) {
|
||||
t.Run("Forbidden", func(t *testing.T) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "14"}, `{"text":"Lorem Ipsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via Team readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "15"}, `{"text":"Lorem Ipsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via Team write", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "16"}, `{"text":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"text":"Lorem Ipsum"`)
|
||||
})
|
||||
t.Run("Shared Via Team admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "17"}, `{"text":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"text":"Lorem Ipsum"`)
|
||||
})
|
||||
|
||||
t.Run("Shared Via User readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "18"}, `{"text":"Lorem Ipsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via User write", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "19"}, `{"text":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"text":"Lorem Ipsum"`)
|
||||
})
|
||||
t.Run("Shared Via User admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "20"}, `{"text":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"text":"Lorem Ipsum"`)
|
||||
})
|
||||
|
||||
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "21"}, `{"text":"Lorem Ipsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "22"}, `{"text":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"text":"Lorem Ipsum"`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "23"}, `{"text":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"text":"Lorem Ipsum"`)
|
||||
})
|
||||
|
||||
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "24"}, `{"text":"Lorem Ipsum"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "25"}, `{"text":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"text":"Lorem Ipsum"`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testUpdateWithUser(nil, map[string]string{"listtask": "26"}, `{"text":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"text":"Lorem Ipsum"`)
|
||||
})
|
||||
})
|
||||
})
|
||||
t.Run("Delete", func(t *testing.T) {
|
||||
t.Run("Normal", func(t *testing.T) {
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "1"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
|
||||
})
|
||||
t.Run("Nonexisting", func(t *testing.T) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "99999"})
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeTaskDoesNotExist)
|
||||
})
|
||||
t.Run("Rights check", func(t *testing.T) {
|
||||
t.Run("Forbidden", func(t *testing.T) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "14"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via Team readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "15"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via Team write", func(t *testing.T) {
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "16"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
|
||||
})
|
||||
t.Run("Shared Via Team admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "17"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
|
||||
})
|
||||
|
||||
t.Run("Shared Via User readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "18"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via User write", func(t *testing.T) {
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "19"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
|
||||
})
|
||||
t.Run("Shared Via User admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "20"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
|
||||
})
|
||||
|
||||
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "21"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceTeam write", func(t *testing.T) {
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "22"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "23"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
|
||||
})
|
||||
|
||||
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "24"})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.(*echo.HTTPError).Message, `Forbidden`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceUser write", func(t *testing.T) {
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "25"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testDeleteWithUser(nil, map[string]string{"listtask": "26"})
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `Successfully deleted.`)
|
||||
})
|
||||
})
|
||||
})
|
||||
t.Run("Create", func(t *testing.T) {
|
||||
t.Run("Normal", func(t *testing.T) {
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "1"}, `{"text":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"text":"Lorem Ipsum"`)
|
||||
})
|
||||
t.Run("Nonexisting", func(t *testing.T) {
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "9999"}, `{"text":"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"}, `{"text":"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"}, `{"text":"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"}, `{"text":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"text":"Lorem Ipsum"`)
|
||||
})
|
||||
t.Run("Shared Via Team admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "8"}, `{"text":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"text":"Lorem Ipsum"`)
|
||||
})
|
||||
|
||||
t.Run("Shared Via User readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "9"}, `{"text":"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"}, `{"text":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"text":"Lorem Ipsum"`)
|
||||
})
|
||||
t.Run("Shared Via User admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "11"}, `{"text":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"text":"Lorem Ipsum"`)
|
||||
})
|
||||
|
||||
t.Run("Shared Via NamespaceTeam readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "12"}, `{"text":"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"}, `{"text":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"text":"Lorem Ipsum"`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceTeam admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "14"}, `{"text":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"text":"Lorem Ipsum"`)
|
||||
})
|
||||
|
||||
t.Run("Shared Via NamespaceUser readonly", func(t *testing.T) {
|
||||
_, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "15"}, `{"text":"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"}, `{"text":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"text":"Lorem Ipsum"`)
|
||||
})
|
||||
t.Run("Shared Via NamespaceUser admin", func(t *testing.T) {
|
||||
rec, err := testHandler.testCreateWithUser(nil, map[string]string{"list": "17"}, `{"text":"Lorem Ipsum"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"text":"Lorem Ipsum"`)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
32
pkg/integrations/token_test.go
Normal file
32
pkg/integrations/token_test.go
Normal file
@@ -0,0 +1,32 @@
|
||||
// Vikunja is a todo-list application to facilitate your life.
|
||||
// Copyright 2019 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package integrations
|
||||
|
||||
import (
|
||||
apiv1 "code.vikunja.io/api/pkg/routes/api/v1"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCheckToken(t *testing.T) {
|
||||
t.Run("Normal test", func(t *testing.T) {
|
||||
rec, err := newTestRequestWithUser(t, http.MethodPost, apiv1.CheckToken, &testuser1, "", nil, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `🍵`)
|
||||
})
|
||||
}
|
||||
60
pkg/integrations/user_change_password_test.go
Normal file
60
pkg/integrations/user_change_password_test.go
Normal file
@@ -0,0 +1,60 @@
|
||||
// Vikunja is a todo-list application to facilitate your life.
|
||||
// Copyright 2019 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package integrations
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
apiv1 "code.vikunja.io/api/pkg/routes/api/v1"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUserChangePassword(t *testing.T) {
|
||||
t.Run("Normal test", func(t *testing.T) {
|
||||
rec, err := newTestRequestWithUser(t, http.MethodPost, apiv1.UserChangePassword, &testuser1, `{
|
||||
"new_password": "12345",
|
||||
"old_password": "1234"
|
||||
}`, nil, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `The password was updated successfully.`)
|
||||
})
|
||||
t.Run("Wrong old password", func(t *testing.T) {
|
||||
_, err := newTestRequestWithUser(t, http.MethodPost, apiv1.UserChangePassword, &testuser1, `{
|
||||
"new_password": "12345",
|
||||
"old_password": "invalid"
|
||||
}`, nil, nil)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeWrongUsernameOrPassword)
|
||||
})
|
||||
t.Run("Empty old password", func(t *testing.T) {
|
||||
_, err := newTestRequestWithUser(t, http.MethodPost, apiv1.UserChangePassword, &testuser1, `{
|
||||
"new_password": "12345",
|
||||
"old_password": ""
|
||||
}`, nil, nil)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeEmptyOldPassword)
|
||||
})
|
||||
t.Run("Empty new password", func(t *testing.T) {
|
||||
_, err := newTestRequestWithUser(t, http.MethodPost, apiv1.UserChangePassword, &testuser1, `{
|
||||
"new_password": "",
|
||||
"old_password": "1234"
|
||||
}`, nil, nil)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeEmptyNewPassword)
|
||||
})
|
||||
}
|
||||
50
pkg/integrations/user_confirm_email_test.go
Normal file
50
pkg/integrations/user_confirm_email_test.go
Normal file
@@ -0,0 +1,50 @@
|
||||
// Vikunja is a todo-list application to facilitate your life.
|
||||
// Copyright 2019 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package integrations
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
apiv1 "code.vikunja.io/api/pkg/routes/api/v1"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUserConfirmEmail(t *testing.T) {
|
||||
t.Run("Normal test", func(t *testing.T) {
|
||||
rec, err := newTestRequest(t, http.MethodPost, apiv1.UserConfirmEmail, `{"token": "tiepiQueed8ahc7zeeFe1eveiy4Ein8osooxegiephauph2Ael"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `The email was confirmed successfully.`)
|
||||
})
|
||||
t.Run("Empty payload", func(t *testing.T) {
|
||||
_, err := newTestRequest(t, http.MethodPost, apiv1.UserConfirmEmail, `{}`)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, http.StatusPreconditionFailed, err.(*echo.HTTPError).Code)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeInvalidEmailConfirmToken)
|
||||
})
|
||||
t.Run("Empty token", func(t *testing.T) {
|
||||
_, err := newTestRequest(t, http.MethodPost, apiv1.UserConfirmEmail, `{"token": ""}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeInvalidEmailConfirmToken)
|
||||
})
|
||||
t.Run("Invalid token", func(t *testing.T) {
|
||||
_, err := newTestRequest(t, http.MethodPost, apiv1.UserConfirmEmail, `{"token": "invalidToken"}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeInvalidEmailConfirmToken)
|
||||
})
|
||||
}
|
||||
45
pkg/integrations/user_list_test.go
Normal file
45
pkg/integrations/user_list_test.go
Normal file
@@ -0,0 +1,45 @@
|
||||
// Vikunja is a todo-list application to facilitate your life.
|
||||
// Copyright 2019 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package integrations
|
||||
|
||||
import (
|
||||
apiv1 "code.vikunja.io/api/pkg/routes/api/v1"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUserList(t *testing.T) {
|
||||
t.Run("Normal test", func(t *testing.T) {
|
||||
rec, err := newTestRequestWithUser(t, http.MethodPost, apiv1.UserList, &testuser1, "", nil, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `user1`)
|
||||
assert.Contains(t, rec.Body.String(), `user2`)
|
||||
assert.Contains(t, rec.Body.String(), `user3`)
|
||||
assert.Contains(t, rec.Body.String(), `user4`)
|
||||
assert.Contains(t, rec.Body.String(), `user5`)
|
||||
})
|
||||
t.Run("Search for user3", func(t *testing.T) {
|
||||
rec, err := newTestRequestWithUser(t, http.MethodPost, apiv1.UserList, &testuser1, "", map[string][]string{"s": {"user3"}}, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `user3`)
|
||||
assert.NotContains(t, rec.Body.String(), `user1`)
|
||||
assert.NotContains(t, rec.Body.String(), `user2`)
|
||||
assert.NotContains(t, rec.Body.String(), `user4`)
|
||||
assert.NotContains(t, rec.Body.String(), `user5`)
|
||||
})
|
||||
}
|
||||
49
pkg/integrations/user_password_request_token_test.go
Normal file
49
pkg/integrations/user_password_request_token_test.go
Normal file
@@ -0,0 +1,49 @@
|
||||
// Vikunja is a todo-list application to facilitate your life.
|
||||
// Copyright 2019 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package integrations
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
apiv1 "code.vikunja.io/api/pkg/routes/api/v1"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUserRequestResetPasswordToken(t *testing.T) {
|
||||
t.Run("Normal requesting a password reset token", func(t *testing.T) {
|
||||
rec, err := newTestRequest(t, http.MethodPost, apiv1.UserRequestResetPasswordToken, `{"email": "user1@example.com"}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `Token was sent.`)
|
||||
})
|
||||
t.Run("Empty payload", func(t *testing.T) {
|
||||
_, err := newTestRequest(t, http.MethodPost, apiv1.UserRequestResetPasswordToken, `{}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeNoUsernamePassword)
|
||||
})
|
||||
t.Run("Invalid email address", func(t *testing.T) {
|
||||
_, err := newTestRequest(t, http.MethodPost, apiv1.UserRequestResetPasswordToken, `{"email": "user1example.com"}`)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, http.StatusBadRequest, err.(*echo.HTTPError).Code)
|
||||
})
|
||||
t.Run("No user with that email address", func(t *testing.T) {
|
||||
_, err := newTestRequest(t, http.MethodPost, apiv1.UserRequestResetPasswordToken, `{"email": "user1000@example.com"}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeUserDoesNotExist)
|
||||
})
|
||||
}
|
||||
58
pkg/integrations/user_password_reset_test.go
Normal file
58
pkg/integrations/user_password_reset_test.go
Normal file
@@ -0,0 +1,58 @@
|
||||
// Vikunja is a todo-list application to facilitate your life.
|
||||
// Copyright 2019 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package integrations
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
apiv1 "code.vikunja.io/api/pkg/routes/api/v1"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUserPasswordReset(t *testing.T) {
|
||||
t.Run("Normal password reset test", func(t *testing.T) {
|
||||
rec, err := newTestRequest(t, http.MethodPost, apiv1.UserResetPassword, `{
|
||||
"new_password": "1234",
|
||||
"token": "passwordresettesttoken"
|
||||
}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `The password was updated successfully.`)
|
||||
})
|
||||
t.Run("Empty payload", func(t *testing.T) {
|
||||
_, err := newTestRequest(t, http.MethodPost, apiv1.UserResetPassword, `{}`)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, http.StatusBadRequest, err.(*echo.HTTPError).Code)
|
||||
})
|
||||
t.Run("No new password", func(t *testing.T) {
|
||||
_, err := newTestRequest(t, http.MethodPost, apiv1.UserResetPassword, `{
|
||||
"new_password": "",
|
||||
"token": "passwordresettesttoken"
|
||||
}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeNoUsernamePassword)
|
||||
})
|
||||
t.Run("Invalid password reset token", func(t *testing.T) {
|
||||
_, err := newTestRequest(t, http.MethodPost, apiv1.UserResetPassword, `{
|
||||
"new_password": "1234",
|
||||
"token": "invalidtoken"
|
||||
}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeInvalidPasswordResetToken)
|
||||
})
|
||||
}
|
||||
34
pkg/integrations/user_show_test.go
Normal file
34
pkg/integrations/user_show_test.go
Normal file
@@ -0,0 +1,34 @@
|
||||
// Vikunja is a todo-list application to facilitate your life.
|
||||
// Copyright 2019 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package integrations
|
||||
|
||||
import (
|
||||
apiv1 "code.vikunja.io/api/pkg/routes/api/v1"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUserShow(t *testing.T) {
|
||||
t.Run("Normal test", func(t *testing.T) {
|
||||
rec, err := newTestRequestWithUser(t, http.MethodPost, apiv1.UserShow, &testuser1, "", nil, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"id":1`)
|
||||
assert.Contains(t, rec.Body.String(), `"username":"user1"`)
|
||||
assert.NotContains(t, rec.Body.String(), `"email":""`)
|
||||
})
|
||||
}
|
||||
@@ -17,20 +17,157 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"github.com/op/go-logging"
|
||||
"github.com/spf13/viper"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Log is the handler for the logger
|
||||
var Log = logging.MustGetLogger("vikunja")
|
||||
// ErrFmt holds the format for all the console logging
|
||||
const ErrFmt = `${time_rfc3339_nano}: ${level} ` + "\t" + `▶ ${prefix} ${short_file}:${line}`
|
||||
|
||||
var format = logging.MustStringFormatter(
|
||||
`%{color}%{time:2006-01-02 15:04:05.000} %{shortfunc} ▶ %{level:.4s} %{id:03x}%{color:reset} %{message}`,
|
||||
)
|
||||
// WebFmt holds the format for all logging related to web requests
|
||||
const WebFmt = `${time_rfc3339_nano}: WEB ` + "\t" + `▶ ${remote_ip} ${id} ${method} ${status} ${uri} ${latency_human} - ${user_agent}`
|
||||
|
||||
// Fmt is the general log format
|
||||
const Fmt = `%{color}%{time:` + time.RFC3339Nano + `}: %{level}` + "\t" + `▶ %{shortpkg}/%{shortfunc} %{id:03x}%{color:reset} %{message}`
|
||||
|
||||
// loginstance is the instance of the logger which is used under the hood to log
|
||||
var logInstance = logging.MustGetLogger("vikunja")
|
||||
|
||||
// InitLogger initializes the global log handler
|
||||
func InitLogger() {
|
||||
backend := logging.NewLogBackend(os.Stderr, "", 0)
|
||||
backendFormatter := logging.NewBackendFormatter(backend, format)
|
||||
logging.SetBackend(backendFormatter)
|
||||
if !config.LogEnabled.GetBool() {
|
||||
// Disable all logging when loggin in general is disabled, overwriting everything a user might have set.
|
||||
config.LogErrors.Set("off")
|
||||
config.LogStandard.Set("off")
|
||||
config.LogDatabase.Set("off")
|
||||
config.LogHTTP.Set("off")
|
||||
config.LogEcho.Set("off")
|
||||
return
|
||||
}
|
||||
|
||||
// This show correct caller functions
|
||||
logInstance.ExtraCalldepth = 1
|
||||
|
||||
if config.LogErrors.GetString() == "file" || config.LogStandard.GetString() == "file" {
|
||||
err := os.Mkdir(config.LogPath.GetString(), 0744)
|
||||
if err != nil && !os.IsExist(err) {
|
||||
log.Fatal("Could not create log folder: ", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
var logBackends []logging.Backend
|
||||
|
||||
// We define our two backends
|
||||
if config.LogStandard.GetString() != "off" {
|
||||
stdWriter := GetLogWriter("standard")
|
||||
stdBackend := logging.NewLogBackend(stdWriter, "", 0)
|
||||
|
||||
// Set the standard backend
|
||||
logBackends = append(logBackends, logging.NewBackendFormatter(stdBackend, logging.MustStringFormatter(Fmt+"\n")))
|
||||
}
|
||||
|
||||
if config.LogErrors.GetString() != "off" {
|
||||
errWriter := GetLogWriter("error")
|
||||
errBackend := logging.NewLogBackend(errWriter, "", 0)
|
||||
|
||||
// Only warnings and more severe messages should go to the error backend
|
||||
errBackendLeveled := logging.AddModuleLevel(errBackend)
|
||||
errBackendLeveled.SetLevel(logging.WARNING, "")
|
||||
logBackends = append(logBackends, errBackendLeveled)
|
||||
}
|
||||
|
||||
// Set our backends
|
||||
logging.SetBackend(logBackends...)
|
||||
}
|
||||
|
||||
// GetLogWriter returns the writer to where the normal log goes, depending on the config
|
||||
func GetLogWriter(logfile string) (writer io.Writer) {
|
||||
writer = os.Stdout // Set the default case to prevent nil pointer panics
|
||||
switch viper.GetString("log." + logfile) {
|
||||
case "file":
|
||||
f, err := os.OpenFile(config.LogPath.GetString()+"/"+logfile+".log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
writer = f
|
||||
case "stderr":
|
||||
writer = os.Stderr
|
||||
case "stdout":
|
||||
default:
|
||||
writer = os.Stdout
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetLogger returns the logging instance. DO NOT USE THIS TO LOG STUFF.
|
||||
func GetLogger() *logging.Logger {
|
||||
return logInstance
|
||||
}
|
||||
|
||||
/////
|
||||
// The following functions are to be used as an "eye-candy", so one can just write log.Error() instead of log.Log.Error()
|
||||
|
||||
// Debug is for debug messages
|
||||
func Debug(args ...interface{}) {
|
||||
logInstance.Debug(args)
|
||||
}
|
||||
|
||||
// Debugf is for debug messages
|
||||
func Debugf(format string, args ...interface{}) {
|
||||
logInstance.Debugf(format, args...)
|
||||
}
|
||||
|
||||
// Info is for info messages
|
||||
func Info(args ...interface{}) {
|
||||
logInstance.Info(args)
|
||||
}
|
||||
|
||||
// Infof is for info messages
|
||||
func Infof(format string, args ...interface{}) {
|
||||
logInstance.Infof(format, args...)
|
||||
}
|
||||
|
||||
// Error is for error messages
|
||||
func Error(args ...interface{}) {
|
||||
logInstance.Error(args)
|
||||
}
|
||||
|
||||
// Errorf is for error messages
|
||||
func Errorf(format string, args ...interface{}) {
|
||||
logInstance.Errorf(format, args...)
|
||||
}
|
||||
|
||||
// Warning is for warning messages
|
||||
func Warning(args ...interface{}) {
|
||||
logInstance.Warning(args)
|
||||
}
|
||||
|
||||
// Warningf is for warning messages
|
||||
func Warningf(format string, args ...interface{}) {
|
||||
logInstance.Warningf(format, args...)
|
||||
}
|
||||
|
||||
// Critical is for critical messages
|
||||
func Critical(args ...interface{}) {
|
||||
logInstance.Critical(args)
|
||||
}
|
||||
|
||||
// Criticalf is for critical messages
|
||||
func Criticalf(format string, args ...interface{}) {
|
||||
logInstance.Criticalf(format, args...)
|
||||
}
|
||||
|
||||
// Fatal is for fatal messages
|
||||
func Fatal(args ...interface{}) {
|
||||
logInstance.Fatal(args)
|
||||
}
|
||||
|
||||
// Fatalf is for fatal messages
|
||||
func Fatalf(format string, args ...interface{}) {
|
||||
logInstance.Fatalf(format, args...)
|
||||
}
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
package mail
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"crypto/tls"
|
||||
"github.com/spf13/viper"
|
||||
"gopkg.in/gomail.v2"
|
||||
"time"
|
||||
)
|
||||
@@ -29,20 +29,20 @@ var Queue chan *gomail.Message
|
||||
|
||||
// StartMailDaemon starts the mail daemon
|
||||
func StartMailDaemon() {
|
||||
Queue = make(chan *gomail.Message, viper.GetInt("mailer.queuelength"))
|
||||
Queue = make(chan *gomail.Message, config.MailerQueuelength.GetInt())
|
||||
|
||||
if !viper.GetBool("mailer.enabled") {
|
||||
if !config.MailerEnabled.GetBool() {
|
||||
return
|
||||
}
|
||||
|
||||
if viper.GetString("mailer.host") == "" {
|
||||
log.Log.Warning("Mailer seems to be not configured! Please see the config docs for more details.")
|
||||
if config.MailerHost.GetString() == "" {
|
||||
log.Warning("Mailer seems to be not configured! Please see the config docs for more details.")
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
d := gomail.NewDialer(viper.GetString("mailer.host"), viper.GetInt("mailer.port"), viper.GetString("mailer.username"), viper.GetString("mailer.password"))
|
||||
d.TLSConfig = &tls.Config{InsecureSkipVerify: viper.GetBool("mailer.skiptlsverify")}
|
||||
d := gomail.NewDialer(config.MailerHost.GetString(), config.MailerPort.GetInt(), config.MailerUsername.GetString(), config.MailerPassword.GetString())
|
||||
d.TLSConfig = &tls.Config{InsecureSkipVerify: config.MailerSkipTLSVerify.GetBool()}
|
||||
|
||||
var s gomail.SendCloser
|
||||
var err error
|
||||
@@ -55,21 +55,21 @@ func StartMailDaemon() {
|
||||
}
|
||||
if !open {
|
||||
if s, err = d.Dial(); err != nil {
|
||||
log.Log.Error("Error during connect to smtp server: %s", err)
|
||||
log.Error("Error during connect to smtp server: %s", err)
|
||||
}
|
||||
open = true
|
||||
}
|
||||
if err := gomail.Send(s, m); err != nil {
|
||||
log.Log.Error("Error when sending mail: %s", err)
|
||||
log.Error("Error when sending mail: %s", err)
|
||||
}
|
||||
// Close the connection to the SMTP server if no email was sent in
|
||||
// the last 30 seconds.
|
||||
case <-time.After(viper.GetDuration("mailer.queuetimeout") * time.Second):
|
||||
case <-time.After(config.MailerQueueTimeout.GetDuration() * time.Second):
|
||||
if open {
|
||||
if err := s.Close(); err != nil {
|
||||
log.Log.Error("Error closing the mail server connection: %s\n", err)
|
||||
log.Error("Error closing the mail server connection: %s\n", err)
|
||||
}
|
||||
log.Log.Infof("Closed connection to mailserver")
|
||||
log.Infof("Closed connection to mailserver")
|
||||
open = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,11 +18,13 @@ package mail
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/static"
|
||||
"code.vikunja.io/api/pkg/utils"
|
||||
"github.com/labstack/gommon/log"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/shurcooL/httpfs/html/vfstemplate"
|
||||
"gopkg.in/gomail.v2"
|
||||
"text/template"
|
||||
"html/template"
|
||||
)
|
||||
|
||||
// Opts holds infos for a mail
|
||||
@@ -54,7 +56,7 @@ type header struct {
|
||||
// SendMail puts a mail in the queue
|
||||
func SendMail(opts *Opts) {
|
||||
m := gomail.NewMessage()
|
||||
m.SetHeader("From", viper.GetString("mailer.fromemail"))
|
||||
m.SetHeader("From", config.MailerFromEmail.GetString())
|
||||
m.SetHeader("To", opts.To)
|
||||
m.SetHeader("Subject", opts.Subject)
|
||||
for _, h := range opts.Headers {
|
||||
@@ -84,22 +86,24 @@ func SendMailWithTemplate(to, subject, tpl string, data map[string]interface{})
|
||||
var htmlContent bytes.Buffer
|
||||
var plainContent bytes.Buffer
|
||||
|
||||
t := &Template{
|
||||
Templates: template.Must(template.ParseGlob(viper.GetString("service.rootpath") + "/templates/mail/*.tmpl")),
|
||||
t, err := vfstemplate.ParseGlob(static.Templates, nil, "*.tmpl")
|
||||
if err != nil {
|
||||
log.Errorf("SendMailWithTemplate: ParseGlob: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
boundary := "np" + utils.MakeRandomString(13)
|
||||
|
||||
data["Boundary"] = boundary
|
||||
data["FrontendURL"] = viper.GetString("service.frontendurl")
|
||||
data["FrontendURL"] = config.ServiceFrontendurl.GetString()
|
||||
|
||||
if err := t.Templates.ExecuteTemplate(&htmlContent, tpl+".html.tmpl", data); err != nil {
|
||||
log.Error(3, "Template: %v", err)
|
||||
if err := t.ExecuteTemplate(&htmlContent, tpl+".html.tmpl", data); err != nil {
|
||||
log.Errorf("ExecuteTemplate: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := t.Templates.ExecuteTemplate(&plainContent, tpl+".plain.tmpl", data); err != nil {
|
||||
log.Error(3, "Template: %v", err)
|
||||
if err := t.ExecuteTemplate(&plainContent, tpl+".plain.tmpl", data); err != nil {
|
||||
log.Errorf("ExecuteTemplate: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ func init() {
|
||||
|
||||
allActiveUsers, err := GetActiveUsers()
|
||||
if err != nil {
|
||||
log.Log.Error(err.Error())
|
||||
log.Error(err.Error())
|
||||
}
|
||||
activeUsersCount := 0
|
||||
for _, u := range allActiveUsers {
|
||||
|
||||
@@ -17,14 +17,15 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/red"
|
||||
"github.com/go-redis/redis"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var r = red.GetRedis()
|
||||
var r *redis.Client
|
||||
|
||||
const (
|
||||
// ListCountKey is the name of the key in which we save the list count
|
||||
@@ -43,7 +44,10 @@ const (
|
||||
TeamCountKey = `teamcount`
|
||||
)
|
||||
|
||||
func init() {
|
||||
// InitMetrics Initializes the metrics
|
||||
func InitMetrics() {
|
||||
r = red.GetRedis()
|
||||
|
||||
// Register total list count metric
|
||||
promauto.NewGaugeFunc(prometheus.GaugeOpts{
|
||||
Name: "vikunja_list_count",
|
||||
@@ -108,16 +112,16 @@ func SetCount(count int64, key string) error {
|
||||
|
||||
// UpdateCount updates a count with a given amount
|
||||
func UpdateCount(update int64, key string) {
|
||||
if !viper.GetBool("service.enablemetrics") {
|
||||
if !config.ServiceEnableMetrics.GetBool() {
|
||||
return
|
||||
}
|
||||
oldtotal, err := GetCount(key)
|
||||
if err != nil {
|
||||
log.Log.Error(err.Error())
|
||||
log.Error(err.Error())
|
||||
}
|
||||
|
||||
err = SetCount(oldtotal+update, key)
|
||||
if err != nil {
|
||||
log.Log.Error(err.Error())
|
||||
log.Error(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
44
pkg/migration/20190324205606.go
Normal file
44
pkg/migration/20190324205606.go
Normal file
@@ -0,0 +1,44 @@
|
||||
// Vikunja is a todo-list application to facilitate your life.
|
||||
// Copyright 2019 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package migration
|
||||
|
||||
import (
|
||||
"github.com/go-xorm/xorm"
|
||||
"src.techknowlogick.com/xormigrate"
|
||||
)
|
||||
|
||||
// Used for rollback
|
||||
type tasksReminderDateMigration20190324205606 struct {
|
||||
ReminderUnix int64 `xorm:"int(11) INDEX"`
|
||||
}
|
||||
|
||||
func (tasksReminderDateMigration20190324205606) TableName() string {
|
||||
return "tasks"
|
||||
}
|
||||
|
||||
func init() {
|
||||
migrations = append(migrations, &xormigrate.Migration{
|
||||
ID: "20190324205606",
|
||||
Description: "Remove reminders_unix from tasks",
|
||||
Migrate: func(tx *xorm.Engine) error {
|
||||
return dropTableColum(tx, "tasks", "reminders_unix")
|
||||
},
|
||||
Rollback: func(tx *xorm.Engine) error {
|
||||
return tx.Sync2(tasksReminderDateMigration20190324205606{})
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// Vikunja is a todo-list application to facilitate your life.
|
||||
// Copyright 2018 Vikunja and contributors. All rights reserved.
|
||||
// Copyright 2019 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
|
||||
@@ -14,30 +14,31 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package models
|
||||
package migration
|
||||
|
||||
// UserRight defines the rights users can have for lists/namespaces
|
||||
type UserRight int
|
||||
|
||||
// define unknown user right
|
||||
const (
|
||||
UserRightUnknown = -1
|
||||
import (
|
||||
"github.com/go-xorm/xorm"
|
||||
"src.techknowlogick.com/xormigrate"
|
||||
)
|
||||
|
||||
// Enumerate all the user rights
|
||||
const (
|
||||
// Can read lists in a User
|
||||
UserRightRead UserRight = iota
|
||||
// Can write tasks in a User like lists and todo tasks. Cannot create new lists.
|
||||
UserRightWrite
|
||||
// Can manage a list/namespace, can do everything
|
||||
UserRightAdmin
|
||||
)
|
||||
|
||||
func (r UserRight) isValid() error {
|
||||
if r != UserRightAdmin && r != UserRightRead && r != UserRightWrite {
|
||||
return ErrInvalidUserRight{r}
|
||||
}
|
||||
|
||||
return nil
|
||||
// Used for rollback
|
||||
type teamMembersMigration20190328074430 struct {
|
||||
Updated int64 `xorm:"updated"`
|
||||
}
|
||||
|
||||
func (teamMembersMigration20190328074430) TableName() string {
|
||||
return "team_members"
|
||||
}
|
||||
|
||||
func init() {
|
||||
migrations = append(migrations, &xormigrate.Migration{
|
||||
ID: "20190328074430",
|
||||
Description: "Remove updated from team_members",
|
||||
Migrate: func(tx *xorm.Engine) error {
|
||||
return dropTableColum(tx, "team_members", "updated")
|
||||
},
|
||||
Rollback: func(tx *xorm.Engine) error {
|
||||
return tx.Sync2(teamMembersMigration20190328074430{})
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// Vikunja is a todo-list application to facilitate your life.
|
||||
// Copyright 2018 Vikunja and contributors. All rights reserved.
|
||||
// Copyright 2019 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
|
||||
@@ -14,22 +14,30 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package models
|
||||
package migration
|
||||
|
||||
import (
|
||||
"gopkg.in/testfixtures.v2"
|
||||
"github.com/go-xorm/xorm"
|
||||
"src.techknowlogick.com/xormigrate"
|
||||
)
|
||||
|
||||
var fixtures *testfixtures.Context
|
||||
|
||||
// InitFixtures initialize test fixtures for a test database
|
||||
func InitFixtures(helper testfixtures.Helper, dir string) (err error) {
|
||||
testfixtures.SkipDatabaseNameCheck(true)
|
||||
fixtures, err = testfixtures.NewFolder(x.DB().DB, helper, dir)
|
||||
return err
|
||||
type listTask20190430111111 struct {
|
||||
HexColor string `xorm:"varchar(6) null" json:"hexColor" valid:"runelength(0|6)" maxLength:"6"`
|
||||
}
|
||||
|
||||
// LoadFixtures load fixtures for a test database
|
||||
func LoadFixtures() error {
|
||||
return fixtures.Load()
|
||||
func (listTask20190430111111) TableName() string {
|
||||
return "tasks"
|
||||
}
|
||||
|
||||
func init() {
|
||||
migrations = append(migrations, &xormigrate.Migration{
|
||||
ID: "20190430111111",
|
||||
Description: "Add task color",
|
||||
Migrate: func(tx *xorm.Engine) error {
|
||||
return tx.Sync2(listTask20190430111111{})
|
||||
},
|
||||
Rollback: func(tx *xorm.Engine) error {
|
||||
return dropTableColum(tx, "tasks", "hex_color")
|
||||
},
|
||||
})
|
||||
}
|
||||
84
pkg/migration/20190511202210.go
Normal file
84
pkg/migration/20190511202210.go
Normal file
@@ -0,0 +1,84 @@
|
||||
// Vikunja is a todo-list application to facilitate your life.
|
||||
// Copyright 2019 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package migration
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/utils"
|
||||
"github.com/go-xorm/xorm"
|
||||
"src.techknowlogick.com/xormigrate"
|
||||
)
|
||||
|
||||
type listTask20190511202210 struct {
|
||||
ID int64 `xorm:"int(11) autoincr not null unique pk" json:"id" param:"listtask"`
|
||||
Text string `xorm:"varchar(250) not null" json:"text" valid:"runelength(3|250)" minLength:"3" maxLength:"250"`
|
||||
Description string `xorm:"varchar(250)" json:"description" valid:"runelength(0|250)" maxLength:"250"`
|
||||
Done bool `xorm:"INDEX null" json:"done"`
|
||||
DoneAtUnix int64 `xorm:"INDEX null" json:"doneAt"`
|
||||
DueDateUnix int64 `xorm:"int(11) INDEX null" json:"dueDate"`
|
||||
RemindersUnix []int64 `xorm:"JSON TEXT null" json:"reminderDates"`
|
||||
CreatedByID int64 `xorm:"int(11) not null" json:"-"` // ID of the user who put that task on the list
|
||||
ListID int64 `xorm:"int(11) INDEX not null" json:"listID" param:"list"`
|
||||
RepeatAfter int64 `xorm:"int(11) INDEX null" json:"repeatAfter"`
|
||||
ParentTaskID int64 `xorm:"int(11) INDEX null" json:"parentTaskID"`
|
||||
Priority int64 `xorm:"int(11) null" json:"priority"`
|
||||
StartDateUnix int64 `xorm:"int(11) INDEX null" json:"startDate" query:"-"`
|
||||
EndDateUnix int64 `xorm:"int(11) INDEX null" json:"endDate" query:"-"`
|
||||
HexColor string `xorm:"varchar(6) null" json:"hexColor" valid:"runelength(0|6)" maxLength:"6"`
|
||||
UID string `xorm:"varchar(250) null" json:"-"`
|
||||
Sorting string `xorm:"-" json:"-" query:"sort"` // Parameter to sort by
|
||||
StartDateSortUnix int64 `xorm:"-" json:"-" query:"startdate"`
|
||||
EndDateSortUnix int64 `xorm:"-" json:"-" query:"enddate"`
|
||||
Created int64 `xorm:"created not null" json:"created"`
|
||||
Updated int64 `xorm:"updated not null" json:"updated"`
|
||||
}
|
||||
|
||||
func (listTask20190511202210) TableName() string {
|
||||
return "tasks"
|
||||
}
|
||||
|
||||
func init() {
|
||||
migrations = append(migrations, &xormigrate.Migration{
|
||||
ID: "20190511202210",
|
||||
Description: "Add task uid",
|
||||
Migrate: func(tx *xorm.Engine) error {
|
||||
err := tx.Sync2(listTask20190511202210{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get all tasks and generate a random uid for them
|
||||
var allTasks []*listTask20190511202210
|
||||
err = tx.Find(&allTasks)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, t := range allTasks {
|
||||
t.UID = utils.MakeRandomString(40)
|
||||
_, err = tx.Where("id = ?", t.ID).Cols("uid").Update(t)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
Rollback: func(tx *xorm.Engine) error {
|
||||
return dropTableColum(tx, "tasks", "uid")
|
||||
},
|
||||
})
|
||||
}
|
||||
43
pkg/migration/20190514192749.go
Normal file
43
pkg/migration/20190514192749.go
Normal file
@@ -0,0 +1,43 @@
|
||||
// Vikunja is a todo-list application to facilitate your life.
|
||||
// Copyright 2019 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package migration
|
||||
|
||||
import (
|
||||
"github.com/go-xorm/xorm"
|
||||
"src.techknowlogick.com/xormigrate"
|
||||
)
|
||||
|
||||
type listTask20190514192749 struct {
|
||||
DoneAtUnix int64 `xorm:"INDEX null" json:"doneAt"`
|
||||
}
|
||||
|
||||
func (listTask20190514192749) TableName() string {
|
||||
return "tasks"
|
||||
}
|
||||
|
||||
func init() {
|
||||
migrations = append(migrations, &xormigrate.Migration{
|
||||
ID: "20190514192749",
|
||||
Description: "Add task done at",
|
||||
Migrate: func(tx *xorm.Engine) error {
|
||||
return tx.Sync2(listTask20190514192749{})
|
||||
},
|
||||
Rollback: func(tx *xorm.Engine) error {
|
||||
return dropTableColum(tx, "tasks", "done_at_unix")
|
||||
},
|
||||
})
|
||||
}
|
||||
70
pkg/migration/20190524205441.go
Normal file
70
pkg/migration/20190524205441.go
Normal file
@@ -0,0 +1,70 @@
|
||||
// Vikunja is a todo-list application to facilitate your life.
|
||||
// Copyright 2019 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package migration
|
||||
|
||||
import (
|
||||
"github.com/go-xorm/xorm"
|
||||
"src.techknowlogick.com/xormigrate"
|
||||
)
|
||||
|
||||
type taskReminder20190524205441 struct {
|
||||
ID int64 `xorm:"int(11) autoincr not null unique pk"`
|
||||
TaskID int64 `xorm:"int(11) not null INDEX"`
|
||||
ReminderUnix int64 `xorm:"int(11) not null INDEX"`
|
||||
Created int64 `xorm:"created not null"`
|
||||
}
|
||||
|
||||
// TableName returns a pretty table name
|
||||
func (taskReminder20190524205441) TableName() string {
|
||||
return "task_reminders"
|
||||
}
|
||||
|
||||
func init() {
|
||||
migrations = append(migrations, &xormigrate.Migration{
|
||||
ID: "20190524205441",
|
||||
Description: "Add extra table for reminders",
|
||||
Migrate: func(tx *xorm.Engine) error {
|
||||
err := tx.Sync2(taskReminder20190524205441{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// get all current reminders and put them into the new table
|
||||
var allTasks []*listTask20190511202210
|
||||
err = tx.Find(&allTasks)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reminders := []*taskReminder20190524205441{}
|
||||
for _, t := range allTasks {
|
||||
for _, reminder := range t.RemindersUnix {
|
||||
reminders = append(reminders, &taskReminder20190524205441{TaskID: t.ID, ReminderUnix: reminder})
|
||||
}
|
||||
}
|
||||
_, err = tx.Insert(reminders)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return dropTableColum(tx, "tasks", "reminders_unix")
|
||||
},
|
||||
Rollback: func(tx *xorm.Engine) error {
|
||||
return tx.DropTables(taskReminder20190524205441{})
|
||||
},
|
||||
})
|
||||
}
|
||||
48
pkg/migration/20190718200716.go
Normal file
48
pkg/migration/20190718200716.go
Normal file
@@ -0,0 +1,48 @@
|
||||
// Vikunja is a todo-list application to facilitate your life.
|
||||
// Copyright 2019 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package migration
|
||||
|
||||
import (
|
||||
"github.com/go-xorm/xorm"
|
||||
"src.techknowlogick.com/xormigrate"
|
||||
)
|
||||
|
||||
func init() {
|
||||
tables := []string{"namespaces", "labels", "teams", "tasks", "list"}
|
||||
migrations = append(migrations, &xormigrate.Migration{
|
||||
ID: "20190718200716",
|
||||
Description: "Update description definition to be longtext",
|
||||
Migrate: func(tx *xorm.Engine) error {
|
||||
for _, table := range tables {
|
||||
err := modifyColumn(tx, table, "description", "longtext null")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
Rollback: func(tx *xorm.Engine) error {
|
||||
for _, table := range tables {
|
||||
err := modifyColumn(tx, table, "description", "varchar(1000) null")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
})
|
||||
}
|
||||
53
pkg/migration/20190818210133.go
Normal file
53
pkg/migration/20190818210133.go
Normal file
@@ -0,0 +1,53 @@
|
||||
// Copyright 2019 Vikunja and contriubtors. All rights reserved.
|
||||
//
|
||||
// This file is part of Vikunja.
|
||||
//
|
||||
// Vikunja is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Vikunja is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Vikunja. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package migration
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"github.com/go-xorm/xorm"
|
||||
"src.techknowlogick.com/xormigrate"
|
||||
)
|
||||
|
||||
type linkSharing20190818210133 struct {
|
||||
ID int64 `xorm:"int(11) autoincr not null unique pk" json:"id"`
|
||||
Hash string `xorm:"varchar(40) not null unique" json:"hash" param:"hash"`
|
||||
ListID int64 `xorm:"int(11) not null" json:"list_id"`
|
||||
Right models.Right `xorm:"int(11) INDEX not null default 0" json:"right" valid:"length(0|2)" maximum:"2" default:"0"`
|
||||
SharingType models.SharingType `xorm:"int(11) INDEX not null default 0" json:"sharing_type" valid:"length(0|2)" maximum:"2" default:"0"`
|
||||
SharedByID int64 `xorm:"int(11) INDEX not null"`
|
||||
Created int64 `xorm:"created not null" json:"created"`
|
||||
Updated int64 `xorm:"updated not null" json:"updated"`
|
||||
}
|
||||
|
||||
// TableName holds the table name for this share
|
||||
func (linkSharing20190818210133) TableName() string {
|
||||
return "link_sharing"
|
||||
}
|
||||
|
||||
func init() {
|
||||
migrations = append(migrations, &xormigrate.Migration{
|
||||
ID: "20190818210133",
|
||||
Description: "Add link sharing table",
|
||||
Migrate: func(tx *xorm.Engine) error {
|
||||
return tx.Sync2(linkSharing20190818210133{})
|
||||
},
|
||||
Rollback: func(tx *xorm.Engine) error {
|
||||
return tx.DropTables(linkSharing20190818210133{})
|
||||
},
|
||||
})
|
||||
}
|
||||
44
pkg/migration/20190920185205.go
Normal file
44
pkg/migration/20190920185205.go
Normal file
@@ -0,0 +1,44 @@
|
||||
// Copyright 2019 Vikunja and contriubtors. All rights reserved.
|
||||
//
|
||||
// This file is part of Vikunja.
|
||||
//
|
||||
// Vikunja is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Vikunja is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Vikunja. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package migration
|
||||
|
||||
import (
|
||||
"github.com/go-xorm/xorm"
|
||||
"src.techknowlogick.com/xormigrate"
|
||||
)
|
||||
|
||||
type task20190920185205 struct {
|
||||
PercentDone float64 `xorm:"DOUBLE null" json:"percentDone"`
|
||||
}
|
||||
|
||||
func (task20190920185205) TableName() string {
|
||||
return "tasks"
|
||||
}
|
||||
|
||||
func init() {
|
||||
migrations = append(migrations, &xormigrate.Migration{
|
||||
ID: "20190920185205",
|
||||
Description: "Add task percent done",
|
||||
Migrate: func(tx *xorm.Engine) error {
|
||||
return tx.Sync2(task20190920185205{})
|
||||
},
|
||||
Rollback: func(tx *xorm.Engine) error {
|
||||
return dropTableColum(tx, "tasks", "percent_done")
|
||||
},
|
||||
})
|
||||
}
|
||||
96
pkg/migration/20190922205826.go
Normal file
96
pkg/migration/20190922205826.go
Normal file
@@ -0,0 +1,96 @@
|
||||
// Copyright 2019 Vikunja and contriubtors. All rights reserved.
|
||||
//
|
||||
// This file is part of Vikunja.
|
||||
//
|
||||
// Vikunja is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Vikunja is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Vikunja. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package migration
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"github.com/go-xorm/xorm"
|
||||
"src.techknowlogick.com/xormigrate"
|
||||
)
|
||||
|
||||
// TaskRelation represents a kind of relation between two tasks
|
||||
type taskRelation20190922205826 struct {
|
||||
ID int64 `xorm:"int(11) autoincr not null unique pk"`
|
||||
TaskID int64 `xorm:"int(11) not null"`
|
||||
OtherTaskID int64 `xorm:"int(11) not null"`
|
||||
RelationKind models.RelationKind `xorm:"varchar(50) not null"`
|
||||
CreatedByID int64 `xorm:"int(11) not null"`
|
||||
Created int64 `xorm:"created not null"`
|
||||
}
|
||||
|
||||
// TableName holds the table name for the task relation table
|
||||
func (taskRelation20190922205826) TableName() string {
|
||||
return "task_relations"
|
||||
}
|
||||
|
||||
type task20190922205826 struct {
|
||||
ID int64 `xorm:"int(11) autoincr not null unique pk"`
|
||||
CreatedByID int64 `xorm:"int(11) not null"`
|
||||
ParentTaskID int64 `xorm:"int(11) INDEX null"`
|
||||
}
|
||||
|
||||
func (task20190922205826) TableName() string {
|
||||
return "tasks"
|
||||
}
|
||||
|
||||
func init() {
|
||||
migrations = append(migrations, &xormigrate.Migration{
|
||||
ID: "20190922205826",
|
||||
Description: "Add task relations",
|
||||
Migrate: func(tx *xorm.Engine) error {
|
||||
err := tx.Sync2(taskRelation20190922205826{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get all current subtasks and put them in a new table
|
||||
tasks := []*task20190922205826{}
|
||||
err = tx.Where("parent_task_id != null OR parent_task_id != 0").Find(&tasks)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var migratedRelations = make([]*taskRelation20190922205826, 0, len(tasks)*2)
|
||||
for _, t := range tasks {
|
||||
migratedRelations = append(migratedRelations,
|
||||
&taskRelation20190922205826{
|
||||
TaskID: t.ID,
|
||||
OtherTaskID: t.ParentTaskID,
|
||||
RelationKind: models.RelationKindParenttask,
|
||||
CreatedByID: t.CreatedByID,
|
||||
},
|
||||
&taskRelation20190922205826{
|
||||
TaskID: t.ParentTaskID,
|
||||
OtherTaskID: t.ID,
|
||||
RelationKind: models.RelationKindSubtask,
|
||||
CreatedByID: t.CreatedByID,
|
||||
})
|
||||
}
|
||||
|
||||
_, err = tx.Insert(migratedRelations)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return dropTableColum(tx, "tasks", "parent_task_id")
|
||||
},
|
||||
Rollback: func(tx *xorm.Engine) error {
|
||||
return tx.DropTables(taskRelation20190922205826{})
|
||||
},
|
||||
})
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user