Compare commits
133 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
7e8c2bb53b | ||
|
|
20c4b249a4 | ||
|
|
0db5292f9f | ||
|
|
08e84f4e8d | ||
|
|
a791fe0fa7 | ||
|
|
19218b28a2 | ||
|
|
cf0b0a2853 | ||
|
|
17fefae8bb | ||
|
|
3d05d0aa45 | ||
|
|
cbcd12b5e5 | ||
|
|
8fdb8b7840 | ||
|
|
ed44478b20 | ||
|
|
0a092214c8 | ||
|
|
3ebf0da1b9 | ||
|
|
07bc4f3f01 | ||
|
|
cb58f55923 | ||
|
|
7cc078aaec | ||
|
|
d20a899f92 | ||
|
|
3cfe8c238a | ||
|
|
22495a66e0 | ||
|
|
d5e68ee037 | ||
|
|
318920fe29 | ||
|
|
364a172876 | ||
|
|
31c39388f1 | ||
|
|
056e37331e | ||
|
|
09787fb29b | ||
|
|
607dbd6ae8 | ||
|
|
3183d10dbe | ||
|
|
6b40df50d3 | ||
|
|
d39007baa0 | ||
|
|
37345e6bd3 | ||
|
|
018dd8164c | ||
|
|
3814b8a504 | ||
|
|
b050132f4f | ||
|
|
36eb580c8a | ||
|
|
067bb695b1 | ||
|
|
fd80094eca | ||
|
|
ce0bff8d9c | ||
|
|
5a93475be9 | ||
|
|
784b890f70 | ||
|
|
7322bfafb3 | ||
|
|
0edc5fd315 | ||
|
|
cbc5995ad3 | ||
|
|
3e4f7fb2f4 | ||
|
|
79b463d0af | ||
|
|
97febdb397 | ||
|
|
67e94f95b0 | ||
|
|
34af627b66 | ||
|
|
ec347a71bc | ||
|
|
777d63dff1 | ||
|
|
424dfe19af | ||
|
|
7b439cc568 | ||
|
|
553055f073 | ||
|
|
70f7f630a3 | ||
|
|
82340cb70b | ||
|
|
94fd2d758a | ||
|
|
e047673c6b | ||
|
|
ee398b5272 | ||
|
|
3e8fd1e619 | ||
|
|
0c1d786ade | ||
|
|
f1fca3346b | ||
|
|
07d6e1219b | ||
|
|
8e5ff72c4a | ||
|
|
fc17232819 | ||
|
|
8aed78b296 | ||
|
|
cc6b35e314 | ||
|
|
f2f881f505 | ||
|
|
6c5885747b | ||
|
|
007d8ec375 | ||
|
|
1b24cdf164 | ||
|
|
ce2cae9430 | ||
|
|
d9304f6996 | ||
|
|
d5e4195ee7 | ||
|
|
136adb69c8 | ||
|
|
06fc9f7886 | ||
|
|
3f44e3b83e | ||
|
|
b73786f7fe | ||
|
|
e5c1588576 | ||
|
|
d51ed8defd | ||
|
|
fded3c23d0 | ||
|
|
af0ce5bb96 | ||
|
|
0c112a0ca3 |
166
.drone.yml
166
.drone.yml
@@ -1,166 +0,0 @@
|
||||
workspace:
|
||||
base: /srv/app
|
||||
path: src/code.vikunja.io/api
|
||||
|
||||
clone:
|
||||
git:
|
||||
image: plugins/git
|
||||
depth: 50
|
||||
tags: true
|
||||
|
||||
pipeline:
|
||||
build:
|
||||
image: vikunja/golang-build:latest
|
||||
pull: true
|
||||
group: build
|
||||
environment:
|
||||
TAGS: bindata sqlite
|
||||
commands:
|
||||
- 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 build
|
||||
when:
|
||||
event: [ push, tag, pull_request ]
|
||||
|
||||
test:
|
||||
image: vikunja/golang-build:latest
|
||||
pull: true
|
||||
commands:
|
||||
- make test
|
||||
when:
|
||||
event: [ push, tag, pull_request ]
|
||||
|
||||
# Build a release when tagging
|
||||
before-static-build:
|
||||
image: karalabe/xgo-latest:latest
|
||||
pull: true
|
||||
environment:
|
||||
TAGS: bindata sqlite
|
||||
GOPATH: /srv/app
|
||||
commands:
|
||||
- make release-dirs
|
||||
when:
|
||||
event: [tag, push]
|
||||
|
||||
static-build-windows:
|
||||
image: karalabe/xgo-latest:latest
|
||||
pull: true
|
||||
group: build-static
|
||||
environment:
|
||||
TAGS: bindata sqlite
|
||||
GOPATH: /srv/app
|
||||
commands:
|
||||
- make release-windows
|
||||
when:
|
||||
event: [tag, push]
|
||||
|
||||
static-build-linux:
|
||||
image: karalabe/xgo-latest:latest
|
||||
pull: true
|
||||
group: build-static
|
||||
environment:
|
||||
TAGS: bindata sqlite
|
||||
GOPATH: /srv/app
|
||||
commands:
|
||||
- make release-linux
|
||||
when:
|
||||
event: [tag, push]
|
||||
|
||||
static-build-darwin:
|
||||
image: karalabe/xgo-latest:latest
|
||||
pull: true
|
||||
group: build-static
|
||||
environment:
|
||||
TAGS: bindata sqlite
|
||||
GOPATH: /srv/app
|
||||
commands:
|
||||
- make release-darwin
|
||||
when:
|
||||
event: [tag, push]
|
||||
|
||||
# static-build-frontend:
|
||||
# image: webhippie/nodejs:current
|
||||
# pull: true
|
||||
# group: build-static
|
||||
# commands:
|
||||
# - make release-frontend
|
||||
# when:
|
||||
# event: [push, tag ]
|
||||
|
||||
after-build-static:
|
||||
image: karalabe/xgo-latest:latest
|
||||
pull: true
|
||||
environment:
|
||||
TAGS: bindata sqlite
|
||||
GOPATH: /srv/app
|
||||
commands:
|
||||
- make release-copy
|
||||
- make release-check
|
||||
- make release-os-package
|
||||
- make release-zip
|
||||
when:
|
||||
event: [tag, push]
|
||||
|
||||
# Push the releases to our pseudo-s3-bucket
|
||||
release:
|
||||
image: plugins/s3:1
|
||||
pull: true
|
||||
secrets: [ aws_access_key_id, aws_secret_access_key ]
|
||||
bucket: vikunja
|
||||
endpoint: https://storage.kolaente.de
|
||||
path_style: true
|
||||
strip_prefix: dist/zip/
|
||||
source: dist/zip/*
|
||||
target: /${DRONE_TAG##v}
|
||||
when:
|
||||
event: [ tag ]
|
||||
|
||||
release:
|
||||
image: plugins/s3:1
|
||||
pull: true
|
||||
secrets: [ aws_access_key_id, aws_secret_access_key ]
|
||||
bucket: vikunja
|
||||
endpoint: https://storage.kolaente.de
|
||||
path_style: true
|
||||
strip_prefix: dist/zip/
|
||||
source: dist/zip/*
|
||||
target: /master
|
||||
when:
|
||||
event: [ push ]
|
||||
branch: [ master ]
|
||||
|
||||
# Build the docker image and push it to docker hub
|
||||
docker:
|
||||
image: plugins/docker
|
||||
pull: true
|
||||
secrets: [ docker_username, docker_password ]
|
||||
repo: vikunja/api
|
||||
auto_tag: true
|
||||
when:
|
||||
event: [ push, tag ]
|
||||
branch: [ master ]
|
||||
|
||||
# Update the instance on try.vikunja.io
|
||||
rancher:
|
||||
image: peloton/drone-rancher
|
||||
url: http://server01.kolaente.de:8080/v1
|
||||
secrets: [ RANCHER_ACCESS_KEY, RANCHER_SECRET_KEY ]
|
||||
service: vikunja-dev/api
|
||||
docker_image: vikunja/api
|
||||
confirm: true
|
||||
when:
|
||||
event: [ push, tag ]
|
||||
branch: [ master ]
|
||||
|
||||
# Tell people vikunja was updated
|
||||
telegram:
|
||||
image: appleboy/drone-telegram
|
||||
secrets: [ TELEGRAM_TOKEN, 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"}}.
|
||||
|
||||
501
.drone1.yml
Normal file
501
.drone1.yml
Normal file
@@ -0,0 +1,501 @@
|
||||
kind: pipeline
|
||||
name: testing
|
||||
|
||||
workspace:
|
||||
base: /srv/app
|
||||
path: src/code.vikunja.io/api
|
||||
|
||||
clone:
|
||||
depth: 50
|
||||
|
||||
services:
|
||||
- name: test-db
|
||||
image: mariadb:10
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: vikunjatest
|
||||
MYSQL_DATABASE: vikunjatest
|
||||
|
||||
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
|
||||
commands:
|
||||
- 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 ]
|
||||
|
||||
- name: test
|
||||
image: vikunja/golang-build:latest
|
||||
pull: true
|
||||
commands:
|
||||
- make test
|
||||
depends_on: [ build ]
|
||||
when:
|
||||
event: [ push, tag, pull_request ]
|
||||
|
||||
- name: test-sqlite
|
||||
image: vikunja/golang-build:latest
|
||||
pull: true
|
||||
environment:
|
||||
VIKUNJA_TESTS_USE_CONFIG: 1
|
||||
VIKUNJA_DATABASE_TYPE: sqlite
|
||||
commands:
|
||||
- make test
|
||||
depends_on: [ build ]
|
||||
when:
|
||||
event: [ push, tag, pull_request ]
|
||||
|
||||
- name: test-mysql
|
||||
image: vikunja/golang-build:latest
|
||||
pull: true
|
||||
environment:
|
||||
VIKUNJA_TESTS_USE_CONFIG: 1
|
||||
VIKUNJA_DATABASE_TYPE: mysql
|
||||
VIKUNJA_DATABASE_HOST: test-db
|
||||
VIKUNJA_DATABASE_USER: root
|
||||
VIKUNJA_DATABASE_PASSWORD: vikunjatest
|
||||
VIKUNJA_DATABASE_DATABASE: vikunjatest
|
||||
commands:
|
||||
- make test
|
||||
depends_on: [ build ]
|
||||
when:
|
||||
event: [ push, tag, pull_request ]
|
||||
|
||||
---
|
||||
########
|
||||
# Build a release when pushing to master
|
||||
# We need to copy this because drone currently can't run a pipeline on either a push to master or a tag.
|
||||
########
|
||||
|
||||
kind: pipeline
|
||||
name: deploy-master
|
||||
depends_on:
|
||||
- testing
|
||||
|
||||
workspace:
|
||||
base: /srv/app
|
||||
path: src/code.vikunja.io/api
|
||||
|
||||
clone:
|
||||
depth: 50
|
||||
|
||||
trigger:
|
||||
branch:
|
||||
- master
|
||||
event:
|
||||
- push
|
||||
|
||||
steps:
|
||||
- name: before-static-build
|
||||
image: karalabe/xgo-latest:latest
|
||||
pull: true
|
||||
commands:
|
||||
- make release-dirs
|
||||
|
||||
- name: static-build-windows
|
||||
image: karalabe/xgo-latest:latest
|
||||
pull: true
|
||||
environment:
|
||||
GOPATH: /srv/app
|
||||
commands:
|
||||
- make release-windows
|
||||
depends_on: [ before-static-build ]
|
||||
|
||||
- name: static-build-linux
|
||||
image: karalabe/xgo-latest:latest
|
||||
pull: true
|
||||
environment:
|
||||
GOPATH: /srv/app
|
||||
commands:
|
||||
- make release-linux
|
||||
depends_on: [ before-static-build ]
|
||||
|
||||
- name: static-build-darwin
|
||||
image: karalabe/xgo-latest:latest
|
||||
pull: true
|
||||
environment:
|
||||
GOPATH: /srv/app
|
||||
commands:
|
||||
- make release-darwin
|
||||
depends_on: [ before-static-build ]
|
||||
|
||||
- name: after-build-static
|
||||
image: karalabe/xgo-latest:latest
|
||||
pull: true
|
||||
depends_on:
|
||||
- static-build-windows
|
||||
- static-build-linux
|
||||
- static-build-darwin
|
||||
commands:
|
||||
- make release-copy
|
||||
- make release-check
|
||||
- make release-os-package
|
||||
- make release-zip
|
||||
|
||||
- name: sign-release
|
||||
image: plugins/gpgsign:1
|
||||
pull: true
|
||||
depends_on: [ after-build-static ]
|
||||
settings:
|
||||
key:
|
||||
from_secret: gpg_privkey
|
||||
passphrase:
|
||||
from_secret: gpg_password
|
||||
files:
|
||||
- dist/zip/*
|
||||
detach_sign: true
|
||||
|
||||
# Push the releases to our pseudo-s3-bucket
|
||||
- name: release-latest
|
||||
image: plugins/s3:1
|
||||
pull: true
|
||||
settings:
|
||||
bucket: vikunja
|
||||
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: dist/zip/
|
||||
source: dist/zip/*
|
||||
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
|
||||
pull: true
|
||||
settings:
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
repo: vikunja/api
|
||||
auto_tag: true
|
||||
|
||||
# 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 ]
|
||||
|
||||
- name: telegram
|
||||
image: appleboy/drone-telegram
|
||||
depends_on:
|
||||
- rancher
|
||||
- release-latest
|
||||
settings:
|
||||
token:
|
||||
from_secret: TELEGRAM_TOKEN
|
||||
to:
|
||||
from_secret: TELEGRAM_TO
|
||||
message: >
|
||||
{{repo.owner}}/{{repo.name}}: \[{{build.status}}] Build {{build.number}}
|
||||
{{commit.author}} pushed to {{commit.branch}} {{commit.sha}}: `{{commit.message}}`
|
||||
Build started at {{datetime build.started "2006-Jan-02T15:04:05Z" "GMT+2"}} finished at {{datetime build.finished "2006-Jan-02T15:04:05Z" "GMT+2"}}.
|
||||
when:
|
||||
status:
|
||||
- success
|
||||
- failure
|
||||
---
|
||||
########
|
||||
# Build a release when tagging
|
||||
# We need to copy this because drone currently can't run a pipeline on either a push to master or a tag.
|
||||
########
|
||||
|
||||
kind: pipeline
|
||||
name: deploy-version
|
||||
depends_on:
|
||||
- testing
|
||||
|
||||
workspace:
|
||||
base: /srv/app
|
||||
path: src/code.vikunja.io/api
|
||||
|
||||
clone:
|
||||
depth: 50
|
||||
|
||||
trigger:
|
||||
event:
|
||||
- tag
|
||||
|
||||
steps:
|
||||
- name: fetch-tags
|
||||
image: docker:git
|
||||
commands:
|
||||
- git fetch --tags
|
||||
|
||||
- name: before-static-build
|
||||
image: karalabe/xgo-latest:latest
|
||||
pull: true
|
||||
commands:
|
||||
- make release-dirs
|
||||
|
||||
- name: static-build-windows
|
||||
image: karalabe/xgo-latest:latest
|
||||
pull: true
|
||||
environment:
|
||||
GOPATH: /srv/app
|
||||
commands:
|
||||
- make release-windows
|
||||
depends_on: [ before-static-build ]
|
||||
|
||||
- name: static-build-linux
|
||||
image: karalabe/xgo-latest:latest
|
||||
pull: true
|
||||
environment:
|
||||
GOPATH: /srv/app
|
||||
commands:
|
||||
- make release-linux
|
||||
depends_on: [ before-static-build ]
|
||||
|
||||
- name: static-build-darwin
|
||||
image: karalabe/xgo-latest:latest
|
||||
pull: true
|
||||
environment:
|
||||
GOPATH: /srv/app
|
||||
commands:
|
||||
- make release-darwin
|
||||
depends_on: [ before-static-build ]
|
||||
|
||||
- name: after-build-static
|
||||
image: karalabe/xgo-latest:latest
|
||||
pull: true
|
||||
depends_on:
|
||||
- static-build-windows
|
||||
- static-build-linux
|
||||
- static-build-darwin
|
||||
commands:
|
||||
- make release-copy
|
||||
- make release-check
|
||||
- make release-os-package
|
||||
- make release-zip
|
||||
|
||||
- name: sign-release
|
||||
image: plugins/gpgsign:1
|
||||
pull: true
|
||||
depends_on: [ after-build-static ]
|
||||
settings:
|
||||
key:
|
||||
from_secret: gpg_privkey
|
||||
passphrase:
|
||||
from_secret: gpg_password
|
||||
files:
|
||||
- dist/zip/*
|
||||
detach_sign: true
|
||||
|
||||
# Push the releases to our pseudo-s3-bucket
|
||||
- name: release-version
|
||||
image: plugins/s3:1
|
||||
pull: true
|
||||
settings:
|
||||
bucket: vikunja
|
||||
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: dist/zip/
|
||||
source: dist/zip/*
|
||||
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
|
||||
pull: true
|
||||
settings:
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
repo: vikunja/api
|
||||
auto_tag: true
|
||||
|
||||
- name: telegram
|
||||
image: appleboy/drone-telegram
|
||||
depends_on:
|
||||
- docker
|
||||
- release-version
|
||||
settings:
|
||||
token:
|
||||
from_secret: TELEGRAM_TOKEN
|
||||
to:
|
||||
from_secret: TELEGRAM_TO
|
||||
message: >
|
||||
{{repo.owner}}/{{repo.name}}: \[{{build.status}}] Build {{build.number}}
|
||||
{{commit.author}} pushed to {{commit.branch}} {{commit.sha}}: `{{commit.message}}`
|
||||
Build started at {{datetime build.started "2006-Jan-02T15:04:05Z" "GMT+2"}} finished at {{datetime build.finished "2006-Jan-02T15:04:05Z" "GMT+2"}}.
|
||||
when:
|
||||
status:
|
||||
- success
|
||||
- failure
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
name: deploy-docs
|
||||
|
||||
workspace:
|
||||
base: /srv/app
|
||||
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: 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
|
||||
|
||||
- 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-website/docs
|
||||
docker_image: vikunja/docs
|
||||
confirm: true
|
||||
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
|
||||
13
.gitignore
vendored
13
.gitignore
vendored
@@ -1,10 +1,19 @@
|
||||
.idea/
|
||||
.idea/*
|
||||
.idea/*/*
|
||||
.idea/httpRequests
|
||||
config.yml
|
||||
config.yaml
|
||||
!docs/config.yml
|
||||
*.db
|
||||
Run
|
||||
dist/
|
||||
cover.*
|
||||
/vikunja
|
||||
/vikunja
|
||||
Test_*
|
||||
bin/
|
||||
secrets
|
||||
*.deb
|
||||
debian/
|
||||
logs/
|
||||
docs/public/
|
||||
docs/resources/
|
||||
|
||||
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
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
###################################
|
||||
#Build stage
|
||||
FROM golang:1.11-alpine3.7 AS build-env
|
||||
FROM golang:1.11-alpine AS build-env
|
||||
|
||||
ARG VIKUNJA_VERSION
|
||||
ENV TAGS "sqlite"
|
||||
@@ -36,7 +36,6 @@ RUN apk --no-cache add \
|
||||
tzdata
|
||||
|
||||
COPY docker /
|
||||
COPY --from=build-env /go/src/code.vikunja.io/api/public /app/vikunja/public
|
||||
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
|
||||
|
||||
|
||||
440
Featurecreep.md
440
Featurecreep.md
@@ -1,219 +1,45 @@
|
||||
# Featurecreep
|
||||
|
||||
* Listen erstellen, ändern, löschen
|
||||
* Todopunkte zu Listen hinzufügen, bearbeiten, löschen
|
||||
* Listen teilen (Email/Benutzername angeben, oder öffentlicher link (+einstellbar ob mit registrierung oder nicht, oder passwortgeschützt)
|
||||
* Rechtemanagement
|
||||
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.
|
||||
|
||||
### Todopunkte
|
||||
## Feature Ideas
|
||||
|
||||
* ID
|
||||
* Text
|
||||
* Description
|
||||
* Status (done, not done)
|
||||
* Fälligkeitsdatum
|
||||
* Erinnerungsdatum (und zeit)
|
||||
* Zuständig (später, mit teilen)
|
||||
* Liste wo der Punkt drauf ist
|
||||
* Timestamps
|
||||
|
||||
### Websockets
|
||||
|
||||
Das ganze soll als Websocket zur verfg gestellt werden, der dann automatisch bescheidsagt wenn sich was ändert. Benachrichtigungen machen clients.
|
||||
|
||||
## API-Roadmap
|
||||
|
||||
Ab v0.3 können wir mit clients anfangen.
|
||||
|
||||
#### v0.1
|
||||
|
||||
* [x] Listen erstellen/bearbeiten/löschen
|
||||
|
||||
* [x] Ansehen
|
||||
* [x] Übersicht
|
||||
* [x] Einzelne liste mit allen todopunkten
|
||||
* [x] Erstellen
|
||||
* [x] Bearbeiten
|
||||
* [x] Löschen
|
||||
|
||||
* [x] Todopunkte hinzufügen/abhaken/löschen
|
||||
|
||||
* [x] Erstellen
|
||||
* [x] Bearbeiten (abhaken)
|
||||
* [x] Löschen
|
||||
|
||||
* [x] Überall nochmal überprüfen dass der Nutzer auch das Recht hat die Liste zu löschen
|
||||
|
||||
* [x] Swaggerdocs !!!!
|
||||
|
||||
Neues Konzept: _Namespaces_
|
||||
|
||||
Ein Namespace kann Listen haben, es gibt mindestens einen Besiter pro Namespace. Wenn ein neuer Nutzer angelegt wird,
|
||||
wird automatisch einer für den Nutzer erstellt.
|
||||
|
||||
Es gibt Lese- und Schreibrechte pro Namespace und Nutzer.
|
||||
|
||||
Namespace:
|
||||
|
||||
* ID
|
||||
* Name
|
||||
* OwnerID
|
||||
* Timestamps
|
||||
|
||||
Teams:
|
||||
|
||||
* ID
|
||||
* Name
|
||||
* Description
|
||||
* Rights (Selbsthochzählende Konstanten als json-array abspeichern)
|
||||
* CreatedByUser
|
||||
* Timestamps
|
||||
|
||||
TeamMembers:
|
||||
|
||||
* ID
|
||||
* TeamID
|
||||
* MemberID
|
||||
* Timestamps
|
||||
|
||||
TeamNamespaces:
|
||||
|
||||
* ID
|
||||
* TeamID
|
||||
* NamespaceID
|
||||
* Timestamps
|
||||
|
||||
TeamLists:
|
||||
|
||||
* ID
|
||||
* TeamID
|
||||
* ListID
|
||||
* Timestamps
|
||||
|
||||
(+Check ob das Team schon Zugriff auf den Namespace hat und dafür sorgen dass das sich nicht überschneidet)
|
||||
Bsp: wenn ein Namespace-Team Schreibrechte hat, soll es nicht möglich sein dieses Team mit Schreibrechten
|
||||
zur Liste hinzuzufügen. Wenn das Team im Namespace aber nur Leserechte Hat soll es möglich sein dieses Team
|
||||
als Schreibend zur Liste hinzuzufügen.
|
||||
|
||||
Oder noch Besser: Man kann globale Rechte pro Namespace vergeben, die man dann wieder feinjustieren kann pro Liste.
|
||||
Es soll aber nicht mgl. sein, ein Team zu einer Liste hinzuzufügen was nicht im Namespace ist.
|
||||
Es muss also möglich sein, Teams zum Namespace hinzuzufügen die keinerlei Rechte haben (damit man
|
||||
denen dann wieder pro Liste welche geben kann)
|
||||
|
||||
Rechte:
|
||||
Erstmal nur 3: Lesen, Schreiben, Admin. Admins dürfen auch Namen ändern, Teams verwalten, neue Listen anlegen, etc.
|
||||
Owner haben immer Adminrechte. Später sollte es auch möglich sein, den ownership an andere zur übertragen.s
|
||||
|
||||
Teams sind global, d.h. Ein Team kann mehrere Namespaces verwalten.
|
||||
|
||||
#### Neues Todo
|
||||
|
||||
* [x] Teams
|
||||
|
||||
* [x] Erstellen
|
||||
* [x] Ansehen
|
||||
* [x] Bearbeiten
|
||||
* [x] Löschen
|
||||
|
||||
~~Ein zu lösendes Problem: Wie regelt man die Berechtigungen um Teams zu verwalten?~~
|
||||
|
||||
* [x] Namespaces
|
||||
|
||||
* [x] Erstellen
|
||||
* [x] Ansehen
|
||||
* [x] Bearbeiten
|
||||
* [x] Löschen
|
||||
* [x] Teams hinzufügen. Der Nutzer kriegt nur Teams angezeigt die er erstellt hat.
|
||||
* [x] Alle Listen eines Namespaces anzeigen
|
||||
|
||||
* [x] Listen
|
||||
|
||||
* [x] Listen zu einem Namespace hinzufügen
|
||||
|
||||
#### v0.2
|
||||
|
||||
* [x] Listen teilbar
|
||||
* [x] Mit anderen Nutzern
|
||||
* [x Namespaces
|
||||
* [x] Teams
|
||||
* [ ] Mit Link
|
||||
* [ ] Offen
|
||||
* [ ] Passwortgeschützt
|
||||
|
||||
* [x] Rechtemanagement (Und damit Unterscheidung zwischen Ownern und Mitgleidern)
|
||||
* [x] Mange Team members
|
||||
* [x] Hinzufügen
|
||||
* [x] Löschen
|
||||
|
||||
*Routen*
|
||||
|
||||
* [x] `namespaces/:id/teams`
|
||||
* [x] Create
|
||||
* [x] ReadAll
|
||||
* [x] Delete
|
||||
* [x] `lists/:id/teams`
|
||||
* [x] Create
|
||||
* [x] ReadAll
|
||||
* [x] Delete
|
||||
|
||||
* [x] /namespaces soll zumindest auch die namen (+id) der dazugehörigen Listen rausgeben
|
||||
|
||||
## Feature-Ideen
|
||||
|
||||
* [ ] Labels
|
||||
* [ ] Priorities
|
||||
* [ ] Assignees
|
||||
* [ ] Subtasks
|
||||
* [ ] Attachments
|
||||
* [ ] 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.
|
||||
* [ ] Tasks innerhalb eines definierbarem Bereich, sollte aber trotzdem der server machen, so à la "Gib mir alles für diesen Monat"
|
||||
* [ ] Namespaces in Namespaces (in Namespaces in Namespaces in Namespaces...)
|
||||
* Rechtemanagement dafür wird schwierig
|
||||
|
||||
## Clients
|
||||
|
||||
* [ ] Webapp (vue.js) + Bulma
|
||||
* [ ] "Native" Clients (auf dem Rechner installiert (mit elektron oder so? Oder native mit qt oder so?)
|
||||
* [ ] Android (Flutter)
|
||||
* [ ] iOS (mit Framework???? (Ging das nich auch mit Flutter?))
|
||||
* [x] Priorities
|
||||
* [x] Repeating tasks
|
||||
* [x] Get all tasks which are due between two given dates
|
||||
* [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.
|
||||
* [ ] Endpoints neu organisieren? Also zb `namespaces/:nID/lists/:lID/items/:iID` statt einzelnen Endpoints für alles
|
||||
* [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] Refactor!!!! Delete everything not being used anymore, simplify.
|
||||
* [x] Drone
|
||||
* [x] Tests
|
||||
* [x] Find a nme
|
||||
* [x] Move packages to a better structure
|
||||
* [x] Swagger UI
|
||||
+ [x] Fix CORS
|
||||
* [x] Use echo.NewHTTPError instead of c.JSON(Message{})
|
||||
* [x] Better error messages when the model which is sent to the server is wrong
|
||||
* [x] Better error handling to show useful error messages and status codes
|
||||
* [x] Viper for config instead of ini
|
||||
* [x] Docs for installing
|
||||
* [x] Tests for rights managemnt
|
||||
* [x] Rights checks:
|
||||
* [x] Create lists
|
||||
* [x] Edit lists
|
||||
* [x] Add tasks
|
||||
* [x] Edit tasks
|
||||
* [x] The -1 namespace should also be accessible seperately
|
||||
|
||||
### 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] Cacher configurable
|
||||
* [x] Should throw an error when an id < 1
|
||||
* [x] /users should also return the rights
|
||||
* [x] Extra endpoint /teams/members /list/users to update rights without needing to remove and re-add them
|
||||
* [x] namespaces & listen update does not work, returns 500
|
||||
* [x] Logging for all errors somewhere
|
||||
* [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)
|
||||
@@ -226,10 +52,10 @@ Teams sind global, d.h. Ein Team kann mehrere Namespaces verwalten.
|
||||
* [x] Basics
|
||||
* [x] Reminders
|
||||
* [ ] Discovery, stichwort PROPFIND
|
||||
* [ ] Wir brauchen noch ne gute idee, wie man die listen kriegt, auf die man nur so Zugriff hat (ohne namespace)
|
||||
* [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
|
||||
* [ ] Testing mit locust: https://locust.io/
|
||||
* [ ] Methode einbauen, um mit einem gültigen token ein neues gültiges zu kriegen
|
||||
* [x] Testing mit locust: https://locust.io/
|
||||
* [ ] Endpoint to get all users who have access to a list - regardless of via team, user share or via namespace
|
||||
|
||||
#### Userstuff
|
||||
|
||||
@@ -240,14 +66,198 @@ Teams sind global, d.h. Ein Team kann mehrere Namespaces verwalten.
|
||||
* [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
|
||||
|
||||
### Later/Nice to have
|
||||
* [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
|
||||
* [x] Re-check rights checks to see if all information which is compared against is properly read from the db and not only based on user input
|
||||
* [x] Lists
|
||||
* [x] List users
|
||||
* [x] List Teams
|
||||
* [x] Labels
|
||||
* [x] Tasks
|
||||
* [x] Namespaces
|
||||
* [x] Namespace users
|
||||
* [x] Namespace teams
|
||||
* [x] Teams
|
||||
* [x] Team member handling
|
||||
* [x] Also check `ReadOne()` for unnessecary database operations since the inital query is already done in `CanRead()`
|
||||
* [x] Add a `User.AfterLoad()` which obfuscates the email address
|
||||
* [x] Fix priority not updating to 0
|
||||
|
||||
* [ ] Globale Limits für anlegbare Listen + Namespaces
|
||||
* [ ] Mgl., dass die Instanz geschlossen ist, also sich keiner registrieren kann, und man sich einloggen muss
|
||||
### Docs
|
||||
|
||||
* [x] Readme
|
||||
* [x] Auch noch nen "link" zum Featurecreep
|
||||
* [x] ToC
|
||||
* [x] Logo
|
||||
* [x] How to build -> Docs
|
||||
* [x] How to dev -> Docs
|
||||
* [x] License
|
||||
* [x] Contributing
|
||||
* [x] Redocs
|
||||
* [x] Swaggerdocs verbessern
|
||||
* [x] Descriptions in structs
|
||||
* [x] Maxlength specify etc. (see swaggo docs)
|
||||
* [x] Rights
|
||||
* [x] API
|
||||
* [x] Anleitung zum Makefile
|
||||
* [x] How to build from source
|
||||
* [x] Struktur erklären
|
||||
* [x] Deploy in die docs
|
||||
* [x] Docker
|
||||
* [x] Native (systemd + nginx/apache)
|
||||
* [x] Backups
|
||||
* [x] 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"
|
||||
* [ ] Roles which enable or disable chaning certain fields of a task -> includes custm fields
|
||||
* [ ] Custom fields: Templates at List > Namespace > Global level, overwriting each other
|
||||
* [ ] Related tasks -> settable with a "kind" of relation like blocked, or just related or so
|
||||
* [ ] Description should be longtext
|
||||
|
||||
### General features
|
||||
|
||||
* [x] Deps nach mod umziehen
|
||||
* [x] Performance bei rechtchecks verbessern
|
||||
* User & Teamright sollte sich für n rechte in einer Funktion testen lassen
|
||||
* [ ] Endpoint um die Rechte mit Beschreibung und code zu kriegen
|
||||
* [ ] "Smart Lists", Listen nach bestimmten Kriterien gefiltert -> speichern und im pseudonamespace
|
||||
* [ ] "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"
|
||||
* [ ] Per list
|
||||
* [ ] For the current user
|
||||
* [ ] ~~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
|
||||
* [ ] Being able to define filters for notifications or turn them silent completely -> Probably frontend only
|
||||
* [ ] 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)
|
||||
* [ ] IMAP-Integration -> Man schickt eine email an Vikunja und es macht daraus dann nen task
|
||||
* [ ] In und Out webhooks, mit Templates vom Payload
|
||||
* [x] Deps nach mod (dem nachfolger von dep) umziehen, blocked by Go 1.11
|
||||
* [ ] /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
|
||||
* [ ] Bindata for templates
|
||||
* [ ] `GetUserByID` and the likes should return pointers
|
||||
* [ ] Colors for lists and namespaces -> Up to the frontend to implement these
|
||||
* [ ] Some kind of milestones for tasks
|
||||
* [ ] Create tasks from a text/markdown file (probably frontend only)
|
||||
* [ ] Label-view: Get a bunch of tasks by label
|
||||
* [ ] Better caldav support (VTODO)
|
||||
* [ ] Debian package should have a service file
|
||||
* [ ] Downloads should be served via nginx (with theme?), minio should only be used for pushing artifacts.
|
||||
* [ ] User struct should have a field for the avatar url (-> gravatar md5 calculated by the backend)
|
||||
* [ ] All `ReadAll` methods should return the number of items per page, the number of items on this page, the total pages and the items
|
||||
-> Check if there's a way to do that efficently. Maybe only implementing it in the web handler.
|
||||
|
||||
### 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.
|
||||
* [x] Things like list/task order should use queries and not url params
|
||||
* [x] Fix lint errors
|
||||
* [ ] Reminders should use an extra table so we can make reverse lookups aka "give me all tasks with reminders in this period" which we'll need for things like email reminders notifications
|
||||
* [ ] Teams and users should also have uuids (for users these can be the username)
|
||||
* [ ] When giving a team or user access to a list/namespace, they should be reffered to by uuid, not numeric id
|
||||
* [ ] Adding users to a team should also use uuid
|
||||
* [ ] Check if the team/user really exist before updating them on lists/namespaces
|
||||
* [ ] Check if the email is properly obfuscated everywhere -> alter GetUser() and add a new method GetUserWithEmail
|
||||
|
||||
### Linters
|
||||
|
||||
* [x] goconst
|
||||
* [x] Staticcheck
|
||||
* [x] gocyclo-check
|
||||
* [ ] gosec-check -> waiting for mod
|
||||
* [x] goconst-check
|
||||
* [ ] golangci -> docker in drone, will probably make all other linters obsolete
|
||||
|
||||
### More server settings
|
||||
|
||||
* [ ] Caldav disable/enable
|
||||
* [ ] Assignees disable/enable
|
||||
* [ ] List/Namespace limits
|
||||
* [ ] Attachements disable/enable
|
||||
* [ ] Attachements size
|
||||
* [ ] Templates disable/enable
|
||||
* [ ] Stats disable/enable
|
||||
* [ ] Activity notifications disable/enable
|
||||
* [ ] IMAP integration disable/enable
|
||||
* [ ] Reminders via mail disable/enable
|
||||
|
||||
### Later
|
||||
|
||||
* [ ] Plugins
|
||||
* [ ] Rename Namespaces?
|
||||
* [ ] Namespaces to collections and n-n (one list can be in multiple collections)?
|
||||
* [ ] Per-User limits of lists/namespaces
|
||||
* [ ] Admin-Interface to do stuff like settings and user management
|
||||
* [ ] Enable/Disable users
|
||||
* [ ] Better rights, fine-graded
|
||||
* [ ] Enable/disable allowing user adding to lists/namespaces for specific lists or namespaces
|
||||
* [ ] Admins should be able to see and mange all the boards
|
||||
* [ ] Limit registration to users with a defined email domain
|
||||
* [ ] Close the instance, either no registration or only one with defined email
|
||||
* [ ] 2fa
|
||||
* [ ] Custom fields for tasks
|
||||
* [ ] Sorting lists by members, tasks, teams, last modified, etc
|
||||
* [ ] "Favourite lists" -> A user can favourize boards which will then show up in a pseudonamespace
|
||||
* [ ] Public lists
|
||||
* [ ] Internal lists -> Only registered users can see the list
|
||||
* [ ] Rights management for both public and internal lists
|
||||
* [ ] Add new users via to a list which don't have an account yet, they'd get a link to sign up for vikunja.
|
||||
* [ ] Respect registration email domain limits
|
||||
* [ ] Export all data from Vikunja to json
|
||||
* [ ] Watch a (n internal) list -> Will get notification for everything
|
||||
* [ ] Archive a task instead of deleting
|
||||
* [ ] Task dependencies
|
||||
* [ ] Time tracking (possible plugin)
|
||||
* [ ] IFTTT
|
||||
* [ ] More sharing features (all of these with the already existing permissions)
|
||||
* [ ] Invite users per mail
|
||||
* [ ] Share a link with/without password
|
||||
* [ ] Comments on tasks
|
||||
* [ ] @mention users in tasks or comments to get them notified
|
||||
* [ ] Summary of tasks to do in a configurable interval (every day/week or so)
|
||||
* [ ] Importer (maybe frontend only)
|
||||
* [ ] Trello
|
||||
* [ ] Wunderlist
|
||||
* [ ] Zenkit
|
||||
* [ ] Asana
|
||||
* [ ] Microsoft Todo
|
||||
* [ ] Nozbe
|
||||
* [ ] Lanes
|
||||
* [ ] Nirvana
|
||||
* [ ] Good ol' Caldav (Tasks)
|
||||
* [ ] More auth providers
|
||||
* [ ] LDAP/AD
|
||||
* [ ] Kerberos
|
||||
* [ ] SAML (what?)
|
||||
* [ ] smtp
|
||||
* [ ] OpenID
|
||||
|
||||
759
LICENSE
759
LICENSE
@@ -1,165 +1,674 @@
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
This version of the GNU Lesser General Public License incorporates
|
||||
the terms and conditions of version 3 of the GNU General Public
|
||||
License, supplemented by the additional permissions listed below.
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
0. Additional Definitions.
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||
General Public License.
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
"The Library" refers to a covered work governed by this License,
|
||||
other than an Application or a Combined Work as defined below.
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
An "Application" is any work that makes use of an interface provided
|
||||
by the Library, but which is not otherwise based on the Library.
|
||||
Defining a subclass of a class defined by the Library is deemed a mode
|
||||
of using an interface provided by the Library.
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
A "Combined Work" is a work produced by combining or linking an
|
||||
Application with the Library. The particular version of the Library
|
||||
with which the Combined Work was made is also called the "Linked
|
||||
Version".
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
The "Minimal Corresponding Source" for a Combined Work means the
|
||||
Corresponding Source for the Combined Work, excluding any source code
|
||||
for portions of the Combined Work that, considered in isolation, are
|
||||
based on the Application, and not on the Linked Version.
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
The "Corresponding Application Code" for a Combined Work means the
|
||||
object code and/or source code for the Application, including any data
|
||||
and utility programs needed for reproducing the Combined Work from the
|
||||
Application, but excluding the System Libraries of the Combined Work.
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
1. Exception to Section 3 of the GNU GPL.
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
You may convey a covered work under sections 3 and 4 of this License
|
||||
without being bound by section 3 of the GNU GPL.
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
2. Conveying Modified Versions.
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
If you modify a copy of the Library, and, in your modifications, a
|
||||
facility refers to a function or data to be supplied by an Application
|
||||
that uses the facility (other than as an argument passed when the
|
||||
facility is invoked), then you may convey a copy of the modified
|
||||
version:
|
||||
0. Definitions.
|
||||
|
||||
a) under this License, provided that you make a good faith effort to
|
||||
ensure that, in the event an Application does not supply the
|
||||
function or data, the facility still operates, and performs
|
||||
whatever part of its purpose remains meaningful, or
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
b) under the GNU GPL, with none of the additional permissions of
|
||||
this License applicable to that copy.
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
3. Object Code Incorporating Material from Library Header Files.
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
The object code form of an Application may incorporate material from
|
||||
a header file that is part of the Library. You may convey such object
|
||||
code under terms of your choice, provided that, if the incorporated
|
||||
material is not limited to numerical parameters, data structure
|
||||
layouts and accessors, or small macros, inline functions and templates
|
||||
(ten or fewer lines in length), you do both of the following:
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
a) Give prominent notice with each copy of the object code that the
|
||||
Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||
document.
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
4. Combined Works.
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
You may convey a Combined Work under terms of your choice that,
|
||||
taken together, effectively do not restrict modification of the
|
||||
portions of the Library contained in the Combined Work and reverse
|
||||
engineering for debugging such modifications, if you also do each of
|
||||
the following:
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
a) Give prominent notice with each copy of the Combined Work that
|
||||
the Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
1. Source Code.
|
||||
|
||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||
document.
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
c) For a Combined Work that displays copyright notices during
|
||||
execution, include the copyright notice for the Library among
|
||||
these notices, as well as a reference directing the user to the
|
||||
copies of the GNU GPL and this license document.
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
d) Do one of the following:
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
0) Convey the Minimal Corresponding Source under the terms of this
|
||||
License, and the Corresponding Application Code in a form
|
||||
suitable for, and under terms that permit, the user to
|
||||
recombine or relink the Application with a modified version of
|
||||
the Linked Version to produce a modified Combined Work, in the
|
||||
manner specified by section 6 of the GNU GPL for conveying
|
||||
Corresponding Source.
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
1) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (a) uses at run time
|
||||
a copy of the Library already present on the user's computer
|
||||
system, and (b) will operate properly with a modified version
|
||||
of the Library that is interface-compatible with the Linked
|
||||
Version.
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
e) Provide Installation Information, but only if you would otherwise
|
||||
be required to provide such information under section 6 of the
|
||||
GNU GPL, and only to the extent that such information is
|
||||
necessary to install and execute a modified version of the
|
||||
Combined Work produced by recombining or relinking the
|
||||
Application with a modified version of the Linked Version. (If
|
||||
you use option 4d0, the Installation Information must accompany
|
||||
the Minimal Corresponding Source and Corresponding Application
|
||||
Code. If you use option 4d1, you must provide the Installation
|
||||
Information in the manner specified by section 6 of the GNU GPL
|
||||
for conveying Corresponding Source.)
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
5. Combined Libraries.
|
||||
2. Basic Permissions.
|
||||
|
||||
You may place library facilities that are a work based on the
|
||||
Library side by side in a single library together with other library
|
||||
facilities that are not Applications and are not covered by this
|
||||
License, and convey such a combined library under terms of your
|
||||
choice, if you do both of the following:
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
a) Accompany the combined library with a copy of the same work based
|
||||
on the Library, uncombined with any other library facilities,
|
||||
conveyed under the terms of this License.
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
b) Give prominent notice with the combined library that part of it
|
||||
is a work based on the Library, and explaining where to find the
|
||||
accompanying uncombined form of the same work.
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
6. Revised Versions of the GNU Lesser General Public License.
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU Lesser General Public License from time to time. Such new
|
||||
versions will be similar in spirit to the present version, but may
|
||||
differ in detail to address new problems or concerns.
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Library as you received it specifies that a certain numbered version
|
||||
of the GNU Lesser General Public License "or any later version"
|
||||
applies to it, you have the option of following the terms and
|
||||
conditions either of that published version or of any later version
|
||||
published by the Free Software Foundation. If the Library as you
|
||||
received it does not specify a version number of the GNU Lesser
|
||||
General Public License, you may choose any version of the GNU Lesser
|
||||
General Public License ever published by the Free Software Foundation.
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
If the Library as you received it specifies that a proxy can decide
|
||||
whether future versions of the GNU Lesser General Public License shall
|
||||
apply, that proxy's public statement of acceptance of any version is
|
||||
permanent authorization for you to choose that version for the
|
||||
Library.
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(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/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
88
Makefile
88
Makefile
@@ -19,15 +19,13 @@ GOFMT ?= gofmt -s
|
||||
GOFLAGS := -v -mod=vendor
|
||||
EXTRA_GOFLAGS ?=
|
||||
|
||||
LDFLAGS := -X "main.Version=$(shell git describe --tags --always | sed 's/-/+/' | sed 's/^v//')" -X "main.Tags=$(TAGS)"
|
||||
LDFLAGS := -X "code.vikunja.io/api/pkg/cmd.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 ./... | grep -v /vendor/))
|
||||
PACKAGES ?= $(filter-out code.vikunja.io/api/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,6 +42,18 @@ else
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(DRONE_WORKSPACE),'')
|
||||
BINLOCATION := $(EXECUTABLE)
|
||||
else
|
||||
BINLOCATION := $(DIST)/binaries/$(EXECUTABLE)-$(VERSION)-linux-amd64
|
||||
endif
|
||||
|
||||
ifeq ($(VERSION),master)
|
||||
PKGVERSION := $(shell git describe --tags --always --abbrev=10 | sed 's/-/+/' | sed 's/^v//' | sed 's/-g/-/')
|
||||
else
|
||||
PKGVERSION := $(VERSION)
|
||||
endif
|
||||
|
||||
VERSION := $(shell echo $(VERSION) | sed 's/\//\-/g')
|
||||
|
||||
.PHONY: all
|
||||
@@ -56,12 +66,9 @@ clean:
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
go test -cover -coverprofile cover.out $(PACKAGES)
|
||||
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: lint
|
||||
lint:
|
||||
@hash golint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
@@ -70,11 +77,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,10 +90,6 @@ 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)
|
||||
|
||||
@@ -106,7 +109,7 @@ release-windows:
|
||||
go install $(GOFLAGS) github.com/karalabe/xgo; \
|
||||
fi
|
||||
xgo -dest $(DIST)/binaries -tags 'netgo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out vikunja-$(VERSION) .
|
||||
ifeq ($(CI),drone)
|
||||
ifneq ($(DRONE_WORKSPACE),'')
|
||||
mv /build/* $(DIST)/binaries
|
||||
endif
|
||||
|
||||
@@ -116,7 +119,7 @@ release-linux:
|
||||
go install $(GOFLAGS) github.com/karalabe/xgo; \
|
||||
fi
|
||||
xgo -dest $(DIST)/binaries -tags 'netgo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'linux/*' -out vikunja-$(VERSION) .
|
||||
ifeq ($(CI),drone)
|
||||
ifneq ($(DRONE_WORKSPACE),'')
|
||||
mv /build/* $(DIST)/binaries
|
||||
endif
|
||||
|
||||
@@ -126,32 +129,37 @@ release-darwin:
|
||||
go install $(GOFLAGS) github.com/karalabe/xgo; \
|
||||
fi
|
||||
xgo -dest $(DIST)/binaries -tags 'netgo $(TAGS)' -ldflags '$(LDFLAGS)' -targets 'darwin/*' -out vikunja-$(VERSION) .
|
||||
ifeq ($(CI),drone)
|
||||
ifneq ($(DRONE_WORKSPACE),'')
|
||||
mv /build/* $(DIST)/binaries
|
||||
endif
|
||||
|
||||
.PHONY: release-copy
|
||||
release-copy:
|
||||
$(foreach file,$(wildcard $(DIST)/binaries/$(EXECUTABLE)-*),cp $(file) $(DIST)/release/$(notdir $(file));)
|
||||
mkdir $(DIST)/release/public
|
||||
cp public/ $(DIST)/release/ -R
|
||||
mkdir $(DIST)/release/templates
|
||||
mkdir $(DIST)/release/templates -p
|
||||
cp templates/ $(DIST)/templates/ -R
|
||||
|
||||
.PHONY: release-check
|
||||
release-check:
|
||||
cd $(DIST)/release; $(foreach file,$(wildcard $(DIST)/release/$(EXECUTABLE)-*),sha256sum $(notdir $(file)) > $(notdir $(file)).sha256;)
|
||||
|
||||
|
||||
.PHONY: release-os-package
|
||||
release-os-package:
|
||||
$(foreach file,$(filter-out %.sha256,$(wildcard $(DIST)/release/$(EXECUTABLE)-*)),mkdir $(file)-full;mv $(file) $(file)-full/; mv $(file).sha256 $(file)-full/; cp config.yml.sample $(file)-full/config.yml; cp $(DIST)/release/public $(file)-full/ -R; cp $(DIST)/release/templates $(file)-full/ -R; cp LICENSE $(file)-full/; )
|
||||
rm $(DIST)/release/public -rf
|
||||
$(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/; )
|
||||
|
||||
.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 ./templates=/opt/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); \
|
||||
@@ -166,7 +174,13 @@ 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 -s ./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/package\ docs/package\ swagger/g' docs/docs.go;
|
||||
sed -i 's/` + \\"`\\" + `/` + "`" + `/g' docs/docs.go;
|
||||
mv ./docs/docs.go ./pkg/swagger/docs.go;
|
||||
|
||||
.PHONY: misspell-check
|
||||
misspell-check:
|
||||
@@ -185,6 +199,30 @@ 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;
|
||||
for S in $(GOFILES); do gocyclo -over 17 $$S || exit 1; done;
|
||||
|
||||
.PHONY: static-check
|
||||
static-check:
|
||||
@hash staticcheck > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
go get -u honnef.co/go/tools; \
|
||||
go install $(GOFLAGS) honnef.co/go/tools/cmd/staticcheck; \
|
||||
fi
|
||||
staticcheck $(PACKAGES);
|
||||
|
||||
.PHONY: gosec-check
|
||||
gosec-check:
|
||||
@hash ./bin/gosec > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
curl -sfL https://raw.githubusercontent.com/securego/gosec/master/install.sh | sh -s 1.2.0; \
|
||||
fi
|
||||
for S in $(PACKAGES); do ./bin/gosec $$S || exit 1; done;
|
||||
|
||||
.PHONY: goconst-check
|
||||
goconst-check:
|
||||
@hash goconst > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
go get -u github.com/jgautheron/goconst/cmd/goconst; \
|
||||
go install $(GOFLAGS) github.com/jgautheron/goconst/cmd/goconst; \
|
||||
fi
|
||||
for S in $(PACKAGES); do goconst $$S || exit 1; done;
|
||||
|
||||
89
README.md
89
README.md
@@ -1,13 +1,24 @@
|
||||
<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)
|
||||
[](https://cover.run/go?tag=golang-1.10&repo=code.vikunja.io%2Fapi)
|
||||
|
||||
# Vikunja API
|
||||
|
||||
> The Todo-app to organize your life.
|
||||
|
||||
[](https://drone.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)
|
||||
# Table of contents
|
||||
|
||||
* [Features](#features)
|
||||
* [Docs](#docs)
|
||||
* [Roadmap](#roadmap)
|
||||
* [Contributing](#contributing)
|
||||
* [License](#license)
|
||||
|
||||
## Features
|
||||
|
||||
@@ -16,47 +27,53 @@
|
||||
* Namespaces: A "group" which bundels multiple lists
|
||||
* Share lists and namespaces with teams and users with granular permissions
|
||||
|
||||
Try it under [try.vikunja.io](https://try.vikunja.io)!
|
||||
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
|
||||
|
||||
> I know, it's still a long way to go. I'm currently working on a lot of "basic" features, the exiting things will come later. Don't worry, they'll come.
|
||||
|
||||
* [ ] Labels for todo lists and tasks
|
||||
* [ ] Prioritize tasks
|
||||
* [ ] Assign users to tasks
|
||||
* [ ] Subtasks
|
||||
* [ ] Repeating tasks
|
||||
* [ ] Attachments on tasks
|
||||
* [ ] Get all tasks for you per interval (day/month/period)
|
||||
* [x] Prioritize tasks
|
||||
* [x] Subtasks
|
||||
* [x] Repeating tasks
|
||||
* [x] Get tasks via caldav
|
||||
* [x] Get all your tasks for an interval (day/month/period)
|
||||
* [x] Labels for tasks
|
||||
* [x] Assign users to tasks
|
||||
* [ ] Attachments on tasks
|
||||
* [ ] More sharing features
|
||||
* [ ] Share with individual users
|
||||
* [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
|
||||
* [ ] "Smart Lists" - Create lists based on filters
|
||||
* [ ] IMAP-Integration - Send an email to Vikunja to create a new task
|
||||
* [ ] Webhooks - Trigger other events when an action is done (like completing a task)
|
||||
* [ ] Performace statistics - Get an overview and beautiful charts about what you got done this month
|
||||
* [ ] Activity feeds - Get a quick overview about who did what
|
||||
* [ ] Bulk-edit multiple tasks at once
|
||||
* [ ] Team-efforts - Requiring a task to be marked as done by multiple members until it's done
|
||||
* [ ] Global limits for namespaces/lists/tasks
|
||||
* [ ] Disable registration, making an instance "invite-only"
|
||||
|
||||
* [ ] [Mobile apps](https://code.vikunja.io/app) (seperate repo)
|
||||
* [ ] [Webapp](https://code.vikunja.io/frontend) (seperate repo)
|
||||
See [Featurecreep.md](Featurecreep.md) for even more! (mostly ideas, for now)
|
||||
|
||||
## Development
|
||||
* [ ] [Mobile apps](https://code.vikunja.io/app) (seperate repo) *In Progress*
|
||||
* [ ] [Webapp](https://code.vikunja.io/frontend) (seperate repo) *In Progress*
|
||||
|
||||
We use go modules to vendor libraries for Vikunja, so you'll need at least go `1.11`.
|
||||
## Contributing
|
||||
|
||||
To contribute to Vikunja, fork the project and work on the master branch.
|
||||
Fork -> Push -> Pull-Request. Also see the [dev docs](https://vikunja.io/docs/development/) for more infos.
|
||||
|
||||
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.
|
||||
## License
|
||||
|
||||
```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.
|
||||
|
||||
@@ -4,7 +4,7 @@ Content-Type: application/json
|
||||
|
||||
{
|
||||
"username": "user",
|
||||
"password": "12345"
|
||||
"password": "1234"
|
||||
}
|
||||
|
||||
> {% client.global.set("auth_token", response.body.token); %}
|
||||
@@ -15,9 +15,9 @@ POST http://localhost:8080/api/v1/register
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"username": "user4",
|
||||
"username": "user",
|
||||
"password": "1234",
|
||||
"email": "4@knt.li"
|
||||
"email": "5@knt.li"
|
||||
}
|
||||
|
||||
###
|
||||
|
||||
70
REST-Tests/labels.http
Normal file
70
REST-Tests/labels.http
Normal file
@@ -0,0 +1,70 @@
|
||||
# Get all labels
|
||||
GET http://localhost:8080/api/v1/labels
|
||||
Authorization: Bearer {{auth_token}}
|
||||
|
||||
###
|
||||
# Add a new label
|
||||
PUT http://localhost:8080/api/v1/labels
|
||||
Authorization: Bearer {{auth_token}}
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"title": "test5"
|
||||
}
|
||||
|
||||
###
|
||||
# Delete a label
|
||||
DELETE http://localhost:8080/api/v1/labels/6
|
||||
Authorization: Bearer {{auth_token}}
|
||||
|
||||
###
|
||||
# Update a label
|
||||
POST http://localhost:8080/api/v1/labels/1
|
||||
Authorization: Bearer {{auth_token}}
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"title": "testschinkenbrot",
|
||||
"description": "käsebrot"
|
||||
}
|
||||
|
||||
###
|
||||
# Get one label
|
||||
GET http://localhost:8080/api/v1/labels/1
|
||||
Authorization: Bearer {{auth_token}}
|
||||
|
||||
###
|
||||
# Get all labels on a task
|
||||
GET http://localhost:8080/api/v1/tasks/3565/labels
|
||||
Authorization: Bearer {{auth_token}}
|
||||
|
||||
###
|
||||
# Add a new label to a task
|
||||
PUT http://localhost:8080/api/v1/tasks/35236365/labels
|
||||
Authorization: Bearer {{auth_token}}
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"label_id": 1
|
||||
}
|
||||
|
||||
###
|
||||
# Delete a label from a task
|
||||
DELETE http://localhost:8080/api/v1/tasks/3565/labels/1
|
||||
Authorization: Bearer {{auth_token}}
|
||||
|
||||
###
|
||||
# Add a new label to a task
|
||||
POST http://localhost:8080/api/v1/tasks/3565/labels/bulk
|
||||
Authorization: Bearer {{auth_token}}
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"labels": [
|
||||
{"id": 1},
|
||||
{"id": 2},
|
||||
{"id": 3}
|
||||
]
|
||||
}
|
||||
|
||||
###
|
||||
@@ -1,38 +1,40 @@
|
||||
# Get all lists
|
||||
GET http://localhost:8080/api/v1/lists
|
||||
GET http://localhost:8080/api/v1/namespaces/35/lists
|
||||
Authorization: Bearer {{auth_token}}
|
||||
|
||||
###
|
||||
|
||||
# Get one list
|
||||
GET http://localhost:8080/api/v1/lists/1
|
||||
GET http://localhost:8080/api/v1/lists/1172
|
||||
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
|
||||
|
||||
{
|
||||
"title": "sc",
|
||||
"created": 0
|
||||
"title": "test"
|
||||
}
|
||||
|
||||
###
|
||||
|
||||
# Add a new list
|
||||
# Add a new item
|
||||
PUT http://localhost:8080/api/v1/lists/1
|
||||
Authorization: Bearer {{auth_token}}
|
||||
Content-Type: application/json
|
||||
|
||||
{"text": "test2", "description": "Schinken"}
|
||||
{
|
||||
"text": "Task",
|
||||
"description": "Schinken"
|
||||
}
|
||||
|
||||
###
|
||||
|
||||
# Delete a list from a list
|
||||
DELETE http://localhost:8080/api/v1/lists/28
|
||||
# Delete a task from a list
|
||||
DELETE http://localhost:8080/api/v1/lists/14
|
||||
Authorization: Bearer {{auth_token}}
|
||||
|
||||
###
|
||||
@@ -80,11 +82,11 @@ Authorization: Bearer {{auth_token}}
|
||||
###
|
||||
|
||||
# Give a user access to that list
|
||||
PUT http://localhost:8080/api/v1/lists/30/users
|
||||
PUT http://localhost:8080/api/v1/lists/1172/users
|
||||
Authorization: Bearer {{auth_token}}
|
||||
Content-Type: application/json
|
||||
|
||||
{"user_id":3, "right":1}
|
||||
{"user_id":1, "right":1}
|
||||
|
||||
###
|
||||
|
||||
@@ -104,7 +106,19 @@ Authorization: Bearer {{auth_token}}
|
||||
###
|
||||
|
||||
# Get all pending tasks
|
||||
GET http://localhost:8080/api/v1/tasks
|
||||
GET http://localhost:8080/api/v1/tasks/all
|
||||
Authorization: Bearer {{auth_token}}
|
||||
|
||||
###
|
||||
|
||||
# Get all pending tasks with priorities
|
||||
GET http://localhost:8080/api/v1/tasks/all?sort=priorityasc
|
||||
Authorization: Bearer {{auth_token}}
|
||||
|
||||
###
|
||||
|
||||
# Get all pending tasks in a range
|
||||
GET http://localhost:8080/api/v1/tasks/all/dueadateasc/1546784000/1548784000
|
||||
Authorization: Bearer {{auth_token}}
|
||||
|
||||
###
|
||||
@@ -113,4 +127,45 @@ Authorization: Bearer {{auth_token}}
|
||||
GET http://localhost:8080/api/v1/tasks/caldav
|
||||
#Authorization: Bearer {{auth_token}}
|
||||
|
||||
###
|
||||
###
|
||||
|
||||
# Update a task
|
||||
POST http://localhost:8080/api/v1/tasks/3565
|
||||
Authorization: Bearer {{auth_token}}
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"priority": 0
|
||||
}
|
||||
|
||||
###
|
||||
|
||||
# Bulk update multiple tasks at once
|
||||
POST http://localhost:8080/api/v1/tasks/bulk
|
||||
Authorization: Bearer {{auth_token}}
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"task_ids": [3518,3519,3521],
|
||||
"text":"bulkupdated"
|
||||
}
|
||||
|
||||
###
|
||||
# Get all assignees
|
||||
GET http://localhost:8080/api/v1/tasks/3565/assignees
|
||||
Authorization: Bearer {{auth_token}}
|
||||
|
||||
###
|
||||
|
||||
# Add a bunch of assignees
|
||||
PUT http://localhost:8080/api/v1/tasks/3565/assignees/bulk
|
||||
Authorization: Bearer {{auth_token}}
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"assignees": [
|
||||
{"id": 17}
|
||||
]
|
||||
}
|
||||
|
||||
###
|
||||
|
||||
@@ -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}}
|
||||
|
||||
###
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
# Get all users
|
||||
GET http://localhost:8080/api/v1/users
|
||||
GET http://localhost:8080/api/v1/user
|
||||
Authorization: Bearer {{auth_token}}
|
||||
|
||||
######
|
||||
@@ -27,7 +27,7 @@ Content-Type: application/json
|
||||
Accept: application/json
|
||||
|
||||
{
|
||||
"user_name": "user"
|
||||
"email": "k@knt.li"
|
||||
}
|
||||
|
||||
### Request a token to reset a password
|
||||
|
||||
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,13 +2,20 @@ 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.
|
||||
frontendurl: ""
|
||||
# The base path on the file system where the binary and assets are.
|
||||
# Vikunja will also look in this path for a config file, so you could provide only this variable to point to a folder
|
||||
# with a config file which will then be used.
|
||||
rootpath: <rootpath>
|
||||
# The number of items which gets returned per page
|
||||
pagecount: 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
|
||||
|
||||
database:
|
||||
# Database type to use. Supported types are mysql and sqlite.
|
||||
@@ -23,31 +30,59 @@ 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
|
||||
|
||||
cache:
|
||||
# If cache is enabled or not
|
||||
enabled: false
|
||||
# Cache type. Possible values are memory or redis
|
||||
# Cache type. Possible values are memory or redis, you'll need to enable redis below when using redis
|
||||
type: memory
|
||||
# When using memory this defines the maximum size an element can take
|
||||
maxelementsize: 1000
|
||||
# When using redis, this is the host of the redis server including its port.
|
||||
redishost: 'localhost:6379'
|
||||
# When using redis, this is the password used to authenicate against the redis server
|
||||
redispassword: ''
|
||||
|
||||
redis:
|
||||
# Whether to enable redis or not
|
||||
enabled: false
|
||||
# The host of the redis server including its port.
|
||||
host: 'localhost:6379'
|
||||
# The password used to authenicate against the redis server
|
||||
password: ''
|
||||
# 0 means default database
|
||||
db: 0
|
||||
|
||||
mailer:
|
||||
# Whether to enable the mailer or not. If it is disabled, all users are enabled right away and password reset is not possible.
|
||||
enabled: false
|
||||
# SMTP Host
|
||||
host: ''
|
||||
host: ""
|
||||
# SMTP Host port
|
||||
port: 587
|
||||
# SMTP username
|
||||
username: 'user'
|
||||
username: "user"
|
||||
# SMTP password
|
||||
password: ''
|
||||
password: ""
|
||||
# Wether to skip verification of the tls certificate on the server
|
||||
skiptlsverify: false
|
||||
# The default from address when sending emails
|
||||
fromemail: 'mail@vikunja'
|
||||
fromemail: "mail@vikunja"
|
||||
# The length of the mail queue.
|
||||
queuelength: 100
|
||||
# The timeout in seconds after which the current open connection to the mailserver will be closed.
|
||||
queuetimeout: 30
|
||||
|
||||
log:
|
||||
# A folder where all the logfiles should go.
|
||||
path: <rootpath>logs
|
||||
# Whether to show any logging at all or none
|
||||
enabled: true
|
||||
# Where the error log should go. Possible values are stdout, stderr, file or off to disable error logging.
|
||||
errors: "stdout"
|
||||
# Where the normal log should go. Possible values are stdout, stderr, file or off to disable standard logging.
|
||||
standard: "stdout"
|
||||
# Whether or not to log database queries. Useful for debugging. Possible values are stdout, stderr, file or off to disable database logging.
|
||||
database: "off"
|
||||
# Whether to log http requests or not. Possible values are stdout, stderr, file or off to disable http logging.
|
||||
http: "stdout"
|
||||
# Echo has its own logging which usually is unnessecary, which is why it is disabled by default. Possible values are stdout, stderr, file or off to disable standard logging.
|
||||
echo: "off"
|
||||
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
|
||||
@@ -1,97 +0,0 @@
|
||||
# Architectural concepts
|
||||
|
||||
Vikunja was built with a maximum flexibility in mind while developing. To achive this, I built a set of easy-to-use
|
||||
functions and respective web handlers, all represented through interfaces.
|
||||
|
||||
## CRUDable
|
||||
|
||||
This interface defines methods to Create/Read/ReadAll/Update/Delete something. In order to use the common web
|
||||
handler, the struct must implement this and the `Rights` interface.
|
||||
|
||||
The interface is defined as followed:
|
||||
|
||||
```go
|
||||
type CRUDable interface {
|
||||
Create(*User) error
|
||||
ReadOne() error
|
||||
ReadAll(string, *User, int) (interface{}, error)
|
||||
Update() error
|
||||
Delete() error
|
||||
}
|
||||
```
|
||||
|
||||
Each of these methods is called on an instance of a struct like so:
|
||||
|
||||
```go
|
||||
func (l *List) ReadOne() (err error) {
|
||||
*l, err = GetListByID(l.ID)
|
||||
return
|
||||
}
|
||||
```
|
||||
|
||||
In that case, it takes the `ID` saved in the struct instance, gets the full list object and fills the original object with it.
|
||||
(See parambinder to understand where that `ID` is coming from).
|
||||
|
||||
All functions should behave like this, if they create or update something, they should return the created/updated struct
|
||||
instance. The only exception is `ReadAll()` which returns an interface. Usually this is an array, because, well you cannot
|
||||
make an array of a set type (If you know a way to do this, don't hesitate to drop me a message).
|
||||
|
||||
### Pagination
|
||||
|
||||
When using the `ReadAll`-method, the third parameter contains the requested page. Your function should return only the number of results
|
||||
corresponding to that page. The number of items per page is definied in the config as `service.pagecount` (Get it with `viper.GetInt("service.pagecount")`).
|
||||
|
||||
These can be calculated in combination with a helper function, `getLimitFromPageIndex(pageIndex)` which returns
|
||||
SQL-needed `limit` (max-length) and `offset` parameters. You can feed this function directly into xorm's `Limit`-Function like so:
|
||||
|
||||
```go
|
||||
lists := []List{}
|
||||
err := x.Limit(getLimitFromPageIndex(pageIndex)).Find(&lists)
|
||||
```
|
||||
|
||||
### Search
|
||||
|
||||
When using the `ReadAll`-method, the first parameter is a search term which should be used to search items of your struct. You define the critera.
|
||||
|
||||
Users can then pass the `?s=something` parameter to the url to search.
|
||||
|
||||
As the logic for "give me everything" and "give me everything where the name contains 'something'" is mostly the same, we made the decision to design the function like this, in order to keep the places with mostly the same logic as few as possible. Also just adding `?s=query` to the url one already knows and uses is a lot more convenient.
|
||||
|
||||
## Rights
|
||||
|
||||
This interface defines methods to check for rights on structs. They accept a `User` as parameter and usually return a `bool`.
|
||||
|
||||
The interface is defined as followed:
|
||||
|
||||
```go
|
||||
type Rights interface {
|
||||
IsAdmin(*User) bool
|
||||
CanWrite(*User) bool
|
||||
CanRead(*User) bool
|
||||
CanDelete(*User) bool
|
||||
CanUpdate(*User) bool
|
||||
CanCreate(*User) bool
|
||||
}
|
||||
```
|
||||
|
||||
When using the standard web handler, all methods except `CanRead()` are called before their `CRUD` counterparts. `CanRead()`
|
||||
is called after `ReadOne()` was invoked as this would otherwise mean getting an object from the db to check if the user has the
|
||||
right to see it and then getting it again if thats the case. Calling the function afterwards means we only have to get the
|
||||
object once.
|
||||
|
||||
## Standard web handler
|
||||
|
||||
## Errors
|
||||
|
||||
Error types with their messages and http-codes are set in `models/error.go`. If the error type implements `HTTPError`, the server returns a user-friendly error message when this error occours. This means it returns a good HTTP status code, a message, and an error code. The error code should be unique across all error codes and can be used on the client to show a localized error message or do other stuff based on the exact error the server returns. That way the client won't have to "guess" that the error message remains the same over multiple versions of Vikunja.
|
||||
|
||||
An `HTTPError` is defined as follows:
|
||||
|
||||
```go
|
||||
type HTTPError struct {
|
||||
HTTPCode int `json:"-"` // Can be any valid HTTP status code, I'd reccomend to use the constants of the http package.
|
||||
Code int `json:"code"` // Must be a uniqe int identifier for this specific error. I'd reccomend defining a constant for this.
|
||||
Message string `json:"message"` // A user-readable message what went wrong.
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
# Configuration options
|
||||
|
||||
You can either use a `config.yml` file in the root directory of vikunja or set all config option with
|
||||
environment variables. If you have both, the value set in the config file is used.
|
||||
|
||||
Variables are nested in the `config.yml`, these nested variables become `VIKUNJA_FIRST_CHILD` when configuring via
|
||||
environment variables. So setting
|
||||
|
||||
```bash
|
||||
export VIKUNJA_FIRST_CHILD=true
|
||||
```
|
||||
|
||||
is the same as defining it in a `config.yml` like so:
|
||||
|
||||
```yaml
|
||||
first:
|
||||
child: true
|
||||
```
|
||||
|
||||
# Default configuration with explanations
|
||||
|
||||
This is the same as the `config.yaml` file you'll find in the root of vikunja.
|
||||
|
||||
```yaml
|
||||
service:
|
||||
# This token is used to verify issued JWT tokens.
|
||||
# Default is a random token which will be generated at each startup of vikunja.
|
||||
# (This means all already issued tokens will be invalid once you restart vikunja)
|
||||
JWTSecret: "cei6gaezoosah2bao3ieZohkae5aicah"
|
||||
# The interface on which to run the webserver
|
||||
interface: ":3456"
|
||||
# The URL of the frontend, used to send password reset emails.
|
||||
frontendurl: ""
|
||||
# The base path on the file system where the binary and assets are
|
||||
rootpath: <the path of the executable>
|
||||
# The number of items which gets returned per page
|
||||
pagecount: 50
|
||||
|
||||
database:
|
||||
# Database type to use. Supported types are mysql and sqlite.
|
||||
type: "sqlite"
|
||||
# Database user which is used to connect to the database.
|
||||
user: "vikunja"
|
||||
# Databse password
|
||||
password: ""
|
||||
# Databse host
|
||||
host: "localhost"
|
||||
# Databse to use
|
||||
database: "vikunja"
|
||||
# When using sqlite, this is the path where to store the data
|
||||
Path: "./vikunja.db"
|
||||
# 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
|
||||
|
||||
|
||||
cache:
|
||||
# If cache is enabled or not
|
||||
enabled: false
|
||||
# Cache type. Possible values are memory or redis
|
||||
type: memory
|
||||
# When using memory this defines the maximum size an element can take
|
||||
maxelementsize: 1000
|
||||
# When using redis, this is the host of the redis server including its port.
|
||||
redishost: 'localhost:6379'
|
||||
# When using redis, this is the password used to authenicate against the redis server
|
||||
redispassword: ''
|
||||
|
||||
mailer:
|
||||
# SMTP Host
|
||||
host: ""
|
||||
# SMTP Host port
|
||||
port: 587
|
||||
# SMTP username
|
||||
username: "user"
|
||||
# SMTP password
|
||||
password: ""
|
||||
# Wether to skip verification of the tls certificate on the server
|
||||
skiptlsverify: false
|
||||
# The default from address when sending emails
|
||||
fromemail: "mail@vikunja"
|
||||
# The length of the mail queue.
|
||||
queuelength: 100
|
||||
# The timeout in seconds after which the current open connection to the mailserver will be closed.
|
||||
queuetimeout: 30
|
||||
```
|
||||
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 >}}
|
||||
61
docs/content/doc/development/development.md
Normal file
61
docs/content/doc/development/development.md
Normal file
@@ -0,0 +1,61 @@
|
||||
---
|
||||
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.
|
||||
132
docs/content/doc/development/make.md
Normal file
132
docs/content/doc/development/make.md
Normal file
@@ -0,0 +1,132 @@
|
||||
---
|
||||
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 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.
|
||||
|
||||
### Build Releases
|
||||
|
||||
{{< highlight bash >}}
|
||||
make build
|
||||
{{< /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/karalabe/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-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.
|
||||
27
docs/content/doc/development/test.md
Normal file
27
docs/content/doc/development/test.md
Normal file
@@ -0,0 +1,27 @@
|
||||
---
|
||||
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.
|
||||
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)).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.
|
||||
132
docs/content/doc/setup/config.md
Normal file
132
docs/content/doc/setup/config.md
Normal file
@@ -0,0 +1,132 @@
|
||||
---
|
||||
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
|
||||
environment variables. If you have both, the value set in the config file is used.
|
||||
|
||||
Variables are nested in the `config.yml`, these nested variables become `VIKUNJA_FIRST_CHILD` when configuring via
|
||||
environment variables. So setting
|
||||
|
||||
{{< highlight bash >}}
|
||||
export VIKUNJA_FIRST_CHILD=true
|
||||
{{< /highlight >}}
|
||||
|
||||
is the same as defining it in a `config.yml` like so:
|
||||
|
||||
{{< 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.
|
||||
|
||||
{{< highlight yaml >}}
|
||||
service:
|
||||
# This token is used to verify issued JWT tokens.
|
||||
# Default is a random token which will be generated at each startup of vikunja.
|
||||
# (This means all already issued tokens will be invalid once you restart vikunja)
|
||||
JWTSecret: "cei6gaezoosah2bao3ieZohkae5aicah"
|
||||
# The interface on which to run the webserver
|
||||
interface: ":3456"
|
||||
# The URL of the frontend, used to send password reset emails.
|
||||
frontendurl: ""
|
||||
# The base path on the file system where the binary and assets are.
|
||||
# Vikunja will also look in this path for a config file, so you could provide only this variable to point to a folder
|
||||
# with a config file which will then be used.
|
||||
rootpath: <the path of the executable>
|
||||
# The number of items which gets returned per page
|
||||
pagecount: 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
|
||||
|
||||
database:
|
||||
# Database type to use. Supported types are mysql and sqlite.
|
||||
type: "sqlite"
|
||||
# Database user which is used to connect to the database.
|
||||
user: "vikunja"
|
||||
# Databse password
|
||||
password: ""
|
||||
# Databse host
|
||||
host: "localhost"
|
||||
# Databse to use
|
||||
database: "vikunja"
|
||||
# When using sqlite, this is the path where to store the data
|
||||
Path: "./vikunja.db"
|
||||
# Sets the max open connections to the database. Only used when using mysql.
|
||||
openconnections: 100
|
||||
|
||||
cache:
|
||||
# If cache is enabled or not
|
||||
enabled: false
|
||||
# Cache type. Possible values are memory or redis, you'll need to enable redis below when using redis
|
||||
type: memory
|
||||
# When using memory this defines the maximum size an element can take
|
||||
maxelementsize: 1000
|
||||
|
||||
redis:
|
||||
# Whether to enable redis or not
|
||||
enabled: false
|
||||
# The host of the redis server including its port.
|
||||
host: 'localhost:6379'
|
||||
# The password used to authenicate against the redis server
|
||||
password: ''
|
||||
# 0 means default database
|
||||
db: 0
|
||||
|
||||
mailer:
|
||||
# Whether to enable the mailer or not. If it is disabled, all users are enabled right away and password reset is not possible.
|
||||
enabled: false
|
||||
# SMTP Host
|
||||
host: ""
|
||||
# SMTP Host port
|
||||
port: 587
|
||||
# SMTP username
|
||||
username: "user"
|
||||
# SMTP password
|
||||
password: ""
|
||||
# Wether to skip verification of the tls certificate on the server
|
||||
skiptlsverify: false
|
||||
# The default from address when sending emails
|
||||
fromemail: "mail@vikunja"
|
||||
# The length of the mail queue.
|
||||
queuelength: 100
|
||||
# The timeout in seconds after which the current open connection to the mailserver will be closed.
|
||||
queuetimeout: 30
|
||||
|
||||
log:
|
||||
# A folder where all the logfiles should go.
|
||||
path: <rootpath>logs
|
||||
# Whether to show any logging at all or none
|
||||
enabled: true
|
||||
# Where the error log should go. Possible values are stdout, stderr, file or off to disable error logging.
|
||||
errors: "stdout"
|
||||
# Where the normal log should go. Possible values are stdout, stderr, file or off to disable standard logging.
|
||||
standard: "stdout"
|
||||
# Whether or not to log database queries. Useful for debugging. Possible values are stdout, stderr, file or off to disable database logging.
|
||||
database: "off"
|
||||
# Whether to log http requests or not. Possible values are stdout, stderr, file or off to disable http logging.
|
||||
http: "stdout"
|
||||
# Echo has its own logging which usually is unnessecary, which is why it is disabled by default. Possible values are stdout, stderr, file or off to disable standard logging.
|
||||
echo: "off"
|
||||
{{< /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">}}).
|
||||
19
docs/content/doc/usage/api.md
Normal file
19
docs/content/doc/usage/api.md
Normal file
@@ -0,0 +1,19 @@
|
||||
---
|
||||
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.
|
||||
A public instance is available on [try.vikunja.io](http://try.vikunja.io/api/v1/docs).
|
||||
|
||||
These docs are autgenerated from annotations in the code with swagger.
|
||||
|
||||
The specification is hosted at `http://vikunja.tld/api/v1/docs.json`.
|
||||
You can use this to embed it into other openapi compatible applications if you want.
|
||||
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,3 +1,13 @@
|
||||
---
|
||||
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.
|
||||
@@ -21,6 +31,9 @@ This document describes the different errors Vikunja can return.
|
||||
| 3005 | 400 | The list title cannot be empty. |
|
||||
| 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. |
|
||||
| 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. |
|
||||
@@ -29,11 +42,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. |
|
||||
| 9001 | 403 | The right is invalid. |
|
||||
29
docs/content/doc/usage/rights.md
Normal file
29
docs/content/doc/usage/rights.md
Normal file
@@ -0,0 +1,29 @@
|
||||
---
|
||||
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.
|
||||
This parameter controls the rights that team or user is going to have (or has, if you request the current sharing status).
|
||||
|
||||
Rights are being specified using integers.
|
||||
|
||||
The following values are possible:
|
||||
|
||||
| Right (int) | Meaning |
|
||||
|-------------|---------|
|
||||
| 0 (Default) | Read only. Anything which is shared with this right cannot be edited. |
|
||||
| 1 | Read and write. Namespaces or lists shared with this right can be read and written to by the team or user. |
|
||||
| 2 | Admin. Can do anything like read and write, but can additionally manage sharing options. |
|
||||
|
||||
### Team admins
|
||||
|
||||
When adding or querying a team, every member has an additional boolean value stating if it is admin or not.
|
||||
A team admin can also add and remove team members and also change whether a user in the team is admin or not.
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
# How the binder works
|
||||
|
||||
The binder binds all values inside the url to their respective fields in a struct. Those fields need to have a tag
|
||||
"param" with the name of the url placeholder which must be the same as in routes.
|
||||
|
||||
Whenever one of the standard CRUD methods is invoked, this binder is called, which enables one handler method
|
||||
to handle all kinds of different urls with different parameters.
|
||||
1
docs/themes/vikunja
vendored
Submodule
1
docs/themes/vikunja
vendored
Submodule
Submodule docs/themes/vikunja added at 611b91ba5d
89
go.mod
89
go.mod
@@ -1,62 +1,77 @@
|
||||
// Vikunja is a todo-list application to facilitate your life.
|
||||
// Copyright 2018 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/>.
|
||||
|
||||
module code.vikunja.io/api
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.30.0 // indirect
|
||||
cloud.google.com/go v0.34.0 // indirect
|
||||
code.vikunja.io/web v0.0.0-20190329170935-7dc1f4191c49
|
||||
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
|
||||
github.com/client9/misspell v0.3.4
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20180901172138-1eb28afdf9b6 // 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 // indirect
|
||||
github.com/go-openapi/analysis v0.17.2 // indirect
|
||||
github.com/go-openapi/errors v0.17.2 // indirect
|
||||
github.com/go-openapi/inflect v0.17.2 // indirect
|
||||
github.com/go-openapi/loads v0.17.2 // indirect
|
||||
github.com/go-openapi/runtime v0.17.2 // indirect
|
||||
github.com/go-openapi/spec v0.17.2 // indirect
|
||||
github.com/go-openapi/strfmt v0.17.2 // indirect
|
||||
github.com/go-openapi/swag v0.17.2 // indirect
|
||||
github.com/go-openapi/validate v0.17.2 // indirect
|
||||
github.com/go-playground/locales v0.12.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.16.0 // indirect
|
||||
github.com/go-sql-driver/mysql v0.0.0-20171007150158-ee359f95877b
|
||||
github.com/go-swagger/go-swagger v0.17.2 // indirect
|
||||
github.com/go-xorm/builder v0.0.0-20170519032130-c8871c857d25 // indirect
|
||||
github.com/go-xorm/core v0.5.8
|
||||
github.com/go-redis/redis v6.14.2+incompatible
|
||||
github.com/go-sql-driver/mysql v1.4.1
|
||||
github.com/go-xorm/builder v0.3.2
|
||||
github.com/go-xorm/core v0.6.0
|
||||
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/google/go-cmp v0.2.0 // indirect
|
||||
github.com/gordonklaus/ineffassign v0.0.0-20180909121442-1003c8bd00dc
|
||||
github.com/gorilla/handlers v1.4.0 // indirect
|
||||
github.com/imdario/mergo v0.3.6
|
||||
github.com/jessevdk/go-flags v1.4.0 // indirect
|
||||
github.com/joho/godotenv v1.3.0 // indirect
|
||||
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/kr/pretty v0.1.0 // indirect
|
||||
github.com/labstack/echo v0.0.0-20180911044237-1abaa3049251
|
||||
github.com/labstack/gommon v0.0.0-20180312174116-6fe1405d73ec
|
||||
github.com/lib/pq v1.0.0 // indirect
|
||||
github.com/mattn/go-oci8 v0.0.0-20181011085415-1a014d1384b5 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.9.0
|
||||
github.com/op/go-logging v0.0.0-20160211212156-b2cb9fa56473
|
||||
github.com/pkg/errors v0.8.0 // indirect
|
||||
github.com/kisielk/gotool v1.0.0 // indirect
|
||||
github.com/labstack/echo v3.3.10+incompatible
|
||||
github.com/labstack/gommon v0.2.8
|
||||
github.com/mattn/go-colorable v0.1.1 // indirect
|
||||
github.com/mattn/go-isatty v0.0.7 // 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/go-homedir v1.1.0 // indirect
|
||||
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/prometheus/client_golang v0.9.2
|
||||
github.com/spf13/cobra v0.0.3
|
||||
github.com/spf13/viper v1.2.0
|
||||
github.com/stretchr/testify v1.2.2
|
||||
github.com/swaggo/echo-swagger v0.0.0-20180315045949-97f46bb9e5a5
|
||||
github.com/swaggo/files v0.0.0-20180215091130-49c8a91ea3fa // indirect
|
||||
github.com/swaggo/swag v1.3.3-0.20181109030545-8f09470d62b2
|
||||
github.com/toqueteos/webbrowser v1.1.0 // indirect
|
||||
github.com/stretchr/objx v0.1.1 // indirect
|
||||
github.com/stretchr/testify v1.3.0
|
||||
github.com/swaggo/swag v1.4.1-0.20181210033626-0e12fd5eb026
|
||||
github.com/urfave/cli v1.20.0 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/ziutek/mymysql v1.5.4 // indirect
|
||||
golang.org/x/crypto v0.0.0-20180312195533-182114d58262
|
||||
github.com/valyala/fasttemplate v1.0.1 // indirect
|
||||
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3
|
||||
golang.org/x/net v0.0.0-20181217023233-e147a9138326 // indirect
|
||||
golang.org/x/sys v0.0.0-20190329044733-9eb1bfa1ce65 // indirect
|
||||
golang.org/x/tools v0.0.0-20181026183834-f60e5f99f081 // indirect
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||
gopkg.in/testfixtures.v2 v2.4.5
|
||||
gopkg.in/testfixtures.v2 v2.5.3
|
||||
gopkg.in/yaml.v2 v2.2.2 // indirect
|
||||
honnef.co/go/tools v0.0.0-20190215041234-466a0476246c
|
||||
src.techknowlogick.com/xormigrate v0.0.0-20190321151057-24497c23c09c
|
||||
)
|
||||
|
||||
239
go.sum
239
go.sum
@@ -1,5 +1,20 @@
|
||||
cloud.google.com/go v0.30.0 h1:xKvyLgk56d0nksWq49J0UyGEeUIicTl4+UBiX1NPX9g=
|
||||
cloud.google.com/go v0.30.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
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-20190123142349-c30ef6073334 h1:a8RDvsjGxDx8w/OsADUpikHYHjZb8CoCiwEOKsQnN4w=
|
||||
code.vikunja.io/web v0.0.0-20190123142349-c30ef6073334/go.mod h1:PmGEu9qI7nbEKDn38H0SWgCoGO4GLdbjdlnWSzFi2PA=
|
||||
code.vikunja.io/web v0.0.0-20190324075814-1ddbccd6c866 h1:12F2fgKvTpnREgw+8GNGNQbRRXXDdI6tWg0WUyoHyaU=
|
||||
code.vikunja.io/web v0.0.0-20190324075814-1ddbccd6c866/go.mod h1:PmGEu9qI7nbEKDn38H0SWgCoGO4GLdbjdlnWSzFi2PA=
|
||||
code.vikunja.io/web v0.0.0-20190324080654-fcc8b45b7e2c h1:j1VTb5aOEQ3y4Y+u0RzU5lj45PpzQ/oSyVE5n/dPXB8=
|
||||
code.vikunja.io/web v0.0.0-20190324080654-fcc8b45b7e2c/go.mod h1:PmGEu9qI7nbEKDn38H0SWgCoGO4GLdbjdlnWSzFi2PA=
|
||||
code.vikunja.io/web v0.0.0-20190324080741-7bd881d9892a h1:nB+kG5/gq0njK9/fEtYgzvLfd+U8i1I4m3CvYC+aN9k=
|
||||
code.vikunja.io/web v0.0.0-20190324080741-7bd881d9892a/go.mod h1:PmGEu9qI7nbEKDn38H0SWgCoGO4GLdbjdlnWSzFi2PA=
|
||||
code.vikunja.io/web v0.0.0-20190324105229-0933ac082307 h1:t2E9v+k56RbvM5WNJF5BFFJDZrzM5l1Ua8qWdZYJAdA=
|
||||
code.vikunja.io/web v0.0.0-20190324105229-0933ac082307/go.mod h1:PmGEu9qI7nbEKDn38H0SWgCoGO4GLdbjdlnWSzFi2PA=
|
||||
code.vikunja.io/web v0.0.0-20190324123058-62b466dd1311 h1:3VRszH3NCTNUh+8y2ImA50ALJiE1e9KNoowv9y8mzvA=
|
||||
code.vikunja.io/web v0.0.0-20190324123058-62b466dd1311/go.mod h1:PmGEu9qI7nbEKDn38H0SWgCoGO4GLdbjdlnWSzFi2PA=
|
||||
code.vikunja.io/web v0.0.0-20190329170935-7dc1f4191c49 h1:onS7evj9KeCnf/3kNGlY1pXCT1BDay3WlbFddH6bwIE=
|
||||
code.vikunja.io/web v0.0.0-20190329170935-7dc1f4191c49/go.mod h1:PmGEu9qI7nbEKDn38H0SWgCoGO4GLdbjdlnWSzFi2PA=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/PuerkitoBio/purell v1.1.0 h1:rmGxhojJlM0tuKtfdvliR84CFHljx9ag64t2xmVkjK4=
|
||||
@@ -10,18 +25,19 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5Vpd
|
||||
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/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/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
|
||||
github.com/cweill/gotests v1.5.2 h1:kKqmKmS2wCV3tuLnfpbiuN8OlkosQZTpCfiqmiuNAsA=
|
||||
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-20180901172138-1eb28afdf9b6 h1:BZGp1dbKFjqlGmxEpwkDpCWNxVwEYnUPoncIzLiHlPo=
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20180901172138-1eb28afdf9b6/go.mod h1:xN/JuLBIz4bjkxNmByTiV1IbhfnYb6oo99phBn4Eqhc=
|
||||
github.com/dgrijalva/jwt-go v0.0.0-20170608005149-a539ee1a749a h1:nmYyGtn9AO7FCeZ2tHr1ZWjJAHi6SfGB3o80F8o7EbA=
|
||||
github.com/dgrijalva/jwt-go v0.0.0-20170608005149-a539ee1a749a/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20181014144952-4e0d7dc8888f h1:WH0w/R4Yoey+04HhFxqZ6VX6I0d7RMyw5aXQ9UTvQPs=
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20181014144952-4e0d7dc8888f/go.mod h1:xN/JuLBIz4bjkxNmByTiV1IbhfnYb6oo99phBn4Eqhc=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fzipp/gocyclo v0.0.0-20150627053110-6acd4345c835 h1:roDmqJ4Qes7hrDOsWsMCce0vQHz3xiMPjJ9m4c2eeNs=
|
||||
@@ -30,178 +46,223 @@ 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/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb h1:D4uzjWwKYQ5XnAvUbuvHW93esHg7F8N/OYeBBcJoTr0=
|
||||
github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q=
|
||||
github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI=
|
||||
github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
|
||||
github.com/go-openapi/analysis v0.17.2 h1:eYp14J1o8TTSCzndHBtsNuckikV1PfZOSnx4BcBeu0c=
|
||||
github.com/go-openapi/analysis v0.17.2/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
|
||||
github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
|
||||
github.com/go-openapi/errors v0.17.2 h1:azEQ8Fnx0jmtFF2fxsnmd6I0x6rsweUF63qqSO1NmKk=
|
||||
github.com/go-openapi/errors v0.17.2/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0=
|
||||
github.com/go-openapi/inflect v0.17.2 h1:4Zg/XLOwxsyKGFHxCI9e4AkxUFSjLKaoY+jnStSJAfw=
|
||||
github.com/go-openapi/inflect v0.17.2/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4=
|
||||
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/jsonreference v0.17.0 h1:yJW3HCkTHg7NOA+gZ83IPHzUSnUzGXhGmsdiCcMexbA=
|
||||
github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
|
||||
github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
|
||||
github.com/go-openapi/loads v0.17.2 h1:tEXYu6Xc0pevpzzQx5ghrMN9F7IVpN/+u4iD3rkYE5o=
|
||||
github.com/go-openapi/loads v0.17.2/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU=
|
||||
github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA=
|
||||
github.com/go-openapi/runtime v0.17.2 h1:/ZK67ikFhQAMFFH/aPu2MaGH7QjP4wHBvHYOVIzDAw0=
|
||||
github.com/go-openapi/runtime v0.17.2/go.mod h1:QO936ZXeisByFmZEO1IS1Dqhtf4QV1sYYFtIq6Ld86Q=
|
||||
github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
|
||||
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/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
|
||||
github.com/go-openapi/strfmt v0.17.2 h1:2KDns36DMHXG9/iYkOjiX+/8fKK9GCU5ELZ+J6qcRVA=
|
||||
github.com/go-openapi/strfmt v0.17.2/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU=
|
||||
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-openapi/validate v0.17.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4=
|
||||
github.com/go-openapi/validate v0.17.2 h1:lwFfiS4sv5DvOrsYDsYq4N7UU8ghXiYtPJ+VcQnC3Xg=
|
||||
github.com/go-openapi/validate v0.17.2/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4=
|
||||
github.com/go-playground/locales v0.12.1 h1:2FITxuFt/xuCNP1Acdhv62OzaCiviiE4kotfhkmOqEc=
|
||||
github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM=
|
||||
github.com/go-playground/universal-translator v0.16.0 h1:X++omBR/4cE2MNg91AoC3rmGrCjJ8eAeUP/K/EKx4DM=
|
||||
github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY=
|
||||
github.com/go-sql-driver/mysql v0.0.0-20171007150158-ee359f95877b h1:/CMGgAYard7jx9+bI7tUIqafFDR7Pv2BRu2Tb5dDaqM=
|
||||
github.com/go-sql-driver/mysql v0.0.0-20171007150158-ee359f95877b/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-swagger/go-swagger v0.17.2 h1:eizwRyO8THHMA4kXyM5Z1UTPslZGE8VsfJC0jJqsRI8=
|
||||
github.com/go-swagger/go-swagger v0.17.2/go.mod h1:fOcXeMI1KPNv3uk4u7cR4VSyq0NyrYx4SS1/ajuTWDg=
|
||||
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-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/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/core v0.5.8 h1:vQ0ghlVGnlnFmm4SpHY+xNnPlH810paMcw+Hwz9BCqE=
|
||||
github.com/go-xorm/core v0.5.8/go.mod h1:d8FJ9Br8OGyQl12MCclmYBuBqqxsyeedpXciV5Myih8=
|
||||
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/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/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
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/gorilla/handlers v1.4.0 h1:XulKRWSQK5uChr4pEgSE4Tc/OcmnU9GJuSwdog/tZsA=
|
||||
github.com/gorilla/handlers v1.4.0/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
|
||||
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/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
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/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ=
|
||||
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/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/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 v0.0.0-20180911044237-1abaa3049251 h1:4q++nZ4OEtmbHazhA/7i3T9B+CBWtnHpuMMcW55ZjRk=
|
||||
github.com/labstack/echo v0.0.0-20180911044237-1abaa3049251/go.mod h1:rWD2DNQgFb1IY9lVYZVLWn2Ko4dyHZ/LpHORyBLP3hI=
|
||||
github.com/labstack/echo v3.1.0+incompatible h1:kb0CCZ0boaiGsZOqR9E9+GDpQEoIaKClVqqo0+/hzbM=
|
||||
github.com/labstack/echo v3.1.0+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s=
|
||||
github.com/labstack/echo v3.2.1+incompatible h1:J2M7YArHx4gi8p/3fDw8tX19SXhBCoRpviyAZSN3I88=
|
||||
github.com/labstack/echo v3.2.1+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s=
|
||||
github.com/labstack/gommon v0.0.0-20170925052817-57409ada9da0 h1:kcJPx2Ug9owxOsVfuXPCludLaIudyI57YQd6ocyrO4o=
|
||||
github.com/labstack/gommon v0.0.0-20170925052817-57409ada9da0/go.mod h1:/tj9csK2iPSBvn+3NLM9e52usepMtrd5ilFYA+wQNJ4=
|
||||
github.com/labstack/gommon v0.0.0-20180312174116-6fe1405d73ec h1:aYKwS4iCpqxskMuvI8+Byq0CxnnWHO/xuLk2pZJ96tY=
|
||||
github.com/labstack/gommon v0.0.0-20180312174116-6fe1405d73ec/go.mod h1:/tj9csK2iPSBvn+3NLM9e52usepMtrd5ilFYA+wQNJ4=
|
||||
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/gommon v0.2.8 h1:JvRqmeZcfrHC5u6uVleB4NxxNbzx6gpbJiQknDbKQu0=
|
||||
github.com/labstack/gommon v0.2.8/go.mod h1:/tj9csK2iPSBvn+3NLM9e52usepMtrd5ilFYA+wQNJ4=
|
||||
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.0-20170816031813-ad5389df28cd h1:eYiiP5pgdf+n78BU5JFWt7yI2bpxW31L/R5Rrk8vLgs=
|
||||
github.com/mattn/go-colorable v0.0.0-20170816031813-ad5389df28cd/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
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/mattn/go-isatty v0.0.0-20170925054904-a5cdd64afdee h1:tUyoJR5V1TdXnTh9v8c1YAHvDdut2+zkuyUX3gAY/wI=
|
||||
github.com/mattn/go-isatty v0.0.0-20170925054904-a5cdd64afdee/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-oci8 v0.0.0-20181011085415-1a014d1384b5 h1:+IPgoz43mdEYG5lrqNcjr3DQpAE38SqHtyx1IsqqQGM=
|
||||
github.com/mattn/go-oci8 v0.0.0-20181011085415-1a014d1384b5/go.mod h1:/M9VLO+lUPmxvoOK2PfWRZ8mTtB4q1Hy9lEGijv9Nr8=
|
||||
github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4=
|
||||
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-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-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/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
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/op/go-logging v0.0.0-20160211212156-b2cb9fa56473 h1:J1QZwDXgZ4dJD2s19iqR9+U00OWM2kDzbf1O/fmvCWg=
|
||||
github.com/op/go-logging v0.0.0-20160211212156-b2cb9fa56473/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
||||
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
|
||||
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.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/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/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=
|
||||
github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275 h1:PnBWHBf+6L0jOqq0gIVUe6Yk0/QMZ640k6NvkxcBf+8=
|
||||
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/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
||||
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/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/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
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/echo-swagger v0.0.0-20180315045949-97f46bb9e5a5 h1:yU0aDQpp0Dq4BAu8rrHnVdC6SZS0LceJVLCUCbGasbE=
|
||||
github.com/swaggo/echo-swagger v0.0.0-20180315045949-97f46bb9e5a5/go.mod h1:mGVJdredle61MBSrJEnaLjKYU0qXJ5V5aNsBgypcUCY=
|
||||
github.com/swaggo/files v0.0.0-20180215091130-49c8a91ea3fa h1:194s4modF+3X3POBfGHFCl9LHGjqzWhB/aUyfRiruZU=
|
||||
github.com/swaggo/files v0.0.0-20180215091130-49c8a91ea3fa/go.mod h1:gxQT6pBGRuIGunNf/+tSOB5OHvguWi8Tbt82WOkf35E=
|
||||
github.com/swaggo/swag v1.3.2 h1:pR137hlBoouh2OWd//4F7xchfCXC1ry6yGFbxEjM9d4=
|
||||
github.com/swaggo/swag v1.3.2/go.mod h1:hog2WgeMOrQ/LvQ+o1YGTeT+vWVrbi0SiIslBtxKTyM=
|
||||
github.com/swaggo/swag v1.3.3-0.20180905151112-e8c6d12e2c12 h1:Ty/c+RTnLOIV3ecaR1T43Eu4ehqpRIyKe6kczC+XxRw=
|
||||
github.com/swaggo/swag v1.3.3-0.20180905151112-e8c6d12e2c12/go.mod h1:hog2WgeMOrQ/LvQ+o1YGTeT+vWVrbi0SiIslBtxKTyM=
|
||||
github.com/swaggo/swag v1.3.3-0.20181109030545-8f09470d62b2 h1:HMUGTfTJJZ2fRHar570Q2SdUhiCEGwcRRJ2doOOnCJE=
|
||||
github.com/swaggo/swag v1.3.3-0.20181109030545-8f09470d62b2/go.mod h1:hog2WgeMOrQ/LvQ+o1YGTeT+vWVrbi0SiIslBtxKTyM=
|
||||
github.com/toqueteos/webbrowser v1.1.0 h1:Prj1okiysRgHPoe3B1bOIVxcv+UuSt525BDQmR5W0x0=
|
||||
github.com/toqueteos/webbrowser v1.1.0/go.mod h1:Hqqqmzj8AHn+VlZyVjaRWY20i25hoOZGAABCcg2el4A=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
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/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/valyala/bytebufferpool v0.0.0-20160817181652-e746df99fe4a/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
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/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-20170930174604-9419663f5a44 h1:9lP3x0pW80sDI6t1UMSLA4to18W7R7imwAI/sWS9S8Q=
|
||||
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20180312195533-182114d58262 h1:1NLVUmR8SQ7cNNA5Vo7ronpXbR+5A+9IwIC/bLE7D8Y=
|
||||
golang.org/x/crypto v0.0.0-20180312195533-182114d58262/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-20190123085648-057139ce5d2b h1:Elez2XeF2p9uyVj0yEUDqQ56NFcDtcBNkYP7yv8YbUE=
|
||||
golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576 h1:aUX/1G2gFSs4AsJJg2cL3HuoRhCSCz733FE5GUSuaT4=
|
||||
golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c h1:Vj5n4GlwjmQteupaxJ9+0FNOmBrHfq7vN4btdGoDZgI=
|
||||
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
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/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/sys v0.0.0-20180312081825-c28acc882ebc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
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/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/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-20190123074212-c6b37f3e9285 h1:b5t9HsJXzMmseFB6KtTJWSEtPP8SlVI5nFdf4hnoRFY=
|
||||
golang.org/x/sys v0.0.0-20190123074212-c6b37f3e9285/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 h1:4gbWbmmPFp4ySWICouJl6emP0MyS31yy9SrTlAGFT+g=
|
||||
golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190329044733-9eb1bfa1ce65 h1:hOY+O8MxdkPV10pNf7/XEHaySCiPKxixMKUshfHsGn0=
|
||||
golang.org/x/sys v0.0.0-20190329044733-9eb1bfa1ce65/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=
|
||||
google.golang.org/appengine v1.3.0 h1:FBSsiFRMz3LBeXIomRnVzrQwSDj4ibvcRexLG0LZGQk=
|
||||
google.golang.org/appengine v1.3.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/go-playground/validator.v9 v9.23.0 h1:oq297iqu7qsywIbeW5DBUTtV1nV750Y4q+H8MnDh0Yc=
|
||||
gopkg.in/go-playground/validator.v9 v9.23.0/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
|
||||
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/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
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/testfixtures.v2 v2.4.5 h1:mnfYPBNoJnis+4crs6UzC4lv4GjTVoLXE9B/tW802q0=
|
||||
gopkg.in/testfixtures.v2 v2.4.5/go.mod h1:vyAq+MYCgNpR29qitQdLZhdbLFf4mR/2MFJRFoQZZ2M=
|
||||
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=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
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=
|
||||
honnef.co/go/tools v0.0.0-20190128043916-71123fcbb8fe h1:/GZ/onp6W295MEgrIwtlbnxmFSKGavFp7/D7tMVyuaM=
|
||||
honnef.co/go/tools v0.0.0-20190128043916-71123fcbb8fe/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190215041234-466a0476246c h1:z+UFwlQ7KVwdlQTE5JjvDvfZmyyAVrEiiwau20b7X8k=
|
||||
honnef.co/go/tools v0.0.0-20190215041234-466a0476246c/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
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=
|
||||
|
||||
83
main.go
83
main.go
@@ -1,70 +1,23 @@
|
||||
// Vikunja is a todo-list application to facilitate your life.
|
||||
// Copyright 2018 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 main
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/docs"
|
||||
"code.vikunja.io/api/pkg/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()
|
||||
|
||||
// Init Config
|
||||
err := config.InitConfig()
|
||||
if err != nil {
|
||||
log.Log.Error(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Set Engine
|
||||
err = models.SetEngine()
|
||||
if err != nil {
|
||||
log.Log.Error(err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
// Vikunja is a todo-list application to facilitate your life.
|
||||
// Copyright 2018 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 caldav
|
||||
|
||||
import (
|
||||
@@ -77,7 +93,8 @@ END:VCALENDAR` // Need a line break
|
||||
}
|
||||
|
||||
func makeCalDavTimeFromUnixTime(unixtime int64) (caldavtime string) {
|
||||
tm := time.Unix(unixtime, 0)
|
||||
tz, _ := time.LoadLocation("UTC")
|
||||
tm := time.Unix(unixtime, 0).In(tz)
|
||||
return tm.Format("20060102T150405")
|
||||
}
|
||||
|
||||
|
||||
246
pkg/caldav/caldav_test.go
Normal file
246
pkg/caldav/caldav_test.go
Normal file
@@ -0,0 +1,246 @@
|
||||
// Vikunja is a todo-list application to facilitate your life.
|
||||
// Copyright 2018 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 caldav
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestParseEvents(t *testing.T) {
|
||||
type args struct {
|
||||
config *Config
|
||||
events []*Event
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantCaldavevents string
|
||||
}{
|
||||
{
|
||||
name: "Test caldavparsing without reminders",
|
||||
args: args{
|
||||
config: &Config{
|
||||
Name: "test",
|
||||
ProdID: "RandomProdID which is not random",
|
||||
},
|
||||
events: []*Event{
|
||||
{
|
||||
Summary: "Event #1",
|
||||
Description: "Lorem Ipsum",
|
||||
UID: "randommduid",
|
||||
TimestampUnix: 1543626724,
|
||||
StartUnix: 1543626724,
|
||||
EndUnix: 1543627824,
|
||||
},
|
||||
{
|
||||
Summary: "Event #2",
|
||||
UID: "randommduidd",
|
||||
TimestampUnix: 1543726724,
|
||||
StartUnix: 1543726724,
|
||||
EndUnix: 1543738724,
|
||||
},
|
||||
{
|
||||
Summary: "Event #3 with empty uid",
|
||||
UID: "20181202T0600242aaef4a81d770c1e775e26bc5abebc87f1d3d7bffaa83",
|
||||
TimestampUnix: 1543726824,
|
||||
StartUnix: 1543726824,
|
||||
EndUnix: 1543727000,
|
||||
},
|
||||
},
|
||||
},
|
||||
wantCaldavevents: `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
X-PUBLISHED-TTL:PT4H
|
||||
X-WR-CALNAME:test
|
||||
PRODID:-//RandomProdID which is not random//EN
|
||||
BEGIN:VEVENT
|
||||
UID:randommduid
|
||||
SUMMARY:Event #1
|
||||
DESCRIPTION:Lorem Ipsum
|
||||
DTSTAMP:20181201T011204
|
||||
DTSTART:20181201T011204
|
||||
DTEND:20181201T013024
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:randommduidd
|
||||
SUMMARY:Event #2
|
||||
DESCRIPTION:
|
||||
DTSTAMP:20181202T045844
|
||||
DTSTART:20181202T045844
|
||||
DTEND:20181202T081844
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:20181202T0600242aaef4a81d770c1e775e26bc5abebc87f1d3d7bffaa83
|
||||
SUMMARY:Event #3 with empty uid
|
||||
DESCRIPTION:
|
||||
DTSTAMP:20181202T050024
|
||||
DTSTART:20181202T050024
|
||||
DTEND:20181202T050320
|
||||
END:VEVENT
|
||||
END:VCALENDAR`,
|
||||
},
|
||||
{
|
||||
name: "Test caldavparsing with reminders",
|
||||
args: args{
|
||||
config: &Config{
|
||||
Name: "test2",
|
||||
ProdID: "RandomProdID which is not random",
|
||||
},
|
||||
events: []*Event{
|
||||
{
|
||||
Summary: "Event #1",
|
||||
Description: "Lorem Ipsum",
|
||||
UID: "randommduid",
|
||||
TimestampUnix: 1543626724,
|
||||
StartUnix: 1543626724,
|
||||
EndUnix: 1543627824,
|
||||
Alarms: []Alarm{
|
||||
{TimeUnix: 1543626524},
|
||||
{TimeUnix: 1543626224},
|
||||
{TimeUnix: 1543626024},
|
||||
},
|
||||
},
|
||||
{
|
||||
Summary: "Event #2",
|
||||
UID: "randommduidd",
|
||||
TimestampUnix: 1543726724,
|
||||
StartUnix: 1543726724,
|
||||
EndUnix: 1543738724,
|
||||
Alarms: []Alarm{
|
||||
{TimeUnix: 1543626524},
|
||||
{TimeUnix: 1543626224},
|
||||
{TimeUnix: 1543626024},
|
||||
},
|
||||
},
|
||||
{
|
||||
Summary: "Event #3 with empty uid",
|
||||
TimestampUnix: 1543726824,
|
||||
StartUnix: 1543726824,
|
||||
EndUnix: 1543727000,
|
||||
Alarms: []Alarm{
|
||||
{TimeUnix: 1543626524},
|
||||
{TimeUnix: 1543626224},
|
||||
{TimeUnix: 1543626024},
|
||||
{TimeUnix: 1543826824},
|
||||
},
|
||||
},
|
||||
{
|
||||
Summary: "Event #4 without any",
|
||||
TimestampUnix: 1543726824,
|
||||
StartUnix: 1543726824,
|
||||
EndUnix: 1543727000,
|
||||
},
|
||||
},
|
||||
},
|
||||
wantCaldavevents: `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
X-PUBLISHED-TTL:PT4H
|
||||
X-WR-CALNAME:test2
|
||||
PRODID:-//RandomProdID which is not random//EN
|
||||
BEGIN:VEVENT
|
||||
UID:randommduid
|
||||
SUMMARY:Event #1
|
||||
DESCRIPTION:Lorem Ipsum
|
||||
DTSTAMP:20181201T011204
|
||||
DTSTART:20181201T011204
|
||||
DTEND:20181201T013024
|
||||
BEGIN:VALARM
|
||||
TRIGGER:-PT3M
|
||||
ACTION:DISPLAY
|
||||
DESCRIPTION:Event #1
|
||||
END:VALARM
|
||||
BEGIN:VALARM
|
||||
TRIGGER:-PT8M
|
||||
ACTION:DISPLAY
|
||||
DESCRIPTION:Event #1
|
||||
END:VALARM
|
||||
BEGIN:VALARM
|
||||
TRIGGER:-PT11M
|
||||
ACTION:DISPLAY
|
||||
DESCRIPTION:Event #1
|
||||
END:VALARM
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:randommduidd
|
||||
SUMMARY:Event #2
|
||||
DESCRIPTION:
|
||||
DTSTAMP:20181202T045844
|
||||
DTSTART:20181202T045844
|
||||
DTEND:20181202T081844
|
||||
BEGIN:VALARM
|
||||
TRIGGER:-PT1670M
|
||||
ACTION:DISPLAY
|
||||
DESCRIPTION:Event #2
|
||||
END:VALARM
|
||||
BEGIN:VALARM
|
||||
TRIGGER:-PT1675M
|
||||
ACTION:DISPLAY
|
||||
DESCRIPTION:Event #2
|
||||
END:VALARM
|
||||
BEGIN:VALARM
|
||||
TRIGGER:-PT1678M
|
||||
ACTION:DISPLAY
|
||||
DESCRIPTION:Event #2
|
||||
END:VALARM
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:20181202T0500242aaef4a81d770c1e775e26bc5abebc87f1d3d7bffaa83
|
||||
SUMMARY:Event #3 with empty uid
|
||||
DESCRIPTION:
|
||||
DTSTAMP:20181202T050024
|
||||
DTSTART:20181202T050024
|
||||
DTEND:20181202T050320
|
||||
BEGIN:VALARM
|
||||
TRIGGER:-PT1671M
|
||||
ACTION:DISPLAY
|
||||
DESCRIPTION:Event #3 with empty uid
|
||||
END:VALARM
|
||||
BEGIN:VALARM
|
||||
TRIGGER:-PT1676M
|
||||
ACTION:DISPLAY
|
||||
DESCRIPTION:Event #3 with empty uid
|
||||
END:VALARM
|
||||
BEGIN:VALARM
|
||||
TRIGGER:-PT1680M
|
||||
ACTION:DISPLAY
|
||||
DESCRIPTION:Event #3 with empty uid
|
||||
END:VALARM
|
||||
BEGIN:VALARM
|
||||
TRIGGER:PT1666M
|
||||
ACTION:DISPLAY
|
||||
DESCRIPTION:Event #3 with empty uid
|
||||
END:VALARM
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:20181202T050024ae7548ce9556df85038abe90dc674d4741a61ce74d1cf
|
||||
SUMMARY:Event #4 without any
|
||||
DESCRIPTION:
|
||||
DTSTAMP:20181202T050024
|
||||
DTSTART:20181202T050024
|
||||
DTEND:20181202T050320
|
||||
END:VEVENT
|
||||
END:VCALENDAR`,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if gotCaldavevents := ParseEvents(tt.args.config, tt.args.events); gotCaldavevents != tt.wantCaldavevents {
|
||||
t.Errorf("ParseEvents() = %v, want %v", gotCaldavevents, tt.wantCaldavevents)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
54
pkg/cmd/cmd.go
Normal file
54
pkg/cmd/cmd.go
Normal file
@@ -0,0 +1,54 @@
|
||||
// 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"
|
||||
"fmt"
|
||||
"github.com/spf13/cobra"
|
||||
"os"
|
||||
)
|
||||
|
||||
// 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"
|
||||
|
||||
func init() {
|
||||
cobra.OnInitialize(config.InitConfig)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
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)
|
||||
},
|
||||
}
|
||||
34
pkg/cmd/version.go
Normal file
34
pkg/cmd/version.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 cmd
|
||||
|
||||
import (
|
||||
"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)
|
||||
},
|
||||
}
|
||||
87
pkg/cmd/web.go
Normal file
87
pkg/cmd/web.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 cmd
|
||||
|
||||
import (
|
||||
"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/routes"
|
||||
"code.vikunja.io/api/pkg/swagger"
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"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) {
|
||||
|
||||
// Set logger
|
||||
log.InitLogger()
|
||||
|
||||
// Run the migrations
|
||||
migration.Migrate(nil)
|
||||
|
||||
// Set Engine
|
||||
err := models.SetEngine()
|
||||
if err != nil {
|
||||
log.Log.Fatal(err.Error())
|
||||
}
|
||||
|
||||
// Start the mail daemon
|
||||
mail.StartMailDaemon()
|
||||
|
||||
// Version notification
|
||||
fmt.Printf("Vikunja version %s\n", Version)
|
||||
|
||||
// Additional swagger information
|
||||
swagger.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, 1)
|
||||
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)
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -1,6 +1,23 @@
|
||||
// Vikunja is a todo-list application to facilitate your life.
|
||||
// Copyright 2018 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 config
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"github.com/spf13/viper"
|
||||
@@ -10,19 +27,20 @@ import (
|
||||
)
|
||||
|
||||
// InitConfig initializes the config, sets defaults etc.
|
||||
func InitConfig() (err error) {
|
||||
func InitConfig() {
|
||||
|
||||
// Set defaults
|
||||
// Service config
|
||||
random, err := random(32)
|
||||
if err != nil {
|
||||
return err
|
||||
log.Log.Fatal(err.Error())
|
||||
}
|
||||
|
||||
// Service
|
||||
viper.SetDefault("service.JWTSecret", random)
|
||||
viper.SetDefault("service.interface", ":3456")
|
||||
viper.SetDefault("service.frontendurl", "")
|
||||
|
||||
ex, err := os.Executable()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -30,6 +48,7 @@ func InitConfig() (err error) {
|
||||
exPath := filepath.Dir(ex)
|
||||
viper.SetDefault("service.rootpath", exPath)
|
||||
viper.SetDefault("service.pagecount", 50)
|
||||
viper.SetDefault("service.enablemetrics", false)
|
||||
// Database
|
||||
viper.SetDefault("database.type", "sqlite")
|
||||
viper.SetDefault("database.host", "localhost")
|
||||
@@ -37,15 +56,13 @@ func InitConfig() (err error) {
|
||||
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)
|
||||
// Cacher
|
||||
viper.SetDefault("cache.enabled", false)
|
||||
viper.SetDefault("cache.type", "memory")
|
||||
viper.SetDefault("cache.maxelementsize", 1000)
|
||||
viper.SetDefault("cache.redishost", "localhost:6379")
|
||||
viper.SetDefault("cache.redispassword", "")
|
||||
// Mailer
|
||||
viper.SetDefault("mailer.enabled", false)
|
||||
viper.SetDefault("mailer.host", "")
|
||||
viper.SetDefault("mailer.port", "587")
|
||||
viper.SetDefault("mailer.user", "user")
|
||||
@@ -54,6 +71,19 @@ func InitConfig() (err error) {
|
||||
viper.SetDefault("mailer.fromemail", "mail@vikunja")
|
||||
viper.SetDefault("mailer.queuelength", 100)
|
||||
viper.SetDefault("mailer.queuetimeout", 30)
|
||||
// Redis
|
||||
viper.SetDefault("redis.enabled", false)
|
||||
viper.SetDefault("redis.host", "localhost:6379")
|
||||
viper.SetDefault("redis.password", "")
|
||||
viper.SetDefault("redis.db", 0)
|
||||
// Logger
|
||||
viper.SetDefault("log.enabled", true)
|
||||
viper.SetDefault("log.errors", "stdout")
|
||||
viper.SetDefault("log.standard", "stdout")
|
||||
viper.SetDefault("log.database", "off")
|
||||
viper.SetDefault("log.http", "stdout")
|
||||
viper.SetDefault("log.echo", "off")
|
||||
viper.SetDefault("log.path", viper.GetString("service.rootpath")+"/logs")
|
||||
|
||||
// Init checking for environment variables
|
||||
viper.SetEnvPrefix("vikunja")
|
||||
@@ -61,15 +91,16 @@ func InitConfig() (err error) {
|
||||
viper.AutomaticEnv()
|
||||
|
||||
// Load the config file
|
||||
viper.AddConfigPath(viper.GetString("service.rootpath"))
|
||||
viper.AddConfigPath("/etc/vikunja/")
|
||||
viper.AddConfigPath("~/.config/vikunja")
|
||||
viper.AddConfigPath(".")
|
||||
viper.SetConfigName("config")
|
||||
err = viper.ReadInConfig()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
fmt.Println("Using defaults.")
|
||||
log.Log.Info(err)
|
||||
log.Log.Info("Using defaults.")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func random(length int) (string, error) {
|
||||
|
||||
78
pkg/db/db.go
Normal file
78
pkg/db/db.go
Normal file
@@ -0,0 +1,78 @@
|
||||
// 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"
|
||||
"fmt"
|
||||
"github.com/go-xorm/core"
|
||||
"github.com/go-xorm/xorm"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
_ "github.com/go-sql-driver/mysql" // Because.
|
||||
_ "github.com/mattn/go-sqlite3" // Because.
|
||||
)
|
||||
|
||||
// CreateDBEngine initializes a db engine from the config
|
||||
func CreateDBEngine() (engine *xorm.Engine, err error) {
|
||||
// If the database type is not set, this likely means we need to initialize the config first
|
||||
if viper.GetString("database.type") == "" {
|
||||
config.InitConfig()
|
||||
}
|
||||
|
||||
// Use Mysql if set
|
||||
if viper.GetString("database.type") == "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(viper.GetString("log.database") != "off")
|
||||
engine.SetLogger(xorm.NewSimpleLogger(log.GetLogWriter("database")))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func initMysqlEngine() (engine *xorm.Engine, err error) {
|
||||
connStr := fmt.Sprintf(
|
||||
"%s:%s@tcp(%s)/%s?charset=utf8&parseTime=true",
|
||||
viper.GetString("database.user"),
|
||||
viper.GetString("database.password"),
|
||||
viper.GetString("database.host"),
|
||||
viper.GetString("database.database"))
|
||||
engine, err = xorm.NewEngine("mysql", connStr)
|
||||
engine.SetMaxOpenConns(viper.GetInt("database.openconnections"))
|
||||
return
|
||||
}
|
||||
|
||||
func initSqliteEngine() (engine *xorm.Engine, err error) {
|
||||
path := viper.GetString("database.path")
|
||||
if path == "" {
|
||||
path = "./db.db"
|
||||
}
|
||||
|
||||
return xorm.NewEngine("sqlite3", path)
|
||||
}
|
||||
@@ -1,20 +1,101 @@
|
||||
// Vikunja is a todo-list application to facilitate your life.
|
||||
// Copyright 2018 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 log
|
||||
|
||||
import (
|
||||
"github.com/op/go-logging"
|
||||
"github.com/spf13/viper"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ErrFmt holds the format for all the console logging
|
||||
const ErrFmt = `${time_rfc3339_nano}: ${level} ` + "\t" + `▶ ${prefix} ${short_file}:${line}`
|
||||
|
||||
// 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}`
|
||||
|
||||
// Log is the handler for the logger
|
||||
var Log = logging.MustGetLogger("vikunja")
|
||||
|
||||
var format = logging.MustStringFormatter(
|
||||
`%{color}%{time:2006-01-02 15:04:05.000} %{shortfunc} ▶ %{level:.4s} %{id:03x}%{color:reset} %{message}`,
|
||||
)
|
||||
|
||||
// InitLogger initializes the global log handler
|
||||
func InitLogger() {
|
||||
backend := logging.NewLogBackend(os.Stderr, "", 0)
|
||||
backendFormatter := logging.NewBackendFormatter(backend, format)
|
||||
logging.SetBackend(backendFormatter)
|
||||
if !viper.GetBool("log.enabled") {
|
||||
// Disable all logging when loggin in general is disabled, overwriting everything a user might have set.
|
||||
viper.Set("log.errors", "off")
|
||||
viper.Set("log.standard", "off")
|
||||
viper.Set("log.database", "off")
|
||||
viper.Set("log.http", "off")
|
||||
viper.Set("log.echo", "off")
|
||||
return
|
||||
}
|
||||
|
||||
if viper.GetString("log.errors") == "file" || viper.GetString("log.standard") == "file" {
|
||||
err := os.Mkdir(viper.GetString("log.path"), 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 viper.GetString("log.standard") != "off" {
|
||||
stdWriter := GetLogWriter("standard")
|
||||
stdBackend := logging.NewLogBackend(stdWriter, "", 0)
|
||||
|
||||
// Set the standard backend
|
||||
logBackends = append(logBackends, logging.NewBackendFormatter(stdBackend, logging.MustStringFormatter(Fmt+"\n")))
|
||||
}
|
||||
|
||||
if viper.GetString("log.error") != "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.Stderr // Set the default case to prevent nil pointer panics
|
||||
switch viper.GetString("log." + logfile) {
|
||||
case "file":
|
||||
f, err := os.OpenFile(viper.GetString("log.path")+"/"+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
|
||||
}
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
// Vikunja is a todo-list application to facilitate your life.
|
||||
// Copyright 2018 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 mail
|
||||
|
||||
import (
|
||||
@@ -15,6 +31,10 @@ var Queue chan *gomail.Message
|
||||
func StartMailDaemon() {
|
||||
Queue = make(chan *gomail.Message, viper.GetInt("mailer.queuelength"))
|
||||
|
||||
if !viper.GetBool("mailer.enabled") {
|
||||
return
|
||||
}
|
||||
|
||||
if viper.GetString("mailer.host") == "" {
|
||||
log.Log.Warning("Mailer seems to be not configured! Please see the config docs for more details.")
|
||||
return
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
// Vikunja is a todo-list application to facilitate your life.
|
||||
// Copyright 2018 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 mail
|
||||
|
||||
import (
|
||||
|
||||
92
pkg/metrics/active_users.go
Normal file
92
pkg/metrics/active_users.go
Normal file
@@ -0,0 +1,92 @@
|
||||
// Vikunja is a todo-list application to facilitate your life.
|
||||
// Copyright 2018 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 metrics
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"encoding/gob"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SecondsUntilInactive defines the seconds until a user is considered inactive
|
||||
const SecondsUntilInactive = 60
|
||||
|
||||
// ActiveUsersKey is the key used to store active users in redis
|
||||
const ActiveUsersKey = `activeusers`
|
||||
|
||||
// ActiveUser defines an active user
|
||||
type ActiveUser struct {
|
||||
UserID int64
|
||||
LastSeen time.Time
|
||||
}
|
||||
|
||||
func init() {
|
||||
promauto.NewGaugeFunc(prometheus.GaugeOpts{
|
||||
Name: "vikunja_active_users",
|
||||
Help: "The currently active users on this node",
|
||||
}, func() float64 {
|
||||
|
||||
allActiveUsers, err := GetActiveUsers()
|
||||
if err != nil {
|
||||
log.Log.Error(err.Error())
|
||||
}
|
||||
activeUsersCount := 0
|
||||
for _, u := range allActiveUsers {
|
||||
if time.Since(u.LastSeen) < SecondsUntilInactive*time.Second {
|
||||
activeUsersCount++
|
||||
}
|
||||
}
|
||||
return float64(activeUsersCount)
|
||||
})
|
||||
}
|
||||
|
||||
// GetActiveUsers returns the active users from redis
|
||||
func GetActiveUsers() (users []*ActiveUser, err error) {
|
||||
|
||||
activeUsersR, err := r.Get(ActiveUsersKey).Bytes()
|
||||
if err != nil {
|
||||
if err.Error() == "redis: nil" {
|
||||
return users, nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var b bytes.Buffer
|
||||
_, err = b.Write(activeUsersR)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d := gob.NewDecoder(&b)
|
||||
if err := d.Decode(&users); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// SetActiveUsers sets the active users from redis
|
||||
func SetActiveUsers(users []*ActiveUser) (err error) {
|
||||
var b bytes.Buffer
|
||||
e := gob.NewEncoder(&b)
|
||||
if err := e.Encode(users); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return r.Set(ActiveUsersKey, b.Bytes(), 0).Err()
|
||||
}
|
||||
123
pkg/metrics/metrics.go
Normal file
123
pkg/metrics/metrics.go
Normal file
@@ -0,0 +1,123 @@
|
||||
// Vikunja is a todo-list application to facilitate your life.
|
||||
// Copyright 2018 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 metrics
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/red"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var r = red.GetRedis()
|
||||
|
||||
const (
|
||||
// ListCountKey is the name of the key in which we save the list count
|
||||
ListCountKey = `listcount`
|
||||
|
||||
// UserCountKey is the name of the key we use to store total users in redis
|
||||
UserCountKey = `usercount`
|
||||
|
||||
// NamespaceCountKey is the name of the key we use to store the amount of total namespaces in redis
|
||||
NamespaceCountKey = `namespacecount`
|
||||
|
||||
// TaskCountKey is the name of the key we use to store the amount of total tasks in redis
|
||||
TaskCountKey = `taskcount`
|
||||
|
||||
// TeamCountKey is the name of the key we use to store the amount of total teams in redis
|
||||
TeamCountKey = `teamcount`
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Register total list count metric
|
||||
promauto.NewGaugeFunc(prometheus.GaugeOpts{
|
||||
Name: "vikunja_list_count",
|
||||
Help: "The number of lists on this instance",
|
||||
}, func() float64 {
|
||||
count, _ := GetCount(ListCountKey)
|
||||
return float64(count)
|
||||
})
|
||||
|
||||
// Register total user count metric
|
||||
promauto.NewGaugeFunc(prometheus.GaugeOpts{
|
||||
Name: "vikunja_user_count",
|
||||
Help: "The total number of users on this instance",
|
||||
}, func() float64 {
|
||||
count, _ := GetCount(UserCountKey)
|
||||
return float64(count)
|
||||
})
|
||||
|
||||
// Register total Namespaces count metric
|
||||
promauto.NewGaugeFunc(prometheus.GaugeOpts{
|
||||
Name: "vikunja_namespcae_count",
|
||||
Help: "The total number of namespaces on this instance",
|
||||
}, func() float64 {
|
||||
count, _ := GetCount(NamespaceCountKey)
|
||||
return float64(count)
|
||||
})
|
||||
|
||||
// Register total Tasks count metric
|
||||
promauto.NewGaugeFunc(prometheus.GaugeOpts{
|
||||
Name: "vikunja_task_count",
|
||||
Help: "The total number of tasks on this instance",
|
||||
}, func() float64 {
|
||||
count, _ := GetCount(TaskCountKey)
|
||||
return float64(count)
|
||||
})
|
||||
|
||||
// Register total user count metric
|
||||
promauto.NewGaugeFunc(prometheus.GaugeOpts{
|
||||
Name: "vikunja_team_count",
|
||||
Help: "The total number of teams on this instance",
|
||||
}, func() float64 {
|
||||
count, _ := GetCount(TeamCountKey)
|
||||
return float64(count)
|
||||
})
|
||||
}
|
||||
|
||||
// GetCount returns the current count from redis
|
||||
func GetCount(key string) (count int64, err error) {
|
||||
count, err = r.Get(key).Int64()
|
||||
if err != nil && err.Error() != "redis: nil" {
|
||||
return
|
||||
}
|
||||
err = nil
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// SetCount sets the list count to a given value
|
||||
func SetCount(count int64, key string) error {
|
||||
return r.Set(key, count, 0).Err()
|
||||
}
|
||||
|
||||
// UpdateCount updates a count with a given amount
|
||||
func UpdateCount(update int64, key string) {
|
||||
if !viper.GetBool("service.enablemetrics") {
|
||||
return
|
||||
}
|
||||
oldtotal, err := GetCount(key)
|
||||
if err != nil {
|
||||
log.Log.Error(err.Error())
|
||||
}
|
||||
|
||||
err = SetCount(oldtotal+update, key)
|
||||
if err != nil {
|
||||
log.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{})
|
||||
},
|
||||
})
|
||||
}
|
||||
44
pkg/migration/20190328074430.go
Normal file
44
pkg/migration/20190328074430.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 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{})
|
||||
},
|
||||
})
|
||||
}
|
||||
124
pkg/migration/migration.go
Normal file
124
pkg/migration/migration.go
Normal file
@@ -0,0 +1,124 @@
|
||||
// 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/db"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"github.com/go-xorm/xorm"
|
||||
"github.com/olekukonko/tablewriter"
|
||||
"github.com/spf13/viper"
|
||||
"os"
|
||||
"sort"
|
||||
"src.techknowlogick.com/xormigrate"
|
||||
)
|
||||
|
||||
// You can get the id string for new migrations by running `date +%Y%m%d%H%M%S` on a unix system.
|
||||
|
||||
var migrations []*xormigrate.Migration
|
||||
|
||||
// A helper function because we need a migration in various places which we can't really solve with an init() function.
|
||||
func initMigration(x *xorm.Engine) *xormigrate.Xormigrate {
|
||||
// Get our own xorm engine if we don't have one
|
||||
if x == nil {
|
||||
var err error
|
||||
x, err = db.CreateDBEngine()
|
||||
if err != nil {
|
||||
log.Log.Criticalf("Could not connect to db: %v", err.Error())
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Because init() does not guarantee the order in which these are added to the slice,
|
||||
// we need to sort them to ensure that they are in order
|
||||
sort.Slice(migrations, func(i, j int) bool {
|
||||
return migrations[i].ID < migrations[j].ID
|
||||
})
|
||||
|
||||
m := xormigrate.New(x, migrations)
|
||||
m.NewLogger(log.GetLogWriter("database"))
|
||||
m.InitSchema(initSchema)
|
||||
return m
|
||||
}
|
||||
|
||||
// Migrate runs all migrations
|
||||
func Migrate(x *xorm.Engine) {
|
||||
m := initMigration(x)
|
||||
err := m.Migrate()
|
||||
if err != nil {
|
||||
log.Log.Fatalf("Migration failed: %v", err)
|
||||
}
|
||||
log.Log.Info("Ran all migrations successfully.")
|
||||
}
|
||||
|
||||
// ListMigrations pretty-prints a list with all migrations.
|
||||
func ListMigrations() {
|
||||
x, err := db.CreateDBEngine()
|
||||
if err != nil {
|
||||
log.Log.Fatalf("Could not connect to db: %v", err.Error())
|
||||
}
|
||||
ms := []*xormigrate.Migration{}
|
||||
err = x.Find(&ms)
|
||||
if err != nil {
|
||||
log.Log.Fatalf("Error getting migration table: %v", err.Error())
|
||||
}
|
||||
|
||||
table := tablewriter.NewWriter(os.Stdout)
|
||||
table.SetHeader([]string{"ID", "Description"})
|
||||
table.SetAlignment(tablewriter.ALIGN_LEFT)
|
||||
table.SetHeaderColor(tablewriter.Colors{tablewriter.Bold, tablewriter.BgGreenColor},
|
||||
tablewriter.Colors{tablewriter.Bold, tablewriter.BgGreenColor})
|
||||
|
||||
for _, m := range ms {
|
||||
table.Append([]string{m.ID, m.Description})
|
||||
}
|
||||
table.Render()
|
||||
}
|
||||
|
||||
// Rollback rolls back all migrations until a certain point.
|
||||
func Rollback(migrationID string) {
|
||||
m := initMigration(nil)
|
||||
err := m.RollbackTo(migrationID)
|
||||
if err != nil {
|
||||
log.Log.Fatalf("Could not rollback: %v", err)
|
||||
}
|
||||
log.Log.Info("Rolled back successfully.")
|
||||
}
|
||||
|
||||
// Deletes a column from a table. All arguments are strings, to let them be standalone and not depending on any struct.
|
||||
func dropTableColum(x *xorm.Engine, tableName, col string) error {
|
||||
|
||||
switch viper.GetString("database.type") {
|
||||
case "sqlite":
|
||||
log.Log.Warning("Unable to drop columns in SQLite")
|
||||
case "mysql":
|
||||
_, err := x.Exec("ALTER TABLE " + tableName + " DROP COLUMN " + col)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
log.Log.Fatal("Unknown db.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func initSchema(tx *xorm.Engine) error {
|
||||
return tx.Sync2(
|
||||
models.GetTables()...,
|
||||
)
|
||||
}
|
||||
135
pkg/models/bulk_list_task.go
Normal file
135
pkg/models/bulk_list_task.go
Normal file
@@ -0,0 +1,135 @@
|
||||
// Vikunja is a todo-list application to facilitate your life.
|
||||
// Copyright 2018 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 models
|
||||
|
||||
import (
|
||||
"code.vikunja.io/web"
|
||||
"github.com/imdario/mergo"
|
||||
)
|
||||
|
||||
// BulkTask is the definition of a bulk update task
|
||||
type BulkTask struct {
|
||||
// A list of task ids to update
|
||||
IDs []int64 `json:"task_ids"`
|
||||
Tasks []*ListTask `json:"-"`
|
||||
ListTask
|
||||
}
|
||||
|
||||
func (bt *BulkTask) checkIfTasksAreOnTheSameList() (err error) {
|
||||
// Get the tasks
|
||||
err = bt.GetTasksByIDs()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(bt.Tasks) == 0 {
|
||||
return ErrBulkTasksNeedAtLeastOne{}
|
||||
}
|
||||
|
||||
// Check if all tasks are in the same list
|
||||
var firstListID = bt.Tasks[0].ListID
|
||||
for _, t := range bt.Tasks {
|
||||
if t.ListID != firstListID {
|
||||
return ErrBulkTasksMustBeInSameList{firstListID, t.ListID}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CanUpdate checks if a user is allowed to update a task
|
||||
func (bt *BulkTask) CanUpdate(a web.Auth) (bool, error) {
|
||||
|
||||
err := bt.checkIfTasksAreOnTheSameList()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// A user can update an task if he has write acces to its list
|
||||
l := &List{ID: bt.Tasks[0].ListID}
|
||||
return l.CanWrite(a)
|
||||
}
|
||||
|
||||
// Update updates a bunch of tasks at once
|
||||
// @Summary Update a bunch of tasks at once
|
||||
// @Description Updates a bunch of tasks at once. This includes marking them as done. Note: although you could supply another ID, it will be ignored. Use task_ids instead.
|
||||
// @tags task
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security JWTKeyAuth
|
||||
// @Param task body models.BulkTask true "The task object. Looks like a normal task, the only difference is it uses an array of list_ids to update."
|
||||
// @Success 200 {object} models.ListTask "The updated task object."
|
||||
// @Failure 400 {object} code.vikunja.io/web.HTTPError "Invalid task object provided."
|
||||
// @Failure 403 {object} code.vikunja.io/web.HTTPError "The user does not have access to the task (aka its list)"
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /tasks/bulk [post]
|
||||
func (bt *BulkTask) Update() (err error) {
|
||||
|
||||
sess := x.NewSession()
|
||||
defer sess.Close()
|
||||
|
||||
err = sess.Begin()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, oldtask := range bt.Tasks {
|
||||
|
||||
// When a repeating task is marked as done, we update all deadlines and reminders and set it as undone
|
||||
updateDone(oldtask, &bt.ListTask)
|
||||
|
||||
// Update the assignees
|
||||
if err := oldtask.updateTaskAssignees(bt.Assignees); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// For whatever reason, xorm dont detect if done is updated, so we need to update this every time by hand
|
||||
// Which is why we merge the actual task struct with the one we got from the
|
||||
// The user struct overrides values in the actual one.
|
||||
if err := mergo.Merge(oldtask, &bt.ListTask, mergo.WithOverride); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// And because a false is considered to be a null value, we need to explicitly check that case here.
|
||||
if !bt.ListTask.Done {
|
||||
oldtask.Done = false
|
||||
}
|
||||
|
||||
_, err = sess.ID(oldtask.ID).
|
||||
Cols("text",
|
||||
"description",
|
||||
"done",
|
||||
"due_date_unix",
|
||||
"reminders_unix",
|
||||
"repeat_after",
|
||||
"parent_task_id",
|
||||
"priority",
|
||||
"start_date_unix",
|
||||
"end_date_unix").
|
||||
Update(oldtask)
|
||||
if err != nil {
|
||||
return sess.Rollback()
|
||||
}
|
||||
}
|
||||
|
||||
err = sess.Commit()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
69
pkg/models/bulk_list_task_test.go
Normal file
69
pkg/models/bulk_list_task_test.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBulkTask_Update(t *testing.T) {
|
||||
type fields struct {
|
||||
IDs []int64
|
||||
Tasks []*ListTask
|
||||
ListTask ListTask
|
||||
User *User
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
wantErr bool
|
||||
wantForbidden bool
|
||||
}{
|
||||
{
|
||||
name: "Test normal update",
|
||||
fields: fields{
|
||||
IDs: []int64{10, 11, 12},
|
||||
ListTask: ListTask{
|
||||
Text: "bulkupdated",
|
||||
},
|
||||
User: &User{ID: 1},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Test with one task on different list",
|
||||
fields: fields{
|
||||
IDs: []int64{10, 11, 12, 13},
|
||||
ListTask: ListTask{
|
||||
Text: "bulkupdated",
|
||||
},
|
||||
User: &User{ID: 1},
|
||||
},
|
||||
wantForbidden: true,
|
||||
},
|
||||
{
|
||||
name: "Test without any tasks",
|
||||
fields: fields{
|
||||
IDs: []int64{},
|
||||
ListTask: ListTask{
|
||||
Text: "bulkupdated",
|
||||
},
|
||||
User: &User{ID: 1},
|
||||
},
|
||||
wantForbidden: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
bt := &BulkTask{
|
||||
IDs: tt.fields.IDs,
|
||||
Tasks: tt.fields.Tasks,
|
||||
ListTask: tt.fields.ListTask,
|
||||
}
|
||||
allowed, _ := bt.CanUpdate(tt.fields.User)
|
||||
if !allowed != tt.wantForbidden {
|
||||
t.Errorf("BulkTask.Update() want forbidden, got %v, want %v", allowed, tt.wantForbidden)
|
||||
}
|
||||
if err := bt.Update(); (err != nil) != tt.wantErr {
|
||||
t.Errorf("BulkTask.Update() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package models
|
||||
|
||||
// CRUDable defines the crud methods
|
||||
type CRUDable interface {
|
||||
Create(*User) error
|
||||
ReadOne() error
|
||||
ReadAll(string, *User, int) (interface{}, error)
|
||||
Update() error
|
||||
Delete() error
|
||||
}
|
||||
@@ -1,22 +1,27 @@
|
||||
// Vikunja is a todo-list application to facilitate your life.
|
||||
// Copyright 2018 Vikunja and contributors. All web.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 models
|
||||
|
||||
import (
|
||||
"code.vikunja.io/web"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// HTTPErrorProcessor is executed when the defined error is thrown, it will make sure the user sees an appropriate error message and http status code
|
||||
type HTTPErrorProcessor interface {
|
||||
HTTPError() HTTPError
|
||||
}
|
||||
|
||||
// HTTPError holds informations about an http error
|
||||
type HTTPError struct {
|
||||
HTTPCode int `json:"-"`
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// =====================
|
||||
// User Operation Errors
|
||||
// =====================
|
||||
@@ -41,8 +46,8 @@ func (err ErrUsernameExists) Error() string {
|
||||
const ErrorCodeUsernameExists = 1001
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrUsernameExists) HTTPError() HTTPError {
|
||||
return HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrorCodeUsernameExists, Message: "A user with this username already exists."}
|
||||
func (err ErrUsernameExists) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrorCodeUsernameExists, Message: "A user with this username already exists."}
|
||||
}
|
||||
|
||||
// ErrUserEmailExists represents a "UserEmailExists" kind of error.
|
||||
@@ -65,8 +70,8 @@ func (err ErrUserEmailExists) Error() string {
|
||||
const ErrorCodeUserEmailExists = 1002
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrUserEmailExists) HTTPError() HTTPError {
|
||||
return HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrorCodeUserEmailExists, Message: "A user with this email address already exists."}
|
||||
func (err ErrUserEmailExists) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrorCodeUserEmailExists, Message: "A user with this email address already exists."}
|
||||
}
|
||||
|
||||
// ErrNoUsernamePassword represents a "NoUsernamePassword" kind of error.
|
||||
@@ -86,8 +91,8 @@ func (err ErrNoUsernamePassword) Error() string {
|
||||
const ErrCodeNoUsernamePassword = 1004
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrNoUsernamePassword) HTTPError() HTTPError {
|
||||
return HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeNoUsernamePassword, Message: "Please specify a username and a password."}
|
||||
func (err ErrNoUsernamePassword) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeNoUsernamePassword, Message: "Please specify a username and a password."}
|
||||
}
|
||||
|
||||
// ErrUserDoesNotExist represents a "UserDoesNotExist" kind of error.
|
||||
@@ -109,8 +114,8 @@ func (err ErrUserDoesNotExist) Error() string {
|
||||
const ErrCodeUserDoesNotExist = 1005
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrUserDoesNotExist) HTTPError() HTTPError {
|
||||
return HTTPError{HTTPCode: http.StatusNotFound, Code: ErrCodeUserDoesNotExist, Message: "The user does not exist."}
|
||||
func (err ErrUserDoesNotExist) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusNotFound, Code: ErrCodeUserDoesNotExist, Message: "The user does not exist."}
|
||||
}
|
||||
|
||||
// ErrCouldNotGetUserID represents a "ErrCouldNotGetUserID" kind of error.
|
||||
@@ -130,8 +135,8 @@ func (err ErrCouldNotGetUserID) Error() string {
|
||||
const ErrCodeCouldNotGetUserID = 1006
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrCouldNotGetUserID) HTTPError() HTTPError {
|
||||
return HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeCouldNotGetUserID, Message: "Could not get user id."}
|
||||
func (err ErrCouldNotGetUserID) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeCouldNotGetUserID, Message: "Could not get user id."}
|
||||
}
|
||||
|
||||
// ErrNoPasswordResetToken represents an error where no password reset token exists for that user
|
||||
@@ -147,8 +152,8 @@ func (err ErrNoPasswordResetToken) Error() string {
|
||||
const ErrCodeNoPasswordResetToken = 1008
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrNoPasswordResetToken) HTTPError() HTTPError {
|
||||
return HTTPError{HTTPCode: http.StatusPreconditionFailed, Code: ErrCodeNoPasswordResetToken, Message: "No token to reset a user's password provided."}
|
||||
func (err ErrNoPasswordResetToken) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusPreconditionFailed, Code: ErrCodeNoPasswordResetToken, Message: "No token to reset a user's password provided."}
|
||||
}
|
||||
|
||||
// ErrInvalidPasswordResetToken is an error where the password reset token is invalid
|
||||
@@ -164,8 +169,8 @@ func (err ErrInvalidPasswordResetToken) Error() string {
|
||||
const ErrCodeInvalidPasswordResetToken = 1009
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrInvalidPasswordResetToken) HTTPError() HTTPError {
|
||||
return HTTPError{HTTPCode: http.StatusPreconditionFailed, Code: ErrCodeInvalidPasswordResetToken, Message: "Invalid token to reset a user's password."}
|
||||
func (err ErrInvalidPasswordResetToken) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusPreconditionFailed, Code: ErrCodeInvalidPasswordResetToken, Message: "Invalid token to reset a user's password."}
|
||||
}
|
||||
|
||||
// IsErrInvalidPasswordResetToken checks if an error is a ErrInvalidPasswordResetToken.
|
||||
@@ -187,8 +192,8 @@ func (err ErrInvalidEmailConfirmToken) Error() string {
|
||||
const ErrCodeInvalidEmailConfirmToken = 1010
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrInvalidEmailConfirmToken) HTTPError() HTTPError {
|
||||
return HTTPError{HTTPCode: http.StatusPreconditionFailed, Code: ErrCodeInvalidEmailConfirmToken, Message: "Invalid email confirm token."}
|
||||
func (err ErrInvalidEmailConfirmToken) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusPreconditionFailed, Code: ErrCodeInvalidEmailConfirmToken, Message: "Invalid email confirm token."}
|
||||
}
|
||||
|
||||
// IsErrInvalidEmailConfirmToken checks if an error is a ErrInvalidEmailConfirmToken.
|
||||
@@ -209,8 +214,8 @@ func (err ErrWrongUsernameOrPassword) Error() string {
|
||||
const ErrCodeWrongUsernameOrPassword = 1011
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrWrongUsernameOrPassword) HTTPError() HTTPError {
|
||||
return HTTPError{HTTPCode: http.StatusPreconditionFailed, Code: ErrCodeWrongUsernameOrPassword, Message: "Wrong username or password."}
|
||||
func (err ErrWrongUsernameOrPassword) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusPreconditionFailed, Code: ErrCodeWrongUsernameOrPassword, Message: "Wrong username or password."}
|
||||
}
|
||||
|
||||
// IsErrWrongUsernameOrPassword checks if an error is a IsErrEmailNotConfirmed.
|
||||
@@ -232,8 +237,8 @@ func (err ErrEmailNotConfirmed) Error() string {
|
||||
const ErrCodeEmailNotConfirmed = 1012
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrEmailNotConfirmed) HTTPError() HTTPError {
|
||||
return HTTPError{HTTPCode: http.StatusPreconditionFailed, Code: ErrCodeEmailNotConfirmed, Message: "Please confirm your email address."}
|
||||
func (err ErrEmailNotConfirmed) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusPreconditionFailed, Code: ErrCodeEmailNotConfirmed, Message: "Please confirm your email address."}
|
||||
}
|
||||
|
||||
// IsErrEmailNotConfirmed checks if an error is a IsErrEmailNotConfirmed.
|
||||
@@ -263,8 +268,8 @@ func (err ErrIDCannotBeZero) Error() string {
|
||||
const ErrCodeIDCannotBeZero = 2001
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrIDCannotBeZero) HTTPError() HTTPError {
|
||||
return HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeIDCannotBeZero, Message: "The ID cannot be empty or 0."}
|
||||
func (err ErrIDCannotBeZero) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeIDCannotBeZero, Message: "The ID cannot be empty or 0."}
|
||||
}
|
||||
|
||||
// ErrInvalidData represents a "ErrInvalidData" kind of error. Used when a struct is invalid -> validation failed.
|
||||
@@ -286,13 +291,13 @@ func (err ErrInvalidData) Error() string {
|
||||
const ErrCodeInvalidData = 2002
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrInvalidData) HTTPError() HTTPError {
|
||||
return HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeInvalidData, Message: err.Message}
|
||||
func (err ErrInvalidData) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeInvalidData, Message: err.Message}
|
||||
}
|
||||
|
||||
// ValidationHTTPError is the http error when a validation fails
|
||||
type ValidationHTTPError struct {
|
||||
HTTPError
|
||||
web.HTTPError
|
||||
InvalidFields []string `json:"invalid_fields"`
|
||||
}
|
||||
|
||||
@@ -327,8 +332,8 @@ func (err ErrListDoesNotExist) Error() string {
|
||||
const ErrCodeListDoesNotExist = 3001
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrListDoesNotExist) HTTPError() HTTPError {
|
||||
return HTTPError{HTTPCode: http.StatusNotFound, Code: ErrCodeListDoesNotExist, Message: "This list does not exist."}
|
||||
func (err ErrListDoesNotExist) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusNotFound, Code: ErrCodeListDoesNotExist, Message: "This list does not exist."}
|
||||
}
|
||||
|
||||
// ErrNeedToHaveListReadAccess represents an error, where the user dont has read access to that List
|
||||
@@ -351,8 +356,8 @@ func (err ErrNeedToHaveListReadAccess) Error() string {
|
||||
const ErrCodeNeedToHaveListReadAccess = 3004
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrNeedToHaveListReadAccess) HTTPError() HTTPError {
|
||||
return HTTPError{HTTPCode: http.StatusForbidden, Code: ErrCodeNeedToHaveListReadAccess, Message: "You need to have read access to this list."}
|
||||
func (err ErrNeedToHaveListReadAccess) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusForbidden, Code: ErrCodeNeedToHaveListReadAccess, Message: "You need to have read access to this list."}
|
||||
}
|
||||
|
||||
// ErrListTitleCannotBeEmpty represents a "ErrListTitleCannotBeEmpty" kind of error. Used if the list does not exist.
|
||||
@@ -372,8 +377,8 @@ func (err ErrListTitleCannotBeEmpty) Error() string {
|
||||
const ErrCodeListTitleCannotBeEmpty = 3005
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrListTitleCannotBeEmpty) HTTPError() HTTPError {
|
||||
return HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeListTitleCannotBeEmpty, Message: "You must provide at least a list title."}
|
||||
func (err ErrListTitleCannotBeEmpty) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeListTitleCannotBeEmpty, Message: "You must provide at least a list title."}
|
||||
}
|
||||
|
||||
// ================
|
||||
@@ -397,8 +402,8 @@ func (err ErrListTaskCannotBeEmpty) Error() string {
|
||||
const ErrCodeListTaskCannotBeEmpty = 4001
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrListTaskCannotBeEmpty) HTTPError() HTTPError {
|
||||
return HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeListTaskCannotBeEmpty, Message: "You must provide at least a list task text."}
|
||||
func (err ErrListTaskCannotBeEmpty) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeListTaskCannotBeEmpty, Message: "You must provide at least a list task text."}
|
||||
}
|
||||
|
||||
// ErrListTaskDoesNotExist represents a "ErrListDoesNotExist" kind of error. Used if the list does not exist.
|
||||
@@ -420,8 +425,81 @@ func (err ErrListTaskDoesNotExist) Error() string {
|
||||
const ErrCodeListTaskDoesNotExist = 4002
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrListTaskDoesNotExist) HTTPError() HTTPError {
|
||||
return HTTPError{HTTPCode: http.StatusNotFound, Code: ErrCodeListTaskDoesNotExist, Message: "This list task does not exist"}
|
||||
func (err ErrListTaskDoesNotExist) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusNotFound, Code: ErrCodeListTaskDoesNotExist, Message: "This list task does not exist"}
|
||||
}
|
||||
|
||||
// ErrBulkTasksMustBeInSameList represents a "ErrBulkTasksMustBeInSameList" kind of error.
|
||||
type ErrBulkTasksMustBeInSameList struct {
|
||||
ShouldBeID int64
|
||||
IsID int64
|
||||
}
|
||||
|
||||
// IsErrBulkTasksMustBeInSameList checks if an error is a ErrBulkTasksMustBeInSameList.
|
||||
func IsErrBulkTasksMustBeInSameList(err error) bool {
|
||||
_, ok := err.(ErrBulkTasksMustBeInSameList)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrBulkTasksMustBeInSameList) Error() string {
|
||||
return fmt.Sprintf("All bulk editing tasks must be in the same list. [Should be: %d, is: %d]", err.ShouldBeID, err.IsID)
|
||||
}
|
||||
|
||||
// ErrCodeBulkTasksMustBeInSameList holds the unique world-error code of this error
|
||||
const ErrCodeBulkTasksMustBeInSameList = 4003
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrBulkTasksMustBeInSameList) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeBulkTasksMustBeInSameList, Message: "All tasks must be in the same list."}
|
||||
}
|
||||
|
||||
// ErrBulkTasksNeedAtLeastOne represents a "ErrBulkTasksNeedAtLeastOne" kind of error.
|
||||
type ErrBulkTasksNeedAtLeastOne struct{}
|
||||
|
||||
// IsErrBulkTasksNeedAtLeastOne checks if an error is a ErrBulkTasksNeedAtLeastOne.
|
||||
func IsErrBulkTasksNeedAtLeastOne(err error) bool {
|
||||
_, ok := err.(ErrBulkTasksNeedAtLeastOne)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrBulkTasksNeedAtLeastOne) Error() string {
|
||||
return fmt.Sprintf("Need at least one task when bulk editing tasks")
|
||||
}
|
||||
|
||||
// ErrCodeBulkTasksNeedAtLeastOne holds the unique world-error code of this error
|
||||
const ErrCodeBulkTasksNeedAtLeastOne = 4004
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrBulkTasksNeedAtLeastOne) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeBulkTasksNeedAtLeastOne, Message: "Need at least one tasks to do bulk editing."}
|
||||
}
|
||||
|
||||
// ErrNoRightToSeeTask represents an error where a user does not have the right to see a task
|
||||
type ErrNoRightToSeeTask struct {
|
||||
TaskID int64
|
||||
UserID int64
|
||||
}
|
||||
|
||||
// IsErrNoRightToSeeTask checks if an error is ErrNoRightToSeeTask.
|
||||
func IsErrNoRightToSeeTask(err error) bool {
|
||||
_, ok := err.(ErrNoRightToSeeTask)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrNoRightToSeeTask) Error() string {
|
||||
return fmt.Sprintf("User does not have the right to see the task [TaskID: %v, UserID: %v]", err.TaskID, err.UserID)
|
||||
}
|
||||
|
||||
// ErrCodeNoRightToSeeTask holds the unique world-error code of this error
|
||||
const ErrCodeNoRightToSeeTask = 4005
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrNoRightToSeeTask) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{
|
||||
HTTPCode: http.StatusForbidden,
|
||||
Code: ErrCodeNoRightToSeeTask,
|
||||
Message: "You don't have the right to see this task.",
|
||||
}
|
||||
}
|
||||
|
||||
// =================
|
||||
@@ -447,8 +525,8 @@ func (err ErrNamespaceDoesNotExist) Error() string {
|
||||
const ErrCodeNamespaceDoesNotExist = 5001
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrNamespaceDoesNotExist) HTTPError() HTTPError {
|
||||
return HTTPError{HTTPCode: http.StatusNotFound, Code: ErrCodeNamespaceDoesNotExist, Message: "Namespace not found."}
|
||||
func (err ErrNamespaceDoesNotExist) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusNotFound, Code: ErrCodeNamespaceDoesNotExist, Message: "Namespace not found."}
|
||||
}
|
||||
|
||||
// ErrUserDoesNotHaveAccessToNamespace represents an error, where the user is not the owner of that namespace (used i.e. when deleting a namespace)
|
||||
@@ -471,8 +549,8 @@ func (err ErrUserDoesNotHaveAccessToNamespace) Error() string {
|
||||
const ErrCodeUserDoesNotHaveAccessToNamespace = 5003
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrUserDoesNotHaveAccessToNamespace) HTTPError() HTTPError {
|
||||
return HTTPError{HTTPCode: http.StatusForbidden, Code: ErrCodeUserDoesNotHaveAccessToNamespace, Message: "This user does not have access to the namespace."}
|
||||
func (err ErrUserDoesNotHaveAccessToNamespace) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusForbidden, Code: ErrCodeUserDoesNotHaveAccessToNamespace, Message: "This user does not have access to the namespace."}
|
||||
}
|
||||
|
||||
// ErrNamespaceNameCannotBeEmpty represents an error, where a namespace name is empty.
|
||||
@@ -495,8 +573,8 @@ func (err ErrNamespaceNameCannotBeEmpty) Error() string {
|
||||
const ErrCodeNamespaceNameCannotBeEmpty = 5006
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrNamespaceNameCannotBeEmpty) HTTPError() HTTPError {
|
||||
return HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeNamespaceNameCannotBeEmpty, Message: "The namespace name cannot be empty."}
|
||||
func (err ErrNamespaceNameCannotBeEmpty) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeNamespaceNameCannotBeEmpty, Message: "The namespace name cannot be empty."}
|
||||
}
|
||||
|
||||
// ErrNeedToHaveNamespaceReadAccess represents an error, where the user is not the owner of that namespace (used i.e. when deleting a namespace)
|
||||
@@ -519,8 +597,8 @@ func (err ErrNeedToHaveNamespaceReadAccess) Error() string {
|
||||
const ErrCodeNeedToHaveNamespaceReadAccess = 5009
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrNeedToHaveNamespaceReadAccess) HTTPError() HTTPError {
|
||||
return HTTPError{HTTPCode: http.StatusForbidden, Code: ErrCodeNeedToHaveNamespaceReadAccess, Message: "You need to have namespace read access to do this."}
|
||||
func (err ErrNeedToHaveNamespaceReadAccess) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusForbidden, Code: ErrCodeNeedToHaveNamespaceReadAccess, Message: "You need to have namespace read access to do this."}
|
||||
}
|
||||
|
||||
// ErrTeamDoesNotHaveAccessToNamespace represents an error, where the Team is not the owner of that namespace (used i.e. when deleting a namespace)
|
||||
@@ -543,8 +621,8 @@ func (err ErrTeamDoesNotHaveAccessToNamespace) Error() string {
|
||||
const ErrCodeTeamDoesNotHaveAccessToNamespace = 5010
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrTeamDoesNotHaveAccessToNamespace) HTTPError() HTTPError {
|
||||
return HTTPError{HTTPCode: http.StatusForbidden, Code: ErrCodeTeamDoesNotHaveAccessToNamespace, Message: "You need to have access to this namespace to do this."}
|
||||
func (err ErrTeamDoesNotHaveAccessToNamespace) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusForbidden, Code: ErrCodeTeamDoesNotHaveAccessToNamespace, Message: "You need to have access to this namespace to do this."}
|
||||
}
|
||||
|
||||
// ErrUserAlreadyHasNamespaceAccess represents an error where a user already has access to a namespace
|
||||
@@ -567,8 +645,8 @@ func (err ErrUserAlreadyHasNamespaceAccess) Error() string {
|
||||
const ErrCodeUserAlreadyHasNamespaceAccess = 5011
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrUserAlreadyHasNamespaceAccess) HTTPError() HTTPError {
|
||||
return HTTPError{HTTPCode: http.StatusConflict, Code: ErrCodeUserAlreadyHasNamespaceAccess, Message: "This user already has access to this namespace."}
|
||||
func (err ErrUserAlreadyHasNamespaceAccess) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusConflict, Code: ErrCodeUserAlreadyHasNamespaceAccess, Message: "This user already has access to this namespace."}
|
||||
}
|
||||
|
||||
// ============
|
||||
@@ -594,8 +672,8 @@ func (err ErrTeamNameCannotBeEmpty) Error() string {
|
||||
const ErrCodeTeamNameCannotBeEmpty = 6001
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrTeamNameCannotBeEmpty) HTTPError() HTTPError {
|
||||
return HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeTeamNameCannotBeEmpty, Message: "The team name cannot be empty"}
|
||||
func (err ErrTeamNameCannotBeEmpty) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeTeamNameCannotBeEmpty, Message: "The team name cannot be empty"}
|
||||
}
|
||||
|
||||
// ErrTeamDoesNotExist represents an error where a team does not exist
|
||||
@@ -617,31 +695,8 @@ func (err ErrTeamDoesNotExist) Error() string {
|
||||
const ErrCodeTeamDoesNotExist = 6002
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrTeamDoesNotExist) HTTPError() HTTPError {
|
||||
return HTTPError{HTTPCode: http.StatusNotFound, Code: ErrCodeTeamDoesNotExist, Message: "This team does not exist."}
|
||||
}
|
||||
|
||||
// ErrInvalidTeamRight represents an error where a team right is invalid
|
||||
type ErrInvalidTeamRight struct {
|
||||
Right TeamRight
|
||||
}
|
||||
|
||||
// IsErrInvalidTeamRight checks if an error is ErrInvalidTeamRight.
|
||||
func IsErrInvalidTeamRight(err error) bool {
|
||||
_, ok := err.(ErrInvalidTeamRight)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrInvalidTeamRight) Error() string {
|
||||
return fmt.Sprintf("Team right invalid [Right: %d]", err.Right)
|
||||
}
|
||||
|
||||
// ErrCodeInvalidTeamRight holds the unique world-error code of this error
|
||||
const ErrCodeInvalidTeamRight = 6003
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrInvalidTeamRight) HTTPError() HTTPError {
|
||||
return HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeInvalidTeamRight, Message: "The team right is invalid."}
|
||||
func (err ErrTeamDoesNotExist) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusNotFound, Code: ErrCodeTeamDoesNotExist, Message: "This team does not exist."}
|
||||
}
|
||||
|
||||
// ErrTeamAlreadyHasAccess represents an error where a team already has access to a list/namespace
|
||||
@@ -664,8 +719,8 @@ func (err ErrTeamAlreadyHasAccess) Error() string {
|
||||
const ErrCodeTeamAlreadyHasAccess = 6004
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrTeamAlreadyHasAccess) HTTPError() HTTPError {
|
||||
return HTTPError{HTTPCode: http.StatusConflict, Code: ErrCodeTeamAlreadyHasAccess, Message: "This team already has access."}
|
||||
func (err ErrTeamAlreadyHasAccess) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusConflict, Code: ErrCodeTeamAlreadyHasAccess, Message: "This team already has access."}
|
||||
}
|
||||
|
||||
// ErrUserIsMemberOfTeam represents an error where a user is already member of a team.
|
||||
@@ -688,8 +743,8 @@ func (err ErrUserIsMemberOfTeam) Error() string {
|
||||
const ErrCodeUserIsMemberOfTeam = 6005
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrUserIsMemberOfTeam) HTTPError() HTTPError {
|
||||
return HTTPError{HTTPCode: http.StatusConflict, Code: ErrCodeUserIsMemberOfTeam, Message: "This user is already a member of that team."}
|
||||
func (err ErrUserIsMemberOfTeam) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusConflict, Code: ErrCodeUserIsMemberOfTeam, Message: "This user is already a member of that team."}
|
||||
}
|
||||
|
||||
// ErrCannotDeleteLastTeamMember represents an error where a user wants to delete the last member of a team (probably himself)
|
||||
@@ -712,8 +767,8 @@ func (err ErrCannotDeleteLastTeamMember) Error() string {
|
||||
const ErrCodeCannotDeleteLastTeamMember = 6006
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrCannotDeleteLastTeamMember) HTTPError() HTTPError {
|
||||
return HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeCannotDeleteLastTeamMember, Message: "You cannot delete the last member of a team."}
|
||||
func (err ErrCannotDeleteLastTeamMember) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeCannotDeleteLastTeamMember, Message: "You cannot delete the last member of a team."}
|
||||
}
|
||||
|
||||
// ErrTeamDoesNotHaveAccessToList represents an error, where the Team is not the owner of that List (used i.e. when deleting a List)
|
||||
@@ -736,37 +791,14 @@ func (err ErrTeamDoesNotHaveAccessToList) Error() string {
|
||||
const ErrCodeTeamDoesNotHaveAccessToList = 6007
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrTeamDoesNotHaveAccessToList) HTTPError() HTTPError {
|
||||
return HTTPError{HTTPCode: http.StatusForbidden, Code: ErrCodeTeamDoesNotHaveAccessToList, Message: "This team does not have access to the list."}
|
||||
func (err ErrTeamDoesNotHaveAccessToList) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusForbidden, Code: ErrCodeTeamDoesNotHaveAccessToList, Message: "This team does not have access to the list."}
|
||||
}
|
||||
|
||||
// ====================
|
||||
// User <-> List errors
|
||||
// ====================
|
||||
|
||||
// ErrInvalidUserRight represents an error where a user right is invalid
|
||||
type ErrInvalidUserRight struct {
|
||||
Right UserRight
|
||||
}
|
||||
|
||||
// IsErrInvalidUserRight checks if an error is ErrInvalidUserRight.
|
||||
func IsErrInvalidUserRight(err error) bool {
|
||||
_, ok := err.(ErrInvalidUserRight)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrInvalidUserRight) Error() string {
|
||||
return fmt.Sprintf("User right is invalid [Right: %d]", err.Right)
|
||||
}
|
||||
|
||||
// ErrCodeInvalidUserRight holds the unique world-error code of this error
|
||||
const ErrCodeInvalidUserRight = 7001
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrInvalidUserRight) HTTPError() HTTPError {
|
||||
return HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeInvalidUserRight, Message: "The user right is invalid."}
|
||||
}
|
||||
|
||||
// ErrUserAlreadyHasAccess represents an error where a user already has access to a list/namespace
|
||||
type ErrUserAlreadyHasAccess struct {
|
||||
UserID int64
|
||||
@@ -787,8 +819,8 @@ func (err ErrUserAlreadyHasAccess) Error() string {
|
||||
const ErrCodeUserAlreadyHasAccess = 7002
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrUserAlreadyHasAccess) HTTPError() HTTPError {
|
||||
return HTTPError{HTTPCode: http.StatusConflict, Code: ErrCodeUserAlreadyHasAccess, Message: "This user already has access to this list."}
|
||||
func (err ErrUserAlreadyHasAccess) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusConflict, Code: ErrCodeUserAlreadyHasAccess, Message: "This user already has access to this list."}
|
||||
}
|
||||
|
||||
// ErrUserDoesNotHaveAccessToList represents an error, where the user is not the owner of that List (used i.e. when deleting a List)
|
||||
@@ -811,6 +843,124 @@ func (err ErrUserDoesNotHaveAccessToList) Error() string {
|
||||
const ErrCodeUserDoesNotHaveAccessToList = 7003
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrUserDoesNotHaveAccessToList) HTTPError() HTTPError {
|
||||
return HTTPError{HTTPCode: http.StatusForbidden, Code: ErrCodeUserDoesNotHaveAccessToList, Message: "This user does not have access to the list."}
|
||||
func (err ErrUserDoesNotHaveAccessToList) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusForbidden, Code: ErrCodeUserDoesNotHaveAccessToList, Message: "This user does not have access to the list."}
|
||||
}
|
||||
|
||||
// =============
|
||||
// Label errors
|
||||
// =============
|
||||
|
||||
// ErrLabelIsAlreadyOnTask represents an error where a label is already bound to a task
|
||||
type ErrLabelIsAlreadyOnTask struct {
|
||||
LabelID int64
|
||||
TaskID int64
|
||||
}
|
||||
|
||||
// IsErrLabelIsAlreadyOnTask checks if an error is ErrLabelIsAlreadyOnTask.
|
||||
func IsErrLabelIsAlreadyOnTask(err error) bool {
|
||||
_, ok := err.(ErrLabelIsAlreadyOnTask)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrLabelIsAlreadyOnTask) Error() string {
|
||||
return fmt.Sprintf("Label already exists on task [TaskID: %v, LabelID: %v]", err.TaskID, err.LabelID)
|
||||
}
|
||||
|
||||
// ErrCodeLabelIsAlreadyOnTask holds the unique world-error code of this error
|
||||
const ErrCodeLabelIsAlreadyOnTask = 8001
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrLabelIsAlreadyOnTask) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{
|
||||
HTTPCode: http.StatusBadRequest,
|
||||
Code: ErrCodeLabelIsAlreadyOnTask,
|
||||
Message: "This label already exists on the task.",
|
||||
}
|
||||
}
|
||||
|
||||
// ErrLabelDoesNotExist represents an error where a label does not exist
|
||||
type ErrLabelDoesNotExist struct {
|
||||
LabelID int64
|
||||
}
|
||||
|
||||
// IsErrLabelDoesNotExist checks if an error is ErrLabelDoesNotExist.
|
||||
func IsErrLabelDoesNotExist(err error) bool {
|
||||
_, ok := err.(ErrLabelDoesNotExist)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrLabelDoesNotExist) Error() string {
|
||||
return fmt.Sprintf("Label does not exist [LabelID: %v]", err.LabelID)
|
||||
}
|
||||
|
||||
// ErrCodeLabelDoesNotExist holds the unique world-error code of this error
|
||||
const ErrCodeLabelDoesNotExist = 8002
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrLabelDoesNotExist) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{
|
||||
HTTPCode: http.StatusNotFound,
|
||||
Code: ErrCodeLabelDoesNotExist,
|
||||
Message: "This label does not exist.",
|
||||
}
|
||||
}
|
||||
|
||||
// ErrUserHasNoAccessToLabel represents an error where a user does not have the right to see a label
|
||||
type ErrUserHasNoAccessToLabel struct {
|
||||
LabelID int64
|
||||
UserID int64
|
||||
}
|
||||
|
||||
// IsErrUserHasNoAccessToLabel checks if an error is ErrUserHasNoAccessToLabel.
|
||||
func IsErrUserHasNoAccessToLabel(err error) bool {
|
||||
_, ok := err.(ErrUserHasNoAccessToLabel)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrUserHasNoAccessToLabel) Error() string {
|
||||
return fmt.Sprintf("The user does not have access to this label [LabelID: %v, UserID: %v]", err.LabelID, err.UserID)
|
||||
}
|
||||
|
||||
// ErrCodeUserHasNoAccessToLabel holds the unique world-error code of this error
|
||||
const ErrCodeUserHasNoAccessToLabel = 8003
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrUserHasNoAccessToLabel) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{
|
||||
HTTPCode: http.StatusForbidden,
|
||||
Code: ErrCodeUserHasNoAccessToLabel,
|
||||
Message: "You don't have access to this label.",
|
||||
}
|
||||
}
|
||||
|
||||
// ========
|
||||
// Rights
|
||||
// ========
|
||||
|
||||
// ErrInvalidRight represents an error where a right is invalid
|
||||
type ErrInvalidRight struct {
|
||||
Right Right
|
||||
}
|
||||
|
||||
// IsErrInvalidRight checks if an error is ErrInvalidRight.
|
||||
func IsErrInvalidRight(err error) bool {
|
||||
_, ok := err.(ErrInvalidRight)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrInvalidRight) Error() string {
|
||||
return fmt.Sprintf(" right invalid [Right: %d]", err.Right)
|
||||
}
|
||||
|
||||
// ErrCodeInvalidRight holds the unique world-error code of this error
|
||||
const ErrCodeInvalidRight = 9001
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrInvalidRight) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{
|
||||
HTTPCode: http.StatusBadRequest,
|
||||
Code: ErrCodeInvalidRight,
|
||||
Message: "The right is invalid.",
|
||||
}
|
||||
}
|
||||
|
||||
4
pkg/models/fixtures/label_task.yml
Normal file
4
pkg/models/fixtures/label_task.yml
Normal file
@@ -0,0 +1,4 @@
|
||||
- id: 1
|
||||
task_id: 1
|
||||
label_id: 4
|
||||
created: 0
|
||||
20
pkg/models/fixtures/labels.yml
Normal file
20
pkg/models/fixtures/labels.yml
Normal file
@@ -0,0 +1,20 @@
|
||||
- id: 1
|
||||
title: 'Label #1'
|
||||
created_by_id: 1
|
||||
updated: 0
|
||||
created: 0
|
||||
- id: 2
|
||||
title: 'Label #2'
|
||||
created_by_id: 1
|
||||
updated: 0
|
||||
created: 0
|
||||
- id: 3
|
||||
title: 'Label #3 - other user'
|
||||
created_by_id: 2
|
||||
updated: 0
|
||||
created: 0
|
||||
- id: 4
|
||||
title: 'Label #4 - visible via other task'
|
||||
created_by_id: 2
|
||||
updated: 0
|
||||
created: 0
|
||||
@@ -4,15 +4,37 @@
|
||||
description: Lorem Ipsum
|
||||
owner_id: 1
|
||||
namespace_id: 1
|
||||
updated: 0
|
||||
created: 0
|
||||
-
|
||||
id: 2
|
||||
title: Test2
|
||||
description: Lorem Ipsum
|
||||
owner_id: 3
|
||||
namespace_id: 1
|
||||
updated: 0
|
||||
created: 0
|
||||
-
|
||||
id: 3
|
||||
title: Test3
|
||||
description: Lorem Ipsum
|
||||
owner_id: 3
|
||||
namespace_id: 2
|
||||
namespace_id: 2
|
||||
updated: 0
|
||||
created: 0
|
||||
-
|
||||
id: 4
|
||||
title: Test4
|
||||
description: Lorem Ipsum
|
||||
owner_id: 3
|
||||
namespace_id: 3
|
||||
updated: 0
|
||||
created: 0
|
||||
-
|
||||
id: 5
|
||||
title: Test5
|
||||
description: Lorem Ipsum
|
||||
owner_id: 5
|
||||
namespace_id: 5
|
||||
updated: 0
|
||||
created: 0
|
||||
|
||||
@@ -3,8 +3,19 @@
|
||||
name: testnamespace
|
||||
description: Lorem Ipsum
|
||||
owner_id: 1
|
||||
updated: 0
|
||||
created: 0
|
||||
-
|
||||
id: 2
|
||||
name: testnamespace2
|
||||
description: Lorem Ipsum
|
||||
owner_id: 2
|
||||
owner_id: 2
|
||||
updated: 0
|
||||
created: 0
|
||||
-
|
||||
id: 3
|
||||
name: testnamespace3
|
||||
description: Lorem Ipsum
|
||||
owner_id: 3
|
||||
updated: 0
|
||||
created: 0
|
||||
|
||||
93
pkg/models/fixtures/tasks.yml
Normal file
93
pkg/models/fixtures/tasks.yml
Normal file
@@ -0,0 +1,93 @@
|
||||
- id: 1
|
||||
text: 'task #1'
|
||||
created_by_id: 1
|
||||
list_id: 1
|
||||
created: 1543626724
|
||||
updated: 1543626724
|
||||
- id: 2
|
||||
text: 'task #2 done'
|
||||
done: true
|
||||
created_by_id: 1
|
||||
list_id: 1
|
||||
created: 1543626724
|
||||
updated: 1543626724
|
||||
- id: 3
|
||||
text: 'task #3 high prio'
|
||||
created_by_id: 1
|
||||
list_id: 1
|
||||
created: 1543626724
|
||||
updated: 1543626724
|
||||
priority: 100
|
||||
- id: 4
|
||||
text: 'task #4 low prio'
|
||||
created_by_id: 1
|
||||
list_id: 1
|
||||
created: 1543626724
|
||||
updated: 1543626724
|
||||
priority: 1
|
||||
- id: 5
|
||||
text: 'task #5 higher due date'
|
||||
created_by_id: 1
|
||||
list_id: 1
|
||||
created: 1543626724
|
||||
updated: 1543626724
|
||||
due_date_unix: 1543636724
|
||||
- id: 6
|
||||
text: 'task #6 lower due date'
|
||||
created_by_id: 1
|
||||
list_id: 1
|
||||
created: 1543626724
|
||||
updated: 1543626724
|
||||
due_date_unix: 1543616724
|
||||
- id: 7
|
||||
text: 'task #7 with start date'
|
||||
created_by_id: 1
|
||||
list_id: 1
|
||||
created: 1543626724
|
||||
updated: 1543626724
|
||||
start_date_unix: 1544600000
|
||||
- id: 8
|
||||
text: 'task #8 with end date'
|
||||
created_by_id: 1
|
||||
list_id: 1
|
||||
created: 1543626724
|
||||
updated: 1543626724
|
||||
end_date_unix: 1544700000
|
||||
- id: 9
|
||||
text: 'task #9 with start and end date'
|
||||
created_by_id: 1
|
||||
list_id: 1
|
||||
created: 1543626724
|
||||
updated: 1543626724
|
||||
start_date_unix: 1544600000
|
||||
end_date_unix: 1544700000
|
||||
- id: 10
|
||||
text: 'task #10 basic'
|
||||
created_by_id: 1
|
||||
list_id: 1
|
||||
created: 1543626724
|
||||
updated: 1543626724
|
||||
- id: 11
|
||||
text: 'task #11 basic'
|
||||
created_by_id: 1
|
||||
list_id: 1
|
||||
created: 1543626724
|
||||
updated: 1543626724
|
||||
- id: 12
|
||||
text: 'task #12 basic'
|
||||
created_by_id: 1
|
||||
list_id: 1
|
||||
created: 1543626724
|
||||
updated: 1543626724
|
||||
- id: 13
|
||||
text: 'task #13 basic other list'
|
||||
created_by_id: 1
|
||||
list_id: 2
|
||||
created: 1543626724
|
||||
updated: 1543626724
|
||||
- id: 14
|
||||
text: 'task #14 basic other list'
|
||||
created_by_id: 5
|
||||
list_id: 5
|
||||
created: 1543626724
|
||||
updated: 1543626724
|
||||
10
pkg/models/fixtures/team_list.yml
Normal file
10
pkg/models/fixtures/team_list.yml
Normal file
@@ -0,0 +1,10 @@
|
||||
- id: 1
|
||||
team_id: 1
|
||||
list_id: 3
|
||||
updated: 0
|
||||
created: 0
|
||||
- id: 2
|
||||
team_id: 2
|
||||
list_id: 3
|
||||
updated: 0
|
||||
created: 0
|
||||
@@ -2,6 +2,8 @@
|
||||
team_id: 1
|
||||
user_id: 1
|
||||
admin: true
|
||||
created: 0
|
||||
-
|
||||
team_id: 1
|
||||
user_id: 2
|
||||
created: 0
|
||||
|
||||
10
pkg/models/fixtures/team_namespaces.yml
Normal file
10
pkg/models/fixtures/team_namespaces.yml
Normal file
@@ -0,0 +1,10 @@
|
||||
- id: 1
|
||||
team_id: 1
|
||||
namespace_id: 3
|
||||
updated: 0
|
||||
created: 0
|
||||
- id: 2
|
||||
team_id: 2
|
||||
namespace_id: 3
|
||||
updated: 0
|
||||
created: 0
|
||||
@@ -2,14 +2,37 @@
|
||||
id: 1
|
||||
username: 'user1'
|
||||
password: '1234'
|
||||
email: 'johndoe@example.com'
|
||||
email: 'user1@example.com'
|
||||
updated: 0
|
||||
created: 0
|
||||
-
|
||||
id: 2
|
||||
username: 'user2'
|
||||
password: '1234'
|
||||
email: 'johndoe@example.com'
|
||||
email: 'user2@example.com'
|
||||
updated: 0
|
||||
created: 0
|
||||
-
|
||||
id: 3
|
||||
username: 'user3'
|
||||
password: '1234'
|
||||
email: 'johndoe@example.com'
|
||||
email: 'user3@example.com'
|
||||
updated: 0
|
||||
created: 0
|
||||
-
|
||||
id: 4
|
||||
username: 'user4'
|
||||
password: '1234'
|
||||
email: 'user4@example.com'
|
||||
email_confirm_token: tiepiQueed8ahc7zeeFe1eveiy4Ein8osooxegiephauph2Ael
|
||||
updated: 0
|
||||
created: 0
|
||||
-
|
||||
id: 5
|
||||
username: 'user5'
|
||||
password: '1234'
|
||||
email: 'user4@example.com'
|
||||
email_confirm_token: tiepiQueed8ahc7zeeFe1eveiy4Ein8osooxegiephauph2Ael
|
||||
is_active: false
|
||||
updated: 0
|
||||
created: 0
|
||||
|
||||
10
pkg/models/fixtures/users_list.yml
Normal file
10
pkg/models/fixtures/users_list.yml
Normal file
@@ -0,0 +1,10 @@
|
||||
- id: 1
|
||||
user_id: 1
|
||||
list_id: 3
|
||||
updated: 0
|
||||
created: 0
|
||||
- id: 2
|
||||
user_id: 2
|
||||
list_id: 3
|
||||
updated: 0
|
||||
created: 0
|
||||
10
pkg/models/fixtures/users_namespace.yml
Normal file
10
pkg/models/fixtures/users_namespace.yml
Normal file
@@ -0,0 +1,10 @@
|
||||
- id: 1
|
||||
user_id: 1
|
||||
namespace_id: 3
|
||||
updated: 0
|
||||
created: 0
|
||||
- id: 2
|
||||
user_id: 2
|
||||
namespace_id: 3
|
||||
updated: 0
|
||||
created: 0
|
||||
50
pkg/models/label.go
Normal file
50
pkg/models/label.go
Normal file
@@ -0,0 +1,50 @@
|
||||
// Vikunja is a todo-list application to facilitate your life.
|
||||
// Copyright 2018 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 models
|
||||
|
||||
import (
|
||||
"code.vikunja.io/web"
|
||||
)
|
||||
|
||||
// Label represents a label
|
||||
type Label struct {
|
||||
// The unique, numeric id of this label.
|
||||
ID int64 `xorm:"int(11) autoincr not null unique pk" json:"id" param:"label"`
|
||||
// The title of the lable. You'll see this one on tasks associated with it.
|
||||
Title string `xorm:"varchar(250) not null" json:"title" valid:"runelength(3|250)" minLength:"3" maxLength:"250"`
|
||||
// The label description.
|
||||
Description string `xorm:"varchar(250) null" json:"description" valid:"runelength(0|250)" maxLength:"250"`
|
||||
// The color this label has
|
||||
HexColor string `xorm:"varchar(6) null" json:"hex_color" valid:"runelength(0|6)" maxLength:"6"`
|
||||
|
||||
CreatedByID int64 `xorm:"int(11) not null" json:"-"`
|
||||
// The user who created this label
|
||||
CreatedBy *User `xorm:"-" json:"created_by"`
|
||||
|
||||
// A unix timestamp when this label was created. You cannot change this value.
|
||||
Created int64 `xorm:"created not null" json:"created"`
|
||||
// A unix timestamp when this label was last updated. You cannot change this value.
|
||||
Updated int64 `xorm:"updated not null" json:"updated"`
|
||||
|
||||
web.CRUDable `xorm:"-" json:"-"`
|
||||
web.Rights `xorm:"-" json:"-"`
|
||||
}
|
||||
|
||||
// TableName makes a pretty table name
|
||||
func (Label) TableName() string {
|
||||
return "labels"
|
||||
}
|
||||
87
pkg/models/label_create_update.go
Normal file
87
pkg/models/label_create_update.go
Normal file
@@ -0,0 +1,87 @@
|
||||
// Vikunja is a todo-list application to facilitate your life.
|
||||
// Copyright 2018 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 models
|
||||
|
||||
import "code.vikunja.io/web"
|
||||
|
||||
// Create creates a new label
|
||||
// @Summary Create a label
|
||||
// @Description Creates a new label.
|
||||
// @tags labels
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security JWTKeyAuth
|
||||
// @Param label body models.Label true "The label object"
|
||||
// @Success 200 {object} models.Label "The created label object."
|
||||
// @Failure 400 {object} code.vikunja.io/web.HTTPError "Invalid label object provided."
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /labels [put]
|
||||
func (l *Label) Create(a web.Auth) (err error) {
|
||||
u, err := getUserWithError(a)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
l.CreatedBy = u
|
||||
l.CreatedByID = u.ID
|
||||
|
||||
_, err = x.Insert(l)
|
||||
return
|
||||
}
|
||||
|
||||
// Update updates a label
|
||||
// @Summary Update a label
|
||||
// @Description Update an existing label. The user needs to be the creator of the label to be able to do this.
|
||||
// @tags labels
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security JWTKeyAuth
|
||||
// @Param id path int true "Label ID"
|
||||
// @Param label body models.Label true "The label object"
|
||||
// @Success 200 {object} models.Label "The created label object."
|
||||
// @Failure 400 {object} code.vikunja.io/web.HTTPError "Invalid label object provided."
|
||||
// @Failure 403 {object} code.vikunja.io/web.HTTPError "Not allowed to update the label."
|
||||
// @Failure 404 {object} code.vikunja.io/web.HTTPError "Label not found."
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /labels/{id} [put]
|
||||
func (l *Label) Update() (err error) {
|
||||
_, err = x.ID(l.ID).Update(l)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = l.ReadOne()
|
||||
return
|
||||
}
|
||||
|
||||
// Delete deletes a label
|
||||
// @Summary Delete a label
|
||||
// @Description Delete an existing label. The user needs to be the creator of the label to be able to do this.
|
||||
// @tags labels
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security JWTKeyAuth
|
||||
// @Param id path int true "Label ID"
|
||||
// @Success 200 {object} models.Label "The label was successfully deleted."
|
||||
// @Failure 403 {object} code.vikunja.io/web.HTTPError "Not allowed to delete the label."
|
||||
// @Failure 404 {object} code.vikunja.io/web.HTTPError "Label not found."
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /labels/{id} [delete]
|
||||
func (l *Label) Delete() (err error) {
|
||||
_, err = x.ID(l.ID).Delete(&Label{})
|
||||
return err
|
||||
}
|
||||
111
pkg/models/label_read.go
Normal file
111
pkg/models/label_read.go
Normal file
@@ -0,0 +1,111 @@
|
||||
// Vikunja is a todo-list application to facilitate your life.
|
||||
// Copyright 2018 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 models
|
||||
|
||||
import (
|
||||
"code.vikunja.io/web"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ReadAll gets all labels a user can use
|
||||
// @Summary Get all labels a user has access to
|
||||
// @Description Returns all labels which are either created by the user or associated with a task the user has at least read-access to.
|
||||
// @tags labels
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param p query int false "The page number. Used for pagination. If not provided, the first page of results is returned."
|
||||
// @Param s query string false "Search labels by label text."
|
||||
// @Security JWTKeyAuth
|
||||
// @Success 200 {array} models.Label "The labels"
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /labels [get]
|
||||
func (l *Label) ReadAll(search string, a web.Auth, page int) (ls interface{}, err error) {
|
||||
u, err := getUserWithError(a)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get all tasks
|
||||
taskIDs, err := getUserTaskIDs(u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return getLabelsByTaskIDs(&LabelByTaskIDsOptions{
|
||||
Search: search,
|
||||
User: u,
|
||||
TaskIDs: taskIDs,
|
||||
GetUnusedLabels: true,
|
||||
})
|
||||
}
|
||||
|
||||
// ReadOne gets one label
|
||||
// @Summary Gets one label
|
||||
// @Description Returns one label by its ID.
|
||||
// @tags labels
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "Label ID"
|
||||
// @Security JWTKeyAuth
|
||||
// @Success 200 {object} models.Label "The label"
|
||||
// @Failure 403 {object} code.vikunja.io/web.HTTPError "The user does not have access to the label"
|
||||
// @Failure 404 {object} code.vikunja.io/web.HTTPError "Label not found"
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /labels/{id} [get]
|
||||
func (l *Label) ReadOne() (err error) {
|
||||
label, err := getLabelByIDSimple(l.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*l = *label
|
||||
|
||||
user, err := GetUserByID(l.CreatedByID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
l.CreatedBy = &user
|
||||
return
|
||||
}
|
||||
|
||||
func getLabelByIDSimple(labelID int64) (*Label, error) {
|
||||
label := Label{}
|
||||
exists, err := x.ID(labelID).Get(&label)
|
||||
if err != nil {
|
||||
return &label, err
|
||||
}
|
||||
|
||||
if !exists {
|
||||
return &Label{}, ErrLabelDoesNotExist{labelID}
|
||||
}
|
||||
return &label, err
|
||||
}
|
||||
|
||||
// Helper method to get all task ids a user has
|
||||
func getUserTaskIDs(u *User) (taskIDs []int64, err error) {
|
||||
tasks, err := GetTasksByUser("", u, -1, SortTasksByUnsorted, time.Unix(0, 0), time.Unix(0, 0))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// make a slice of task ids
|
||||
for _, t := range tasks {
|
||||
taskIDs = append(taskIDs, t.ID)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
74
pkg/models/label_rights.go
Normal file
74
pkg/models/label_rights.go
Normal file
@@ -0,0 +1,74 @@
|
||||
// Vikunja is a todo-list application to facilitate your life.
|
||||
// Copyright 2018 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 models
|
||||
|
||||
import (
|
||||
"code.vikunja.io/web"
|
||||
"github.com/go-xorm/builder"
|
||||
)
|
||||
|
||||
// CanUpdate checks if a user can update a label
|
||||
func (l *Label) CanUpdate(a web.Auth) (bool, error) {
|
||||
return l.isLabelOwner(a) // Only owners should be allowed to update a label
|
||||
}
|
||||
|
||||
// CanDelete checks if a user can delete a label
|
||||
func (l *Label) CanDelete(a web.Auth) (bool, error) {
|
||||
return l.isLabelOwner(a) // Only owners should be allowed to delete a label
|
||||
}
|
||||
|
||||
// CanRead checks if a user can read a label
|
||||
func (l *Label) CanRead(a web.Auth) (bool, error) {
|
||||
return l.hasAccessToLabel(a)
|
||||
}
|
||||
|
||||
// CanCreate checks if the user can create a label
|
||||
// Currently a dummy.
|
||||
func (l *Label) CanCreate(a web.Auth) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (l *Label) isLabelOwner(a web.Auth) (bool, error) {
|
||||
u := getUserForRights(a)
|
||||
lorig, err := getLabelByIDSimple(l.ID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return lorig.CreatedByID == u.ID, nil
|
||||
}
|
||||
|
||||
// Helper method to check if a user can see a specific label
|
||||
func (l *Label) hasAccessToLabel(a web.Auth) (bool, error) {
|
||||
u := getUserForRights(a)
|
||||
|
||||
// Get all tasks
|
||||
taskIDs, err := getUserTaskIDs(u)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Get all labels associated with these tasks
|
||||
var labels []*Label
|
||||
has, err := x.Table("labels").
|
||||
Select("labels.*").
|
||||
Join("LEFT", "label_task", "label_task.label_id = labels.id").
|
||||
Where("label_task.label_id != null OR labels.created_by_id = ?", u.ID).
|
||||
Or(builder.In("label_task.task_id", taskIDs)).
|
||||
And("labels.id = ?", l.ID).
|
||||
Exist(&labels)
|
||||
return has, err
|
||||
}
|
||||
307
pkg/models/label_task.go
Normal file
307
pkg/models/label_task.go
Normal file
@@ -0,0 +1,307 @@
|
||||
// Vikunja is a todo-list application to facilitate your life.
|
||||
// Copyright 2018 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 models
|
||||
|
||||
import (
|
||||
"code.vikunja.io/web"
|
||||
"github.com/go-xorm/builder"
|
||||
)
|
||||
|
||||
// LabelTask represents a relation between a label and a task
|
||||
type LabelTask struct {
|
||||
// The unique, numeric id of this label.
|
||||
ID int64 `xorm:"int(11) autoincr not null unique pk" json:"-"`
|
||||
TaskID int64 `xorm:"int(11) INDEX not null" json:"-" param:"listtask"`
|
||||
// The label id you want to associate with a task.
|
||||
LabelID int64 `xorm:"int(11) INDEX not null" json:"label_id" param:"label"`
|
||||
// A unix timestamp when this task was created. You cannot change this value.
|
||||
Created int64 `xorm:"created not null" json:"created"`
|
||||
|
||||
web.CRUDable `xorm:"-" json:"-"`
|
||||
web.Rights `xorm:"-" json:"-"`
|
||||
}
|
||||
|
||||
// TableName makes a pretty table name
|
||||
func (LabelTask) TableName() string {
|
||||
return "label_task"
|
||||
}
|
||||
|
||||
// Delete deletes a label on a task
|
||||
// @Summary Remove a label from a task
|
||||
// @Description Remove a label from a task. The user needs to have write-access to the list to be able do this.
|
||||
// @tags labels
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security JWTKeyAuth
|
||||
// @Param task path int true "Task ID"
|
||||
// @Param label path int true "Label ID"
|
||||
// @Success 200 {object} models.Message "The label was successfully removed."
|
||||
// @Failure 403 {object} code.vikunja.io/web.HTTPError "Not allowed to remove the label."
|
||||
// @Failure 404 {object} code.vikunja.io/web.HTTPError "Label not found."
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /tasks/{task}/labels/{label} [delete]
|
||||
func (lt *LabelTask) Delete() (err error) {
|
||||
_, err = x.Delete(&LabelTask{LabelID: lt.LabelID, TaskID: lt.TaskID})
|
||||
return err
|
||||
}
|
||||
|
||||
// Create adds a label to a task
|
||||
// @Summary Add a label to a task
|
||||
// @Description Add a label to a task. The user needs to have write-access to the list to be able do this.
|
||||
// @tags labels
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security JWTKeyAuth
|
||||
// @Param task path int true "Task ID"
|
||||
// @Param label body models.LabelTask true "The label object"
|
||||
// @Success 200 {object} models.LabelTask "The created label relation object."
|
||||
// @Failure 400 {object} code.vikunja.io/web.HTTPError "Invalid label object provided."
|
||||
// @Failure 403 {object} code.vikunja.io/web.HTTPError "Not allowed to add the label."
|
||||
// @Failure 404 {object} code.vikunja.io/web.HTTPError "The label does not exist."
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /tasks/{task}/labels [put]
|
||||
func (lt *LabelTask) Create(a web.Auth) (err error) {
|
||||
// Check if the label is already added
|
||||
exists, err := x.Exist(&LabelTask{LabelID: lt.LabelID, TaskID: lt.TaskID})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exists {
|
||||
return ErrLabelIsAlreadyOnTask{lt.LabelID, lt.TaskID}
|
||||
}
|
||||
|
||||
// Insert it
|
||||
_, err = x.Insert(lt)
|
||||
return
|
||||
}
|
||||
|
||||
// ReadAll gets all labels on a task
|
||||
// @Summary Get all labels on a task
|
||||
// @Description Returns all labels which are assicociated with a given task.
|
||||
// @tags labels
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param task path int true "Task ID"
|
||||
// @Param p query int false "The page number. Used for pagination. If not provided, the first page of results is returned."
|
||||
// @Param s query string false "Search labels by label text."
|
||||
// @Security JWTKeyAuth
|
||||
// @Success 200 {array} models.Label "The labels"
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /tasks/{task}/labels [get]
|
||||
func (lt *LabelTask) ReadAll(search string, a web.Auth, page int) (labels interface{}, err error) {
|
||||
u, err := getUserWithError(a)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Check if the user has the right to see the task
|
||||
task := ListTask{ID: lt.TaskID}
|
||||
canRead, err := task.CanRead(a)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !canRead {
|
||||
return nil, ErrNoRightToSeeTask{lt.TaskID, u.ID}
|
||||
}
|
||||
|
||||
return getLabelsByTaskIDs(&LabelByTaskIDsOptions{
|
||||
User: u,
|
||||
Search: search,
|
||||
Page: page,
|
||||
TaskIDs: []int64{lt.TaskID},
|
||||
})
|
||||
}
|
||||
|
||||
// Helper struct, contains the label + its task ID
|
||||
type labelWithTaskID struct {
|
||||
TaskID int64
|
||||
Label `xorm:"extends"`
|
||||
}
|
||||
|
||||
// LabelByTaskIDsOptions is a struct to not clutter the function with too many optional parameters.
|
||||
type LabelByTaskIDsOptions struct {
|
||||
User *User
|
||||
Search string
|
||||
Page int
|
||||
TaskIDs []int64
|
||||
GetUnusedLabels bool
|
||||
}
|
||||
|
||||
// Helper function to get all labels for a set of tasks
|
||||
// Used when getting all labels for one task as well when getting all lables
|
||||
func getLabelsByTaskIDs(opts *LabelByTaskIDsOptions) (ls []*labelWithTaskID, err error) {
|
||||
// Include unused labels. Needed to be able to show a list of all unused labels a user
|
||||
// has access to.
|
||||
var uidOrNil interface{}
|
||||
var requestOrNil interface{}
|
||||
if opts.GetUnusedLabels {
|
||||
uidOrNil = opts.User.ID
|
||||
requestOrNil = "label_task.label_id != null OR labels.created_by_id = ?"
|
||||
}
|
||||
|
||||
// Get all labels associated with these tasks
|
||||
var labels []*labelWithTaskID
|
||||
err = x.Table("labels").
|
||||
Select("labels.*, label_task.task_id").
|
||||
Join("LEFT", "label_task", "label_task.label_id = labels.id").
|
||||
Where(requestOrNil, uidOrNil).
|
||||
Or(builder.In("label_task.task_id", opts.TaskIDs)).
|
||||
And("labels.title LIKE ?", "%"+opts.Search+"%").
|
||||
GroupBy("labels.id,label_task.task_id"). // This filters out doubles
|
||||
Limit(getLimitFromPageIndex(opts.Page)).
|
||||
Find(&labels)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get all created by users
|
||||
var userids []int64
|
||||
for _, l := range labels {
|
||||
userids = append(userids, l.CreatedByID)
|
||||
}
|
||||
users := make(map[int64]*User)
|
||||
err = x.In("id", userids).Find(&users)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Put it all together
|
||||
for in, l := range labels {
|
||||
labels[in].CreatedBy = users[l.CreatedByID]
|
||||
}
|
||||
|
||||
return labels, err
|
||||
}
|
||||
|
||||
// Create or update a bunch of task labels
|
||||
func (t *ListTask) updateTaskLabels(creator web.Auth, labels []*Label) (err error) {
|
||||
|
||||
// If we don't have any new labels, delete everything right away. Saves us some hassle.
|
||||
if len(labels) == 0 && len(t.Labels) > 0 {
|
||||
_, err = x.Where("task_id = ?", t.ID).
|
||||
Delete(LabelTask{})
|
||||
return err
|
||||
}
|
||||
|
||||
// If we didn't change anything (from 0 to zero) don't do anything.
|
||||
if len(labels) == 0 && len(t.Labels) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Make a hashmap of the new labels for easier comparison
|
||||
newLabels := make(map[int64]*Label, len(labels))
|
||||
for _, newLabel := range labels {
|
||||
newLabels[newLabel.ID] = newLabel
|
||||
}
|
||||
|
||||
// Get old labels to delete
|
||||
var found bool
|
||||
var labelsToDelete []int64
|
||||
oldLabels := make(map[int64]*Label, len(t.Labels))
|
||||
allLabels := t.Labels
|
||||
t.Labels = []*Label{} // We re-empty our labels struct here because we want it to be fully empty so we can put in all the actual labels.
|
||||
for _, oldLabel := range allLabels {
|
||||
found = false
|
||||
if newLabels[oldLabel.ID] != nil {
|
||||
found = true // If a new label is already in the list with old labels
|
||||
}
|
||||
|
||||
// Put all labels which are only on the old list to the trash
|
||||
if !found {
|
||||
labelsToDelete = append(labelsToDelete, oldLabel.ID)
|
||||
} else {
|
||||
t.Labels = append(t.Labels, oldLabel)
|
||||
}
|
||||
|
||||
// Put it in a list with all old labels, just using the loop here
|
||||
oldLabels[oldLabel.ID] = oldLabel
|
||||
}
|
||||
|
||||
// Delete all labels not passed
|
||||
if len(labelsToDelete) > 0 {
|
||||
_, err = x.In("label_id", labelsToDelete).
|
||||
And("task_id = ?", t.ID).
|
||||
Delete(LabelTask{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Loop through our labels and add them
|
||||
for _, l := range labels {
|
||||
// Check if the label is already added on the task and only add it if not
|
||||
if oldLabels[l.ID] != nil {
|
||||
// continue outer loop
|
||||
continue
|
||||
}
|
||||
|
||||
// Add the new label
|
||||
label, err := getLabelByIDSimple(l.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check if the user has the rights to see the label he is about to add
|
||||
hasAccessToLabel, err := label.hasAccessToLabel(creator)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !hasAccessToLabel {
|
||||
user, _ := creator.(*User)
|
||||
return ErrUserHasNoAccessToLabel{LabelID: l.ID, UserID: user.ID}
|
||||
}
|
||||
|
||||
// Insert it
|
||||
_, err = x.Insert(&LabelTask{LabelID: l.ID, TaskID: t.ID})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.Labels = append(t.Labels, label)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// LabelTaskBulk is a helper struct to update a bunch of labels at once
|
||||
type LabelTaskBulk struct {
|
||||
// All labels you want to update at once.
|
||||
Labels []*Label `json:"labels"`
|
||||
TaskID int64 `json:"-" param:"listtask"`
|
||||
|
||||
web.CRUDable `json:"-"`
|
||||
web.Rights `json:"-"`
|
||||
}
|
||||
|
||||
// Create updates a bunch of labels on a task at once
|
||||
// @Summary Update all labels on a task.
|
||||
// @Description Updates all labels on a task. Every label which is not passed but exists on the task will be deleted. Every label which does not exist on the task will be added. All labels which are passed and already exist on the task won't be touched.
|
||||
// @tags labels
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security JWTKeyAuth
|
||||
// @Param label body models.LabelTaskBulk true "The array of labels"
|
||||
// @Param taskID path int true "Task ID"
|
||||
// @Success 200 {object} models.LabelTaskBulk "The updated labels object."
|
||||
// @Failure 400 {object} code.vikunja.io/web.HTTPError "Invalid label object provided."
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /tasks/{taskID}/labels/bulk [post]
|
||||
func (ltb *LabelTaskBulk) Create(a web.Auth) (err error) {
|
||||
task, err := GetListTaskByID(ltb.TaskID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return task.updateTaskLabels(a, ltb.Labels)
|
||||
}
|
||||
77
pkg/models/label_task_rights.go
Normal file
77
pkg/models/label_task_rights.go
Normal file
@@ -0,0 +1,77 @@
|
||||
// Vikunja is a todo-list application to facilitate your life.
|
||||
// Copyright 2018 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 models
|
||||
|
||||
import (
|
||||
"code.vikunja.io/web"
|
||||
)
|
||||
|
||||
// CanCreate checks if a user can add a label to a task
|
||||
func (lt *LabelTask) CanCreate(a web.Auth) (bool, error) {
|
||||
label, err := getLabelByIDSimple(lt.LabelID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
hasAccessTolabel, err := label.hasAccessToLabel(a)
|
||||
if err != nil || !hasAccessTolabel { // If the user doesn't have access to the label, we can error out here
|
||||
return false, err
|
||||
}
|
||||
|
||||
canDoLabelTask, err := canDoLabelTask(lt.TaskID, a)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return hasAccessTolabel && canDoLabelTask, nil
|
||||
}
|
||||
|
||||
// CanDelete checks if a user can delete a label from a task
|
||||
func (lt *LabelTask) CanDelete(a web.Auth) (bool, error) {
|
||||
canDoLabelTask, err := canDoLabelTask(lt.TaskID, a)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !canDoLabelTask {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// We don't care here if the label exists or not. The only relevant thing here is if the relation already exists,
|
||||
// throw an error.
|
||||
exists, err := x.Exist(&LabelTask{LabelID: lt.LabelID, TaskID: lt.TaskID})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return exists, err
|
||||
}
|
||||
|
||||
// CanCreate determines if a user can update a labeltask
|
||||
func (ltb *LabelTaskBulk) CanCreate(a web.Auth) (bool, error) {
|
||||
return canDoLabelTask(ltb.TaskID, a)
|
||||
}
|
||||
|
||||
// Helper function to check if a user can write to a task
|
||||
// + is able to see the label
|
||||
// always the same check for either deleting or adding a label to a task
|
||||
func canDoLabelTask(taskID int64, a web.Auth) (bool, error) {
|
||||
// A user can add a label to a task if he can write to the task
|
||||
task, err := getTaskByIDSimple(taskID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return task.CanUpdate(a)
|
||||
}
|
||||
280
pkg/models/label_task_test.go
Normal file
280
pkg/models/label_task_test.go
Normal file
@@ -0,0 +1,280 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"code.vikunja.io/web"
|
||||
)
|
||||
|
||||
func TestLabelTask_ReadAll(t *testing.T) {
|
||||
type fields struct {
|
||||
ID int64
|
||||
TaskID int64
|
||||
LabelID int64
|
||||
Created int64
|
||||
CRUDable web.CRUDable
|
||||
Rights web.Rights
|
||||
}
|
||||
type args struct {
|
||||
search string
|
||||
a web.Auth
|
||||
page int
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
wantLabels interface{}
|
||||
wantErr bool
|
||||
errType func(error) bool
|
||||
}{
|
||||
{
|
||||
name: "normal",
|
||||
fields: fields{
|
||||
TaskID: 1,
|
||||
},
|
||||
args: args{
|
||||
a: &User{ID: 1},
|
||||
},
|
||||
wantLabels: []*labelWithTaskID{
|
||||
{
|
||||
TaskID: 1,
|
||||
Label: Label{
|
||||
ID: 4,
|
||||
Title: "Label #4 - visible via other task",
|
||||
CreatedByID: 2,
|
||||
CreatedBy: &User{
|
||||
ID: 2,
|
||||
Username: "user2",
|
||||
Password: "1234",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no right to see the task",
|
||||
fields: fields{
|
||||
TaskID: 14,
|
||||
},
|
||||
args: args{
|
||||
a: &User{ID: 1},
|
||||
},
|
||||
wantErr: true,
|
||||
errType: IsErrNoRightToSeeTask,
|
||||
},
|
||||
{
|
||||
name: "nonexistant task",
|
||||
fields: fields{
|
||||
TaskID: 9999,
|
||||
},
|
||||
args: args{
|
||||
a: &User{ID: 1},
|
||||
},
|
||||
wantErr: true,
|
||||
errType: IsErrListTaskDoesNotExist,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := &LabelTask{
|
||||
ID: tt.fields.ID,
|
||||
TaskID: tt.fields.TaskID,
|
||||
LabelID: tt.fields.LabelID,
|
||||
Created: tt.fields.Created,
|
||||
CRUDable: tt.fields.CRUDable,
|
||||
Rights: tt.fields.Rights,
|
||||
}
|
||||
gotLabels, err := l.ReadAll(tt.args.search, tt.args.a, tt.args.page)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("LabelTask.ReadAll() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if (err != nil) && tt.wantErr && !tt.errType(err) {
|
||||
t.Errorf("LabelTask.ReadAll() Wrong error type! Error = %v, want = %v", err, runtime.FuncForPC(reflect.ValueOf(tt.errType).Pointer()).Name())
|
||||
}
|
||||
if !reflect.DeepEqual(gotLabels, tt.wantLabels) {
|
||||
t.Errorf("LabelTask.ReadAll() = %v, want %v", gotLabels, tt.wantLabels)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLabelTask_Create(t *testing.T) {
|
||||
type fields struct {
|
||||
ID int64
|
||||
TaskID int64
|
||||
LabelID int64
|
||||
Created int64
|
||||
CRUDable web.CRUDable
|
||||
Rights web.Rights
|
||||
}
|
||||
type args struct {
|
||||
a web.Auth
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
wantErr bool
|
||||
errType func(error) bool
|
||||
wantForbidden bool
|
||||
}{
|
||||
{
|
||||
name: "normal",
|
||||
fields: fields{
|
||||
TaskID: 1,
|
||||
LabelID: 1,
|
||||
},
|
||||
args: args{
|
||||
a: &User{ID: 1},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "already existing",
|
||||
fields: fields{
|
||||
TaskID: 1,
|
||||
LabelID: 1,
|
||||
},
|
||||
args: args{
|
||||
a: &User{ID: 1},
|
||||
},
|
||||
wantErr: true,
|
||||
errType: IsErrLabelIsAlreadyOnTask,
|
||||
},
|
||||
{
|
||||
name: "nonexisting label",
|
||||
fields: fields{
|
||||
TaskID: 1,
|
||||
LabelID: 9999,
|
||||
},
|
||||
args: args{
|
||||
a: &User{ID: 1},
|
||||
},
|
||||
wantForbidden: true,
|
||||
},
|
||||
{
|
||||
name: "nonexisting task",
|
||||
fields: fields{
|
||||
TaskID: 9999,
|
||||
LabelID: 1,
|
||||
},
|
||||
args: args{
|
||||
a: &User{ID: 1},
|
||||
},
|
||||
wantForbidden: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := &LabelTask{
|
||||
ID: tt.fields.ID,
|
||||
TaskID: tt.fields.TaskID,
|
||||
LabelID: tt.fields.LabelID,
|
||||
Created: tt.fields.Created,
|
||||
CRUDable: tt.fields.CRUDable,
|
||||
Rights: tt.fields.Rights,
|
||||
}
|
||||
allowed, _ := l.CanCreate(tt.args.a)
|
||||
if !allowed && !tt.wantForbidden {
|
||||
t.Errorf("LabelTask.CanCreate() forbidden, want %v", tt.wantForbidden)
|
||||
}
|
||||
err := l.Create(tt.args.a)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("LabelTask.Create() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
if (err != nil) && tt.wantErr && !tt.errType(err) {
|
||||
t.Errorf("LabelTask.Create() Wrong error type! Error = %v, want = %v", err, runtime.FuncForPC(reflect.ValueOf(tt.errType).Pointer()).Name())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLabelTask_Delete(t *testing.T) {
|
||||
type fields struct {
|
||||
ID int64
|
||||
TaskID int64
|
||||
LabelID int64
|
||||
Created int64
|
||||
CRUDable web.CRUDable
|
||||
Rights web.Rights
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
wantErr bool
|
||||
errType func(error) bool
|
||||
auth web.Auth
|
||||
wantForbidden bool
|
||||
}{
|
||||
{
|
||||
name: "normal",
|
||||
fields: fields{
|
||||
TaskID: 1,
|
||||
LabelID: 1,
|
||||
},
|
||||
auth: &User{ID: 1},
|
||||
},
|
||||
{
|
||||
name: "delete nonexistant",
|
||||
fields: fields{
|
||||
TaskID: 1,
|
||||
LabelID: 1,
|
||||
},
|
||||
auth: &User{ID: 1},
|
||||
wantForbidden: true,
|
||||
},
|
||||
{
|
||||
name: "nonexisting label",
|
||||
fields: fields{
|
||||
TaskID: 1,
|
||||
LabelID: 9999,
|
||||
},
|
||||
auth: &User{ID: 1},
|
||||
wantForbidden: true,
|
||||
},
|
||||
{
|
||||
name: "nonexisting task",
|
||||
fields: fields{
|
||||
TaskID: 9999,
|
||||
LabelID: 1,
|
||||
},
|
||||
auth: &User{ID: 1},
|
||||
wantForbidden: true,
|
||||
},
|
||||
{
|
||||
name: "existing, but forbidden task",
|
||||
fields: fields{
|
||||
TaskID: 14,
|
||||
LabelID: 1,
|
||||
},
|
||||
auth: &User{ID: 1},
|
||||
wantForbidden: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := &LabelTask{
|
||||
ID: tt.fields.ID,
|
||||
TaskID: tt.fields.TaskID,
|
||||
LabelID: tt.fields.LabelID,
|
||||
Created: tt.fields.Created,
|
||||
CRUDable: tt.fields.CRUDable,
|
||||
Rights: tt.fields.Rights,
|
||||
}
|
||||
allowed, _ := l.CanDelete(tt.auth)
|
||||
if !allowed && !tt.wantForbidden {
|
||||
t.Errorf("LabelTask.CanDelete() forbidden, want %v", tt.wantForbidden)
|
||||
}
|
||||
err := l.Delete()
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("LabelTask.Delete() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
if (err != nil) && tt.wantErr && !tt.errType(err) {
|
||||
t.Errorf("LabelTask.Delete() Wrong error type! Error = %v, want = %v", err, runtime.FuncForPC(reflect.ValueOf(tt.errType).Pointer()).Name())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
452
pkg/models/label_test.go
Normal file
452
pkg/models/label_test.go
Normal file
@@ -0,0 +1,452 @@
|
||||
// Vikunja is a todo-list application to facilitate your life.
|
||||
// Copyright 2018 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 models
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"code.vikunja.io/web"
|
||||
)
|
||||
|
||||
func TestLabel_ReadAll(t *testing.T) {
|
||||
type fields struct {
|
||||
ID int64
|
||||
Title string
|
||||
Description string
|
||||
HexColor string
|
||||
CreatedByID int64
|
||||
CreatedBy *User
|
||||
Created int64
|
||||
Updated int64
|
||||
CRUDable web.CRUDable
|
||||
Rights web.Rights
|
||||
}
|
||||
type args struct {
|
||||
search string
|
||||
a web.Auth
|
||||
page int
|
||||
}
|
||||
user1 := &User{
|
||||
ID: 1,
|
||||
Username: "user1",
|
||||
Password: "1234",
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
wantLs interface{}
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "normal",
|
||||
args: args{
|
||||
a: &User{ID: 1},
|
||||
},
|
||||
wantLs: []*labelWithTaskID{
|
||||
{
|
||||
Label: Label{
|
||||
ID: 1,
|
||||
Title: "Label #1",
|
||||
CreatedByID: 1,
|
||||
CreatedBy: user1,
|
||||
},
|
||||
},
|
||||
{
|
||||
Label: Label{
|
||||
ID: 2,
|
||||
Title: "Label #2",
|
||||
CreatedByID: 1,
|
||||
CreatedBy: user1,
|
||||
},
|
||||
},
|
||||
{
|
||||
TaskID: 1,
|
||||
Label: Label{
|
||||
ID: 4,
|
||||
Title: "Label #4 - visible via other task",
|
||||
CreatedByID: 2,
|
||||
CreatedBy: &User{
|
||||
ID: 2,
|
||||
Username: "user2",
|
||||
Password: "1234",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid user",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := &Label{
|
||||
ID: tt.fields.ID,
|
||||
Title: tt.fields.Title,
|
||||
Description: tt.fields.Description,
|
||||
HexColor: tt.fields.HexColor,
|
||||
CreatedByID: tt.fields.CreatedByID,
|
||||
CreatedBy: tt.fields.CreatedBy,
|
||||
Created: tt.fields.Created,
|
||||
Updated: tt.fields.Updated,
|
||||
CRUDable: tt.fields.CRUDable,
|
||||
Rights: tt.fields.Rights,
|
||||
}
|
||||
gotLs, err := l.ReadAll(tt.args.search, tt.args.a, tt.args.page)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Label.ReadAll() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(gotLs, tt.wantLs) {
|
||||
t.Errorf("Label.ReadAll() = %v, want %v", gotLs, tt.wantLs)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLabel_ReadOne(t *testing.T) {
|
||||
type fields struct {
|
||||
ID int64
|
||||
Title string
|
||||
Description string
|
||||
HexColor string
|
||||
CreatedByID int64
|
||||
CreatedBy *User
|
||||
Created int64
|
||||
Updated int64
|
||||
CRUDable web.CRUDable
|
||||
Rights web.Rights
|
||||
}
|
||||
user1 := &User{
|
||||
ID: 1,
|
||||
Username: "user1",
|
||||
Password: "1234",
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want *Label
|
||||
wantErr bool
|
||||
errType func(error) bool
|
||||
auth web.Auth
|
||||
wantForbidden bool
|
||||
}{
|
||||
{
|
||||
name: "Get label #1",
|
||||
fields: fields{
|
||||
ID: 1,
|
||||
},
|
||||
want: &Label{
|
||||
ID: 1,
|
||||
Title: "Label #1",
|
||||
CreatedByID: 1,
|
||||
CreatedBy: user1,
|
||||
},
|
||||
auth: &User{ID: 1},
|
||||
},
|
||||
{
|
||||
name: "Get nonexistant label",
|
||||
fields: fields{
|
||||
ID: 9999,
|
||||
},
|
||||
wantErr: true,
|
||||
errType: IsErrLabelDoesNotExist,
|
||||
wantForbidden: true,
|
||||
auth: &User{ID: 1},
|
||||
},
|
||||
{
|
||||
name: "no rights",
|
||||
fields: fields{
|
||||
ID: 3,
|
||||
},
|
||||
wantForbidden: true,
|
||||
auth: &User{ID: 1},
|
||||
},
|
||||
{
|
||||
name: "Get label #4 - other user",
|
||||
fields: fields{
|
||||
ID: 4,
|
||||
},
|
||||
want: &Label{
|
||||
ID: 4,
|
||||
Title: "Label #4 - visible via other task",
|
||||
CreatedByID: 2,
|
||||
CreatedBy: &User{
|
||||
ID: 2,
|
||||
Username: "user2",
|
||||
Password: "1234",
|
||||
},
|
||||
},
|
||||
auth: &User{ID: 1},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := &Label{
|
||||
ID: tt.fields.ID,
|
||||
Title: tt.fields.Title,
|
||||
Description: tt.fields.Description,
|
||||
HexColor: tt.fields.HexColor,
|
||||
CreatedByID: tt.fields.CreatedByID,
|
||||
CreatedBy: tt.fields.CreatedBy,
|
||||
Created: tt.fields.Created,
|
||||
Updated: tt.fields.Updated,
|
||||
CRUDable: tt.fields.CRUDable,
|
||||
Rights: tt.fields.Rights,
|
||||
}
|
||||
|
||||
allowed, _ := l.CanRead(tt.auth)
|
||||
if !allowed && !tt.wantForbidden {
|
||||
t.Errorf("Label.CanRead() forbidden, want %v", tt.wantForbidden)
|
||||
}
|
||||
err := l.ReadOne()
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("Label.ReadOne() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
if (err != nil) && tt.wantErr && !tt.errType(err) {
|
||||
t.Errorf("Label.ReadOne() Wrong error type! Error = %v, want = %v", err, runtime.FuncForPC(reflect.ValueOf(tt.errType).Pointer()).Name())
|
||||
}
|
||||
if !reflect.DeepEqual(l, tt.want) && !tt.wantErr && !tt.wantForbidden {
|
||||
t.Errorf("Label.ReadOne() = %v, want %v", l, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLabel_Create(t *testing.T) {
|
||||
type fields struct {
|
||||
ID int64
|
||||
Title string
|
||||
Description string
|
||||
HexColor string
|
||||
CreatedByID int64
|
||||
CreatedBy *User
|
||||
Created int64
|
||||
Updated int64
|
||||
CRUDable web.CRUDable
|
||||
Rights web.Rights
|
||||
}
|
||||
type args struct {
|
||||
a web.Auth
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
wantErr bool
|
||||
wantForbidden bool
|
||||
}{
|
||||
{
|
||||
name: "normal",
|
||||
fields: fields{
|
||||
Title: "Test #1",
|
||||
Description: "Lorem Ipsum",
|
||||
HexColor: "ffccff",
|
||||
},
|
||||
args: args{
|
||||
a: &User{ID: 1},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := &Label{
|
||||
ID: tt.fields.ID,
|
||||
Title: tt.fields.Title,
|
||||
Description: tt.fields.Description,
|
||||
HexColor: tt.fields.HexColor,
|
||||
CreatedByID: tt.fields.CreatedByID,
|
||||
CreatedBy: tt.fields.CreatedBy,
|
||||
Created: tt.fields.Created,
|
||||
Updated: tt.fields.Updated,
|
||||
CRUDable: tt.fields.CRUDable,
|
||||
Rights: tt.fields.Rights,
|
||||
}
|
||||
allowed, _ := l.CanCreate(tt.args.a)
|
||||
if !allowed && !tt.wantForbidden {
|
||||
t.Errorf("Label.CanCreate() forbidden, want %v", tt.wantForbidden)
|
||||
}
|
||||
if err := l.Create(tt.args.a); (err != nil) != tt.wantErr {
|
||||
t.Errorf("Label.Create() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLabel_Update(t *testing.T) {
|
||||
type fields struct {
|
||||
ID int64
|
||||
Title string
|
||||
Description string
|
||||
HexColor string
|
||||
CreatedByID int64
|
||||
CreatedBy *User
|
||||
Created int64
|
||||
Updated int64
|
||||
CRUDable web.CRUDable
|
||||
Rights web.Rights
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
wantErr bool
|
||||
auth web.Auth
|
||||
wantForbidden bool
|
||||
}{
|
||||
{
|
||||
name: "normal",
|
||||
fields: fields{
|
||||
ID: 1,
|
||||
Title: "new and better",
|
||||
},
|
||||
auth: &User{ID: 1},
|
||||
},
|
||||
{
|
||||
name: "nonexisting",
|
||||
fields: fields{
|
||||
ID: 99999,
|
||||
Title: "new and better",
|
||||
},
|
||||
auth: &User{ID: 1},
|
||||
wantForbidden: true,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "no rights",
|
||||
fields: fields{
|
||||
ID: 3,
|
||||
Title: "new and better",
|
||||
},
|
||||
auth: &User{ID: 1},
|
||||
wantForbidden: true,
|
||||
},
|
||||
{
|
||||
name: "no rights other creator but access",
|
||||
fields: fields{
|
||||
ID: 4,
|
||||
Title: "new and better",
|
||||
},
|
||||
auth: &User{ID: 1},
|
||||
wantForbidden: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := &Label{
|
||||
ID: tt.fields.ID,
|
||||
Title: tt.fields.Title,
|
||||
Description: tt.fields.Description,
|
||||
HexColor: tt.fields.HexColor,
|
||||
CreatedByID: tt.fields.CreatedByID,
|
||||
CreatedBy: tt.fields.CreatedBy,
|
||||
Created: tt.fields.Created,
|
||||
Updated: tt.fields.Updated,
|
||||
CRUDable: tt.fields.CRUDable,
|
||||
Rights: tt.fields.Rights,
|
||||
}
|
||||
allowed, _ := l.CanUpdate(tt.auth)
|
||||
if !allowed && !tt.wantForbidden {
|
||||
t.Errorf("Label.CanUpdate() forbidden, want %v", tt.wantForbidden)
|
||||
}
|
||||
if err := l.Update(); (err != nil) != tt.wantErr {
|
||||
t.Errorf("Label.Update() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLabel_Delete(t *testing.T) {
|
||||
type fields struct {
|
||||
ID int64
|
||||
Title string
|
||||
Description string
|
||||
HexColor string
|
||||
CreatedByID int64
|
||||
CreatedBy *User
|
||||
Created int64
|
||||
Updated int64
|
||||
CRUDable web.CRUDable
|
||||
Rights web.Rights
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
wantErr bool
|
||||
auth web.Auth
|
||||
wantForbidden bool
|
||||
}{
|
||||
|
||||
{
|
||||
name: "normal",
|
||||
fields: fields{
|
||||
ID: 1,
|
||||
},
|
||||
auth: &User{ID: 1},
|
||||
},
|
||||
{
|
||||
name: "nonexisting",
|
||||
fields: fields{
|
||||
ID: 99999,
|
||||
},
|
||||
auth: &User{ID: 1},
|
||||
wantForbidden: true, // When the label does not exist, it is forbidden. We should fix this, but for everything.
|
||||
},
|
||||
{
|
||||
name: "no rights",
|
||||
fields: fields{
|
||||
ID: 3,
|
||||
},
|
||||
auth: &User{ID: 1},
|
||||
wantForbidden: true,
|
||||
},
|
||||
{
|
||||
name: "no rights but visible",
|
||||
fields: fields{
|
||||
ID: 4,
|
||||
},
|
||||
auth: &User{ID: 1},
|
||||
wantForbidden: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := &Label{
|
||||
ID: tt.fields.ID,
|
||||
Title: tt.fields.Title,
|
||||
Description: tt.fields.Description,
|
||||
HexColor: tt.fields.HexColor,
|
||||
CreatedByID: tt.fields.CreatedByID,
|
||||
CreatedBy: tt.fields.CreatedBy,
|
||||
Created: tt.fields.Created,
|
||||
Updated: tt.fields.Updated,
|
||||
CRUDable: tt.fields.CRUDable,
|
||||
Rights: tt.fields.Rights,
|
||||
}
|
||||
allowed, _ := l.CanDelete(tt.auth)
|
||||
if !allowed && !tt.wantForbidden {
|
||||
t.Errorf("Label.CanDelete() forbidden, want %v", tt.wantForbidden)
|
||||
}
|
||||
if err := l.Delete(); (err != nil) != tt.wantErr {
|
||||
t.Errorf("Label.Delete() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,28 +1,72 @@
|
||||
// Vikunja is a todo-list application to facilitate your life.
|
||||
// Copyright 2018 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 models
|
||||
|
||||
import "sort"
|
||||
import (
|
||||
"code.vikunja.io/web"
|
||||
)
|
||||
|
||||
// List represents a list of tasks
|
||||
type List struct {
|
||||
ID int64 `xorm:"int(11) autoincr not null unique pk" json:"id" param:"list"`
|
||||
Title string `xorm:"varchar(250)" json:"title" valid:"required,runelength(5|250)"`
|
||||
Description string `xorm:"varchar(1000)" json:"description" valid:"runelength(0|1000)"`
|
||||
OwnerID int64 `xorm:"int(11) INDEX" json:"-"`
|
||||
NamespaceID int64 `xorm:"int(11) INDEX" json:"-" param:"namespace"`
|
||||
// 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) not null" json:"title" valid:"required,runelength(3|250)" minLength:"3" maxLength:"250"`
|
||||
// The description of the list.
|
||||
Description string `xorm:"varchar(1000) null" json:"description" valid:"runelength(0|1000)" maxLength:"1000"`
|
||||
OwnerID int64 `xorm:"int(11) INDEX not null" json:"-"`
|
||||
NamespaceID int64 `xorm:"int(11) INDEX not null" json:"-" param:"namespace"`
|
||||
|
||||
Owner User `xorm:"-" json:"owner"`
|
||||
// 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"`
|
||||
|
||||
Created int64 `xorm:"created" json:"created" valid:"range(0|0)"`
|
||||
Updated int64 `xorm:"updated" json:"updated" valid:"range(0|0)"`
|
||||
// A unix timestamp when this list was created. You cannot change this value.
|
||||
Created int64 `xorm:"created not null" json:"created"`
|
||||
// A unix timestamp when this list was last updated. You cannot change this value.
|
||||
Updated int64 `xorm:"updated not null" json:"updated"`
|
||||
|
||||
CRUDable `xorm:"-" json:"-"`
|
||||
Rights `xorm:"-" json:"-"`
|
||||
web.CRUDable `xorm:"-" json:"-"`
|
||||
web.Rights `xorm:"-" json:"-"`
|
||||
}
|
||||
|
||||
// GetListsByNamespaceID gets all lists in a namespace
|
||||
func GetListsByNamespaceID(nID int64) (lists []*List, err error) {
|
||||
err = x.Where("namespace_id = ?", nID).Find(&lists)
|
||||
func GetListsByNamespaceID(nID int64, doer *User) (lists []*List, err error) {
|
||||
if nID == -1 {
|
||||
err = x.Select("l.*").
|
||||
Table("list").
|
||||
Alias("l").
|
||||
Join("LEFT", []string{"team_list", "tl"}, "l.id = tl.list_id").
|
||||
Join("LEFT", []string{"team_members", "tm"}, "tm.team_id = tl.team_id").
|
||||
Join("LEFT", []string{"users_list", "ul"}, "ul.list_id = l.id").
|
||||
Where("tm.user_id = ?", doer.ID).
|
||||
Or("ul.user_id = ?", doer.ID).
|
||||
GroupBy("l.id").
|
||||
Find(&lists)
|
||||
} else {
|
||||
err = x.Where("namespace_id = ?", nID).Find(&lists)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// get more list details
|
||||
err = AddListDetails(lists)
|
||||
return lists, err
|
||||
}
|
||||
|
||||
@@ -34,12 +78,17 @@ func GetListsByNamespaceID(nID int64) (lists []*List, err error) {
|
||||
// @Produce json
|
||||
// @Param p query int false "The page number. Used for pagination. If not provided, the first page of results is returned."
|
||||
// @Param s query string false "Search lists by title."
|
||||
// @Security ApiKeyAuth
|
||||
// @Security JWTKeyAuth
|
||||
// @Success 200 {array} models.List "The lists"
|
||||
// @Failure 403 {object} models.HTTPError "The user does not have access to the list"
|
||||
// @Failure 403 {object} code.vikunja.io/web.HTTPError "The user does not have access to the list"
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /lists [get]
|
||||
func (l *List) ReadAll(search string, u *User, page int) (interface{}, error) {
|
||||
func (l *List) ReadAll(search string, a web.Auth, page int) (interface{}, error) {
|
||||
u, err := getUserWithError(a)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lists, err := getRawListsForUser(search, u, page)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -57,18 +106,13 @@ func (l *List) ReadAll(search string, u *User, page int) (interface{}, error) {
|
||||
// @tags list
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security ApiKeyAuth
|
||||
// @Security JWTKeyAuth
|
||||
// @Param id path int true "List ID"
|
||||
// @Success 200 {object} models.List "The list"
|
||||
// @Failure 403 {object} models.HTTPError "The user does not have access to the list"
|
||||
// @Failure 403 {object} code.vikunja.io/web.HTTPError "The user does not have access to the list"
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /lists/{id} [get]
|
||||
func (l *List) ReadOne() (err error) {
|
||||
err = l.GetSimpleByID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get list tasks
|
||||
l.Tasks, err = GetTasksByListID(l.ID)
|
||||
if err != nil {
|
||||
@@ -102,6 +146,28 @@ func (l *List) GetSimpleByID() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// GetListSimplByTaskID gets a list by a task id
|
||||
func GetListSimplByTaskID(taskID int64) (l *List, err error) {
|
||||
// We need to re-init our list object, because otherwise xorm creates a "where for every item in that list object,
|
||||
// leading to not finding anything if the id is good, but for example the title is different.
|
||||
var list List
|
||||
exists, err := x.
|
||||
Select("list.*").
|
||||
Table(List{}).
|
||||
Join("INNER", "tasks", "list.id = tasks.list_id").
|
||||
Where("tasks.id = ?", taskID).
|
||||
Get(&list)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !exists {
|
||||
return &List{}, ErrListDoesNotExist{ID: l.ID}
|
||||
}
|
||||
|
||||
return &list, nil
|
||||
}
|
||||
|
||||
// Gets the lists only, without any tasks or so
|
||||
func getRawListsForUser(search string, u *User, page int) (lists []*List, err error) {
|
||||
fullUser, err := GetUserByID(u.ID)
|
||||
@@ -177,46 +243,3 @@ func AddListDetails(lists []*List) (err error) {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ReadAll gets all tasks for a user
|
||||
// @Summary Get tasks
|
||||
// @Description Returns all tasks on any list the user has access to.
|
||||
// @tags task
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param p query int false "The page number. Used for pagination. If not provided, the first page of results is returned."
|
||||
// @Param s query string false "Search tasks by task text."
|
||||
// @Security ApiKeyAuth
|
||||
// @Success 200 {array} models.List "The tasks"
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /tasks [get]
|
||||
func (lt *ListTask) ReadAll(search string, u *User, page int) (interface{}, error) {
|
||||
return GetTasksByUser(search, u, page)
|
||||
}
|
||||
|
||||
//GetTasksByUser returns all tasks for a user
|
||||
func GetTasksByUser(search string, u *User, page int) (tasks []*ListTask, err error) {
|
||||
// Get all lists
|
||||
lists, err := getRawListsForUser("", u, page)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get all list IDs and get the tasks
|
||||
var listIDs []int64
|
||||
for _, l := range lists {
|
||||
listIDs = append(listIDs, l.ID)
|
||||
}
|
||||
|
||||
// Then return all tasks for that lists
|
||||
if err := x.In("list_id", listIDs).Where("text LIKE ?", "%"+search+"%").Find(&tasks); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Sort it by due date
|
||||
sort.Slice(tasks, func(i, j int) bool {
|
||||
return tasks[i].DueDateUnix > tasks[j].DueDateUnix
|
||||
})
|
||||
|
||||
return tasks, err
|
||||
}
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
// Vikunja is a todo-list application to facilitate your life.
|
||||
// Copyright 2018 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 models
|
||||
|
||||
import (
|
||||
@@ -21,7 +37,8 @@ func TestList_Create(t *testing.T) {
|
||||
}
|
||||
|
||||
// Check if the user can create
|
||||
assert.True(t, dummylist.CanCreate(&doer))
|
||||
allowed, _ := dummylist.CanCreate(&doer)
|
||||
assert.True(t, allowed)
|
||||
|
||||
// Create it
|
||||
err = dummylist.Create(&doer)
|
||||
@@ -29,6 +46,9 @@ func TestList_Create(t *testing.T) {
|
||||
|
||||
// Get the list
|
||||
newdummy := List{ID: dummylist.ID}
|
||||
canRead, err := newdummy.CanRead(&doer)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, canRead)
|
||||
err = newdummy.ReadOne()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, dummylist.Title, newdummy.Title)
|
||||
@@ -36,16 +56,19 @@ func TestList_Create(t *testing.T) {
|
||||
assert.Equal(t, dummylist.OwnerID, doer.ID)
|
||||
|
||||
// Check if the user can see it
|
||||
assert.True(t, dummylist.CanRead(&doer))
|
||||
allowed, _ = dummylist.CanRead(&doer)
|
||||
assert.True(t, allowed)
|
||||
|
||||
// Try updating a list
|
||||
assert.True(t, dummylist.CanUpdate(&doer))
|
||||
allowed, _ = dummylist.CanUpdate(&doer)
|
||||
assert.True(t, allowed)
|
||||
dummylist.Description = "Lorem Ipsum dolor sit amet."
|
||||
err = dummylist.Update()
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Delete it
|
||||
assert.True(t, dummylist.CanDelete(&doer))
|
||||
allowed, _ = dummylist.CanDelete(&doer)
|
||||
assert.True(t, allowed)
|
||||
|
||||
err = dummylist.Delete()
|
||||
assert.NoError(t, err)
|
||||
|
||||
@@ -1,5 +1,26 @@
|
||||
// Vikunja is a todo-list application to facilitate your life.
|
||||
// Copyright 2018 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 models
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/metrics"
|
||||
"code.vikunja.io/web"
|
||||
)
|
||||
|
||||
// CreateOrUpdateList updates a list or creates it if it doesn't exist
|
||||
func CreateOrUpdateList(list *List) (err error) {
|
||||
|
||||
@@ -18,6 +39,7 @@ func CreateOrUpdateList(list *List) (err error) {
|
||||
|
||||
if list.ID == 0 {
|
||||
_, err = x.Insert(list)
|
||||
metrics.UpdateCount(1, metrics.ListCountKey)
|
||||
} else {
|
||||
_, err = x.ID(list.ID).Update(list)
|
||||
}
|
||||
@@ -26,6 +48,11 @@ func CreateOrUpdateList(list *List) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
err = list.GetSimpleByID()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = list.ReadOne()
|
||||
return
|
||||
|
||||
@@ -37,20 +64,20 @@ func CreateOrUpdateList(list *List) (err error) {
|
||||
// @tags list
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security ApiKeyAuth
|
||||
// @Security JWTKeyAuth
|
||||
// @Param id path int true "List ID"
|
||||
// @Param list body models.List true "The list with updated values you want to update."
|
||||
// @Success 200 {object} models.List "The updated list."
|
||||
// @Failure 400 {object} models.HTTPError "Invalid list object provided."
|
||||
// @Failure 403 {object} models.HTTPError "The user does not have access to the list"
|
||||
// @Failure 400 {object} code.vikunja.io/web.HTTPError "Invalid list object provided."
|
||||
// @Failure 403 {object} code.vikunja.io/web.HTTPError "The user does not have access to the list"
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /lists/{id} [post]
|
||||
func (l *List) Update() (err error) {
|
||||
// Check if it exists
|
||||
if err = l.GetSimpleByID(); err != nil {
|
||||
lorig := List{ID: l.ID}
|
||||
if err = lorig.GetSimpleByID(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return CreateOrUpdateList(l)
|
||||
}
|
||||
|
||||
@@ -60,15 +87,20 @@ func (l *List) Update() (err error) {
|
||||
// @tags list
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security ApiKeyAuth
|
||||
// @Security JWTKeyAuth
|
||||
// @Param namespaceID path int true "Namespace ID"
|
||||
// @Param list body models.List true "The list you want to create."
|
||||
// @Success 200 {object} models.List "The created list."
|
||||
// @Failure 400 {object} models.HTTPError "Invalid list object provided."
|
||||
// @Failure 403 {object} models.HTTPError "The user does not have access to the list"
|
||||
// @Failure 400 {object} code.vikunja.io/web.HTTPError "Invalid list object provided."
|
||||
// @Failure 403 {object} code.vikunja.io/web.HTTPError "The user does not have access to the list"
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /namespaces/{namespaceID}/lists [put]
|
||||
func (l *List) Create(doer *User) (err error) {
|
||||
func (l *List) Create(a web.Auth) (err error) {
|
||||
doer, err := getUserWithError(a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check rights
|
||||
u, err := GetUserByID(doer.ID)
|
||||
if err != nil {
|
||||
|
||||
@@ -1,15 +1,36 @@
|
||||
// Vikunja is a todo-list application to facilitate your life.
|
||||
// Copyright 2018 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 models
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/metrics"
|
||||
_ "code.vikunja.io/web" // For swaggerdocs generation
|
||||
)
|
||||
|
||||
// Delete implements the delete method of CRUDable
|
||||
// @Summary Deletes a list
|
||||
// @Description Delets a list
|
||||
// @tags list
|
||||
// @Produce json
|
||||
// @Security ApiKeyAuth
|
||||
// @Security JWTKeyAuth
|
||||
// @Param id path int true "List ID"
|
||||
// @Success 200 {object} models.Message "The list was successfully deleted."
|
||||
// @Failure 400 {object} models.HTTPError "Invalid list object provided."
|
||||
// @Failure 403 {object} models.HTTPError "The user does not have access to the list"
|
||||
// @Failure 400 {object} code.vikunja.io/web.HTTPError "Invalid list object provided."
|
||||
// @Failure 403 {object} code.vikunja.io/web.HTTPError "The user does not have access to the list"
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /lists/{id} [delete]
|
||||
func (l *List) Delete() (err error) {
|
||||
@@ -23,6 +44,7 @@ func (l *List) Delete() (err error) {
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
metrics.UpdateCount(-1, metrics.ListCountKey)
|
||||
|
||||
// Delete all todotasks on that list
|
||||
_, err = x.Where("list_id = ?", l.ID).Delete(&ListTask{})
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
// Vikunja is a todo-list application to facilitate your life.
|
||||
// Copyright 2018 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 models
|
||||
|
||||
import (
|
||||
@@ -11,7 +27,7 @@ func TestList_ReadAll(t *testing.T) {
|
||||
//assert.NoError(t, PrepareTestDatabase())
|
||||
|
||||
// Get all lists for our namespace
|
||||
lists, err := GetListsByNamespaceID(1)
|
||||
lists, err := GetListsByNamespaceID(1, &User{})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, len(lists), 2)
|
||||
|
||||
@@ -24,7 +40,7 @@ func TestList_ReadAll(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, reflect.TypeOf(lists3).Kind(), reflect.Slice)
|
||||
s := reflect.ValueOf(lists3)
|
||||
assert.Equal(t, s.Len(), 1)
|
||||
assert.Equal(t, s.Len(), 3)
|
||||
|
||||
// Try getting lists for a nonexistant user
|
||||
_, err = lists2.ReadAll("", &User{ID: 984234}, 1)
|
||||
|
||||
@@ -1,115 +1,159 @@
|
||||
// Vikunja is a todo-list application to facilitate your life.
|
||||
// Copyright 2018 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 models
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/web"
|
||||
"github.com/go-xorm/builder"
|
||||
)
|
||||
|
||||
// IsAdmin returns whether the user has admin rights on the list or not
|
||||
func (l *List) IsAdmin(u *User) bool {
|
||||
// Owners are always admins
|
||||
if l.Owner.ID == u.ID {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check individual rights
|
||||
if l.checkListUserRight(u, UserRightAdmin) {
|
||||
return true
|
||||
}
|
||||
|
||||
return l.checkListTeamRight(u, TeamRightAdmin)
|
||||
}
|
||||
|
||||
// CanWrite return whether the user can write on that list or not
|
||||
func (l *List) CanWrite(user *User) bool {
|
||||
// Admins always have write access
|
||||
if l.IsAdmin(user) {
|
||||
return true
|
||||
func (l *List) CanWrite(a web.Auth) (bool, error) {
|
||||
|
||||
// Get the list and check the right
|
||||
originalList := &List{ID: l.ID}
|
||||
err := originalList.GetSimpleByID()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Check individual rights
|
||||
if l.checkListUserRight(user, UserRightWrite) {
|
||||
return true
|
||||
user := getUserForRights(a)
|
||||
// Check if the user is either owner or can write to the list
|
||||
if originalList.isOwner(user) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return l.checkListTeamRight(user, TeamRightWrite)
|
||||
return originalList.checkRight(user, RightWrite, RightAdmin)
|
||||
}
|
||||
|
||||
// CanRead checks if a user has read access to a list
|
||||
func (l *List) CanRead(user *User) bool {
|
||||
// Admins always have read access
|
||||
if l.IsAdmin(user) {
|
||||
return true
|
||||
}
|
||||
func (l *List) CanRead(a web.Auth) (bool, error) {
|
||||
user := getUserForRights(a)
|
||||
|
||||
// Check individual rights
|
||||
if l.checkListUserRight(user, UserRightRead) {
|
||||
return true
|
||||
}
|
||||
|
||||
return l.checkListTeamRight(user, TeamRightRead)
|
||||
}
|
||||
|
||||
// CanDelete checks if the user can delete a list
|
||||
func (l *List) CanDelete(doer *User) bool {
|
||||
// Check if the user is either owner or can read
|
||||
if err := l.GetSimpleByID(); err != nil {
|
||||
log.Log.Error("Error occurred during CanDelete for List: %s", err)
|
||||
return false
|
||||
return false, err
|
||||
}
|
||||
return l.IsAdmin(doer)
|
||||
if l.isOwner(user) {
|
||||
return true, nil
|
||||
}
|
||||
return l.checkRight(user, RightRead, RightWrite, RightAdmin)
|
||||
}
|
||||
|
||||
// CanUpdate checks if the user can update a list
|
||||
func (l *List) CanUpdate(doer *User) bool {
|
||||
if err := l.GetSimpleByID(); err != nil {
|
||||
log.Log.Error("Error occurred during CanUpdate for List: %s", err)
|
||||
return false
|
||||
}
|
||||
return l.CanWrite(doer)
|
||||
func (l *List) CanUpdate(a web.Auth) (bool, error) {
|
||||
return l.CanWrite(a)
|
||||
}
|
||||
|
||||
// CanDelete checks if the user can delete a list
|
||||
func (l *List) CanDelete(a web.Auth) (bool, error) {
|
||||
return l.IsAdmin(a)
|
||||
}
|
||||
|
||||
// CanCreate checks if the user can update a list
|
||||
func (l *List) CanCreate(doer *User) bool {
|
||||
func (l *List) CanCreate(a web.Auth) (bool, error) {
|
||||
// A user can create a list if he has write access to the namespace
|
||||
n, _ := GetNamespaceByID(l.NamespaceID)
|
||||
return n.CanWrite(doer)
|
||||
n := &Namespace{ID: l.NamespaceID}
|
||||
return n.CanWrite(a)
|
||||
}
|
||||
|
||||
func (l *List) checkListTeamRight(user *User, r TeamRight) bool {
|
||||
// IsAdmin returns whether the user has admin rights on the list or not
|
||||
func (l *List) IsAdmin(a web.Auth) (bool, error) {
|
||||
user := getUserForRights(a)
|
||||
|
||||
originalList := &List{ID: l.ID}
|
||||
err := originalList.GetSimpleByID()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Check all the things
|
||||
// Check if the user is either owner or can write to the list
|
||||
// Owners are always admins
|
||||
if originalList.isOwner(user) {
|
||||
return true, nil
|
||||
}
|
||||
return originalList.checkRight(user, RightAdmin)
|
||||
}
|
||||
|
||||
// Little helper function to check if a user is list owner
|
||||
func (l *List) isOwner(u *User) bool {
|
||||
return l.OwnerID == u.ID
|
||||
}
|
||||
|
||||
// Checks n different rights for any given user
|
||||
func (l *List) checkRight(user *User, rights ...Right) (bool, error) {
|
||||
|
||||
/*
|
||||
The following loop creates an sql condition like this one:
|
||||
|
||||
(ul.user_id = 1 AND ul.right = 1) OR (un.user_id = 1 AND un.right = 1) OR
|
||||
(tm.user_id = 1 AND tn.right = 1) OR (tm2.user_id = 1 AND tl.right = 1) OR
|
||||
|
||||
for each passed right. That way, we can check with a single sql query (instead if 8)
|
||||
if the user has the right to see the list or not.
|
||||
*/
|
||||
|
||||
var conds []builder.Cond
|
||||
for _, r := range rights {
|
||||
// User conditions
|
||||
// If the list was shared directly with the user and the user has the right
|
||||
conds = append(conds, builder.And(
|
||||
builder.Eq{"ul.user_id": user.ID},
|
||||
builder.Eq{"ul.right": r},
|
||||
))
|
||||
// If the namespace this list belongs to was shared directly with the user and the user has the right
|
||||
conds = append(conds, builder.And(
|
||||
builder.Eq{"un.user_id": user.ID},
|
||||
builder.Eq{"un.right": r},
|
||||
))
|
||||
|
||||
// Team rights
|
||||
// If the list was shared directly with the team and the team has the right
|
||||
conds = append(conds, builder.And(
|
||||
builder.Eq{"tm2.user_id": user.ID},
|
||||
builder.Eq{"tl.right": r},
|
||||
))
|
||||
// If the namespace this list belongs to was shared directly with the team and the team has the right
|
||||
conds = append(conds, builder.And(
|
||||
builder.Eq{"tm.user_id": user.ID},
|
||||
builder.Eq{"tn.right": r},
|
||||
))
|
||||
}
|
||||
|
||||
exists, err := x.Select("l.*").
|
||||
Table("list").
|
||||
Alias("l").
|
||||
// User stuff
|
||||
Join("LEFT", []string{"users_namespace", "un"}, "un.namespace_id = l.namespace_id").
|
||||
Join("LEFT", []string{"users_list", "ul"}, "ul.list_id = l.id").
|
||||
Join("LEFT", []string{"namespaces", "n"}, "n.id = l.namespace_id").
|
||||
// Team stuff
|
||||
Join("LEFT", []string{"team_namespaces", "tn"}, " l.namespace_id = tn.namespace_id").
|
||||
Join("LEFT", []string{"team_members", "tm"}, "tm.team_id = tn.team_id").
|
||||
Join("LEFT", []string{"team_list", "tl"}, "l.id = tl.list_id").
|
||||
Join("LEFT", []string{"team_members", "tm2"}, "tm2.team_id = tl.team_id").
|
||||
Where("((tm.user_id = ? AND tn.right = ?) OR (tm2.user_id = ? AND tl.right = ?)) AND l.id = ?",
|
||||
user.ID, r, user.ID, r, l.ID).
|
||||
// The actual condition
|
||||
Where(builder.And(
|
||||
builder.Or(
|
||||
conds...,
|
||||
),
|
||||
builder.Eq{"l.id": l.ID},
|
||||
)).
|
||||
Exist(&List{})
|
||||
if err != nil {
|
||||
log.Log.Error("Error occurred during checkListTeamRight for List: %s", err)
|
||||
return false
|
||||
}
|
||||
|
||||
return exists
|
||||
}
|
||||
|
||||
func (l *List) checkListUserRight(user *User, r UserRight) bool {
|
||||
exists, err := x.Select("l.*").
|
||||
Table("list").
|
||||
Alias("l").
|
||||
Join("LEFT", []string{"users_namespace", "un"}, "un.namespace_id = l.namespace_id").
|
||||
Join("LEFT", []string{"users_list", "ul"}, "ul.list_id = l.id").
|
||||
Join("LEFT", []string{"namespaces", "n"}, "n.id = l.namespace_id").
|
||||
Where("((ul.user_id = ? AND ul.right = ?) "+
|
||||
"OR (un.user_id = ? AND un.right = ?) "+
|
||||
"OR n.owner_id = ?)"+
|
||||
"AND l.id = ?",
|
||||
user.ID, r, user.ID, r, user.ID, l.ID).
|
||||
Exist(&List{})
|
||||
if err != nil {
|
||||
log.Log.Error("Error occurred during checkListUserRight for List: %s", err)
|
||||
return false
|
||||
}
|
||||
|
||||
return exists
|
||||
return exists, err
|
||||
}
|
||||
|
||||
246
pkg/models/list_task_assignees.go
Normal file
246
pkg/models/list_task_assignees.go
Normal file
@@ -0,0 +1,246 @@
|
||||
// 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 models
|
||||
|
||||
import (
|
||||
"code.vikunja.io/web"
|
||||
)
|
||||
|
||||
// ListTaskAssginee represents an assignment of a user to a task
|
||||
type ListTaskAssginee struct {
|
||||
ID int64 `xorm:"int(11) autoincr not null unique pk" json:"-"`
|
||||
TaskID int64 `xorm:"int(11) INDEX not null" json:"-" param:"listtask"`
|
||||
UserID int64 `xorm:"int(11) INDEX not null" json:"user_id" param:"user"`
|
||||
Created int64 `xorm:"created not null"`
|
||||
|
||||
web.CRUDable `xorm:"-" json:"-"`
|
||||
web.Rights `xorm:"-" json:"-"`
|
||||
}
|
||||
|
||||
// TableName makes a pretty table name
|
||||
func (ListTaskAssginee) TableName() string {
|
||||
return "task_assignees"
|
||||
}
|
||||
|
||||
// ListTaskAssigneeWithUser is a helper type to deal with user joins
|
||||
type ListTaskAssigneeWithUser struct {
|
||||
TaskID int64
|
||||
User `xorm:"extends"`
|
||||
}
|
||||
|
||||
func getRawTaskAssigneesForTasks(taskIDs []int64) (taskAssignees []*ListTaskAssigneeWithUser, err error) {
|
||||
taskAssignees = []*ListTaskAssigneeWithUser{nil}
|
||||
err = x.Table("task_assignees").
|
||||
Select("task_id, users.*").
|
||||
In("task_id", taskIDs).
|
||||
Join("INNER", "users", "task_assignees.user_id = users.id").
|
||||
Find(&taskAssignees)
|
||||
return
|
||||
}
|
||||
|
||||
// Create or update a bunch of task assignees
|
||||
func (t *ListTask) updateTaskAssignees(assignees []*User) (err error) {
|
||||
|
||||
// If we don't have any new assignees, delete everything right away. Saves us some hassle.
|
||||
if len(assignees) == 0 && len(t.Assignees) > 0 {
|
||||
_, err = x.Where("task_id = ?", t.ID).
|
||||
Delete(ListTaskAssginee{})
|
||||
return err
|
||||
}
|
||||
|
||||
// If we didn't change anything (from 0 to zero) don't do anything.
|
||||
if len(assignees) == 0 && len(t.Assignees) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Make a hashmap of the new assignees for easier comparison
|
||||
newAssignees := make(map[int64]*User, len(assignees))
|
||||
for _, newAssignee := range assignees {
|
||||
newAssignees[newAssignee.ID] = newAssignee
|
||||
}
|
||||
|
||||
// Get old assignees to delete
|
||||
var found bool
|
||||
var assigneesToDelete []int64
|
||||
oldAssignees := make(map[int64]*User, len(t.Assignees))
|
||||
for _, oldAssignee := range t.Assignees {
|
||||
found = false
|
||||
if newAssignees[oldAssignee.ID] != nil {
|
||||
found = true // If a new assignee is already in the list with old assignees
|
||||
}
|
||||
|
||||
// Put all assignees which are only on the old list to the trash
|
||||
if !found {
|
||||
assigneesToDelete = append(assigneesToDelete, oldAssignee.ID)
|
||||
}
|
||||
|
||||
oldAssignees[oldAssignee.ID] = oldAssignee
|
||||
}
|
||||
|
||||
// Delete all assignees not passed
|
||||
if len(assigneesToDelete) > 0 {
|
||||
_, err = x.In("user_id", assigneesToDelete).
|
||||
And("task_id = ?", t.ID).
|
||||
Delete(ListTaskAssginee{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Get the list to perform later checks
|
||||
list := List{ID: t.ListID}
|
||||
err = list.GetSimpleByID()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Loop through our users and add them
|
||||
for _, u := range assignees {
|
||||
// Check if the user is already assigned and assign him only if not
|
||||
if oldAssignees[u.ID] != nil {
|
||||
// continue outer loop
|
||||
continue
|
||||
}
|
||||
|
||||
// Add the new assignee
|
||||
err = t.addNewAssigneeByID(u.ID, &list)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Delete a task assignee
|
||||
// @Summary Delete an assignee
|
||||
// @Description Un-assign a user from a task.
|
||||
// @tags assignees
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security JWTKeyAuth
|
||||
// @Param taskID path int true "Task ID"
|
||||
// @Param userID path int true "Assignee user ID"
|
||||
// @Success 200 {object} models.Message "The assignee was successfully deleted."
|
||||
// @Failure 403 {object} code.vikunja.io/web.HTTPError "Not allowed to delete the assignee."
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /tasks/{taskID}/assignees/{userID} [delete]
|
||||
func (la *ListTaskAssginee) Delete() (err error) {
|
||||
_, err = x.Delete(&ListTaskAssginee{TaskID: la.TaskID, UserID: la.UserID})
|
||||
return
|
||||
}
|
||||
|
||||
// Create adds a new assignee to a task
|
||||
// @Summary Add a new assignee to a task
|
||||
// @Description Adds a new assignee to a task. The assignee needs to have access to the list, the doer must be able to edit this task.
|
||||
// @tags assignees
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security JWTKeyAuth
|
||||
// @Param assignee body models.ListTaskAssginee true "The assingee object"
|
||||
// @Param taskID path int true "Task ID"
|
||||
// @Success 200 {object} models.ListTaskAssginee "The created assingee object."
|
||||
// @Failure 400 {object} code.vikunja.io/web.HTTPError "Invalid assignee object provided."
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /tasks/{taskID}/assignees [put]
|
||||
func (la *ListTaskAssginee) Create(a web.Auth) (err error) {
|
||||
|
||||
// Get the list to perform later checks
|
||||
list, err := GetListSimplByTaskID(la.TaskID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
task := &ListTask{ID: la.TaskID}
|
||||
return task.addNewAssigneeByID(la.UserID, list)
|
||||
}
|
||||
|
||||
func (t *ListTask) addNewAssigneeByID(newAssigneeID int64, list *List) (err error) {
|
||||
// Check if the user exists and has access to the list
|
||||
newAssignee, err := GetUserByID(newAssigneeID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
canRead, err := list.CanRead(&newAssignee)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !canRead {
|
||||
return ErrUserDoesNotHaveAccessToList{list.ID, newAssigneeID}
|
||||
}
|
||||
|
||||
_, err = x.Insert(ListTaskAssginee{
|
||||
TaskID: t.ID,
|
||||
UserID: newAssigneeID,
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// ReadAll gets all assignees for a task
|
||||
// @Summary Get all assignees for a task
|
||||
// @Description Returns an array with all assignees for this task.
|
||||
// @tags assignees
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param p query int false "The page number. Used for pagination. If not provided, the first page of results is returned."
|
||||
// @Param s query string false "Search assignees by their username."
|
||||
// @Security JWTKeyAuth
|
||||
// @Success 200 {array} models.User "The assignees"
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /labels [get]
|
||||
func (la *ListTaskAssginee) ReadAll(search string, a web.Auth, page int) (interface{}, error) {
|
||||
var taskAssignees []*User
|
||||
err := x.Table("task_assignees").
|
||||
Select("users.*").
|
||||
Join("INNER", "users", "task_assignees.user_id = users.id").
|
||||
Where("task_id = ? AND users.username LIKE ?", la.TaskID, "%"+search+"%").
|
||||
Limit(getLimitFromPageIndex(page)).
|
||||
Find(&taskAssignees)
|
||||
return taskAssignees, err
|
||||
}
|
||||
|
||||
// BulkAssignees is a helper struct used to update multiple assignees at once.
|
||||
type BulkAssignees struct {
|
||||
// A list with all assignees
|
||||
Assignees []*User `json:"assignees"`
|
||||
TaskID int64 `json:"-" param:"listtask"`
|
||||
|
||||
web.CRUDable `json:"-"`
|
||||
web.Rights `json:"-"`
|
||||
}
|
||||
|
||||
// Create adds new assignees to a task
|
||||
// @Summary Add multiple new assignees to a task
|
||||
// @Description Adds multiple new assignees to a task. The assignee needs to have access to the list, the doer must be able to edit this task. Every user not in the list will be unassigned from the task, pass an empty array to unassign everyone.
|
||||
// @tags assignees
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security JWTKeyAuth
|
||||
// @Param assignee body models.BulkAssignees true "The array of assignees"
|
||||
// @Param taskID path int true "Task ID"
|
||||
// @Success 200 {object} models.ListTaskAssginee "The created assingees object."
|
||||
// @Failure 400 {object} code.vikunja.io/web.HTTPError "Invalid assignee object provided."
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /tasks/{taskID}/assignees/bulk [post]
|
||||
func (ba *BulkAssignees) Create(a web.Auth) (err error) {
|
||||
task, err := GetListTaskByID(ba.TaskID) // We need to use the full method here because we need all current assignees.
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return task.updateTaskAssignees(ba.Assignees)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user