Update module swaggo/swag to v1.6.5 - autoclosed #329
|
@ -0,0 +1,9 @@
|
|||
files/
|
||||
dist/
|
||||
logs/
|
||||
|
||||
Dockerfile
|
||||
docker-manifest.tmpl
|
||||
docker-manifest-unstable.tmpl
|
||||
*.db
|
||||
*.zip
|
|
@ -0,0 +1,748 @@
|
|||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: testing
|
||||
|
||||
workspace:
|
||||
base: /go
|
||||
path: src/code.vikunja.io/api
|
||||
|
||||
volumes:
|
||||
- name: tmp-sqlite-unit
|
||||
temp:
|
||||
medium: memory
|
||||
- name: tmp-sqlite-integration
|
||||
temp:
|
||||
medium: memory
|
||||
- name: tmp-sqlite-migration
|
||||
temp:
|
||||
medium: memory
|
||||
- name: tmp-mysql-unit
|
||||
temp:
|
||||
medium: memory
|
||||
- name: tmp-mysql-integration
|
||||
temp:
|
||||
medium: memory
|
||||
- name: tmp-mysql-migration
|
||||
temp:
|
||||
medium: memory
|
||||
- name: tmp-postgres-unit
|
||||
temp:
|
||||
medium: memory
|
||||
- name: tmp-postgres-integration
|
||||
temp:
|
||||
medium: memory
|
||||
- name: tmp-postgres-migration
|
||||
temp:
|
||||
medium: memory
|
||||
|
||||
|
||||
services:
|
||||
- name: test-mysql-unit
|
||||
image: mariadb:10
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: vikunjatest
|
||||
MYSQL_DATABASE: vikunjatest
|
||||
volumes:
|
||||
- name: tmp-mysql-unit
|
||||
path: /var/lib/mysql
|
||||
- name: test-mysql-integration
|
||||
image: mariadb:10
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: vikunjatest
|
||||
MYSQL_DATABASE: vikunjatest
|
||||
volumes:
|
||||
- name: tmp-mysql-integration
|
||||
path: /var/lib/mysql
|
||||
- name: test-mysql-migration
|
||||
image: mariadb:10
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: vikunjatest
|
||||
MYSQL_DATABASE: vikunjatest
|
||||
volumes:
|
||||
- name: tmp-mysql-migration
|
||||
path: /var/lib/mysql
|
||||
- name: test-postgres-unit
|
||||
image: postgres:14
|
||||
environment:
|
||||
POSTGRES_PASSWORD: vikunjatest
|
||||
POSTGRES_DB: vikunjatest
|
||||
volumes:
|
||||
- name: tmp-postgres-unit
|
||||
path: /var/lib/postgresql/data
|
||||
commands:
|
||||
- docker-entrypoint.sh -c fsync=off -c full_page_writes=off # turns of wal
|
||||
- name: test-postgres-integration
|
||||
image: postgres:14
|
||||
environment:
|
||||
POSTGRES_PASSWORD: vikunjatest
|
||||
POSTGRES_DB: vikunjatest
|
||||
volumes:
|
||||
- name: tmp-postgres-integration
|
||||
path: /var/lib/postgresql/data
|
||||
commands:
|
||||
- docker-entrypoint.sh -c fsync=off -c full_page_writes=off # turns of wal
|
||||
- name: test-postgres-migration
|
||||
image: postgres:14
|
||||
environment:
|
||||
POSTGRES_PASSWORD: vikunjatest
|
||||
POSTGRES_DB: vikunjatest
|
||||
volumes:
|
||||
- name: tmp-postgres-migration
|
||||
path: /var/lib/postgresql/data
|
||||
commands:
|
||||
- docker-entrypoint.sh -c fsync=off -c full_page_writes=off # turns of wal
|
||||
|
||||
trigger:
|
||||
branch:
|
||||
include:
|
||||
- main
|
||||
event:
|
||||
include:
|
||||
- push
|
||||
- pull_request
|
||||
|
||||
steps:
|
||||
- name: fetch-tags
|
||||
image: docker:git
|
||||
commands:
|
||||
- git fetch --tags
|
||||
|
||||
# We're statically compiling the magefile to avoid race condition issues caused by multiple pipeline steps
|
||||
# compiling the same magefile at the same time. It's also faster if each step does not need to compile it first.
|
||||
- name: mage
|
||||
image: vikunja/golang-build:latest
|
||||
pull: always
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
commands:
|
||||
- mage -compile ./mage-static
|
||||
- env
|
||||
when:
|
||||
event: [ push, tag, pull_request ]
|
||||
|
||||
- name: prepare-build
|
||||
image: vikunja/golang-build:latest
|
||||
pull: always
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
depends_on: [ mage ]
|
||||
commands:
|
||||
- ./mage-static do-the-swag
|
||||
when:
|
||||
event: [ push, tag, pull_request ]
|
||||
|
||||
- name: build
|
||||
image: vikunja/golang-build:latest
|
||||
pull: always
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
depends_on: [ prepare-build ]
|
||||
commands:
|
||||
- ./mage-static build:build
|
||||
when:
|
||||
event: [ push, tag, pull_request ]
|
||||
|
||||
- name: lint
|
||||
image: golang:1.19-alpine
|
||||
pull: always
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
depends_on: [ build ]
|
||||
commands:
|
||||
- export "GOROOT=$(go env GOROOT)"
|
||||
- apk --no-cache add build-base git
|
||||
- wget -O - -q https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.52.1
|
||||
- ./mage-static check:golangci
|
||||
when:
|
||||
event: [ push, tag, pull_request ]
|
||||
|
||||
- name: test-migration-prepare
|
||||
image: kolaente/toolbox:latest
|
||||
pull: always
|
||||
commands:
|
||||
# Get the latest version
|
||||
- wget https://dl.vikunja.io/api/unstable/vikunja-unstable-linux-amd64-full.zip -q -O vikunja-latest.zip
|
||||
- unzip vikunja-latest.zip vikunja-unstable-linux-amd64
|
||||
|
||||
- name: test-migration-sqlite
|
||||
image: vikunja/golang-build:latest
|
||||
pull: always
|
||||
depends_on: [ test-migration-prepare, build ]
|
||||
environment:
|
||||
VIKUNJA_DATABASE_TYPE: sqlite
|
||||
VIKUNJA_DATABASE_PATH: /db/vikunja-migration-test.db
|
||||
VIKUNJA_LOG_DATABASE: stdout
|
||||
VIKUNJA_LOG_DATABASELEVEL: debug
|
||||
volumes:
|
||||
- name: tmp-sqlite-migration
|
||||
path: /db
|
||||
commands:
|
||||
- ./vikunja-unstable-linux-amd64 migrate
|
||||
# Run the migrations from the binary build in the step before
|
||||
- ./vikunja migrate
|
||||
when:
|
||||
event: [ push, tag, pull_request ]
|
||||
|
||||
- name: test-migration-mysql
|
||||
image: vikunja/golang-build:latest
|
||||
pull: always
|
||||
depends_on: [ test-migration-prepare, build ]
|
||||
environment:
|
||||
VIKUNJA_DATABASE_TYPE: mysql
|
||||
VIKUNJA_DATABASE_HOST: test-mysql-migration
|
||||
VIKUNJA_DATABASE_USER: root
|
||||
VIKUNJA_DATABASE_PASSWORD: vikunjatest
|
||||
VIKUNJA_DATABASE_DATABASE: vikunjatest
|
||||
VIKUNJA_LOG_DATABASE: stdout
|
||||
VIKUNJA_LOG_DATABASELEVEL: debug
|
||||
commands:
|
||||
- ./vikunja-unstable-linux-amd64 migrate
|
||||
# Run the migrations from the binary build in the step before
|
||||
- ./vikunja migrate
|
||||
when:
|
||||
event: [ push, tag, pull_request ]
|
||||
|
||||
- name: test-migration-psql
|
||||
image: vikunja/golang-build:latest
|
||||
pull: always
|
||||
depends_on: [ test-migration-prepare, build ]
|
||||
environment:
|
||||
VIKUNJA_DATABASE_TYPE: postgres
|
||||
VIKUNJA_DATABASE_HOST: test-postgres-migration
|
||||
VIKUNJA_DATABASE_USER: postgres
|
||||
VIKUNJA_DATABASE_PASSWORD: vikunjatest
|
||||
VIKUNJA_DATABASE_DATABASE: vikunjatest
|
||||
VIKUNJA_DATABASE_SSLMODE: disable
|
||||
VIKUNJA_LOG_DATABASE: stdout
|
||||
VIKUNJA_LOG_DATABASELEVEL: debug
|
||||
commands:
|
||||
- ./vikunja-unstable-linux-amd64 migrate
|
||||
# Run the migrations from the binary build in the step before
|
||||
- ./vikunja migrate
|
||||
when:
|
||||
event: [ push, tag, pull_request ]
|
||||
|
||||
- name: test
|
||||
image: vikunja/golang-build:latest
|
||||
pull: always
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
commands:
|
||||
- ./mage-static test:unit
|
||||
depends_on: [ fetch-tags, prepare-build ]
|
||||
when:
|
||||
event: [ push, tag, pull_request ]
|
||||
|
||||
- name: test-sqlite
|
||||
image: vikunja/golang-build:latest
|
||||
pull: always
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
VIKUNJA_TESTS_USE_CONFIG: 1
|
||||
VIKUNJA_DATABASE_TYPE: sqlite
|
||||
VIKUNJA_DATABASE_PATH: /db/vikunja-test.db
|
||||
volumes:
|
||||
- name: tmp-sqlite-unit
|
||||
path: /db
|
||||
commands:
|
||||
- ./mage-static test:unit
|
||||
depends_on: [ fetch-tags, prepare-build ]
|
||||
when:
|
||||
event: [ push, tag, pull_request ]
|
||||
|
||||
- name: test-mysql
|
||||
image: vikunja/golang-build:latest
|
||||
pull: always
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
VIKUNJA_TESTS_USE_CONFIG: 1
|
||||
VIKUNJA_DATABASE_TYPE: mysql
|
||||
VIKUNJA_DATABASE_HOST: test-mysql-unit
|
||||
VIKUNJA_DATABASE_USER: root
|
||||
VIKUNJA_DATABASE_PASSWORD: vikunjatest
|
||||
VIKUNJA_DATABASE_DATABASE: vikunjatest
|
||||
commands:
|
||||
- ./mage-static test:unit
|
||||
depends_on: [ fetch-tags, prepare-build ]
|
||||
when:
|
||||
event: [ push, tag, pull_request ]
|
||||
|
||||
- name: test-postgres
|
||||
image: vikunja/golang-build:latest
|
||||
pull: always
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
VIKUNJA_TESTS_USE_CONFIG: 1
|
||||
VIKUNJA_DATABASE_TYPE: postgres
|
||||
VIKUNJA_DATABASE_HOST: test-postgres-unit
|
||||
VIKUNJA_DATABASE_USER: postgres
|
||||
VIKUNJA_DATABASE_PASSWORD: vikunjatest
|
||||
VIKUNJA_DATABASE_DATABASE: vikunjatest
|
||||
VIKUNJA_DATABASE_SSLMODE: disable
|
||||
commands:
|
||||
- ./mage-static test:unit
|
||||
depends_on: [ fetch-tags, prepare-build ]
|
||||
when:
|
||||
event: [ push, tag, pull_request ]
|
||||
|
||||
- name: integration-test
|
||||
image: vikunja/golang-build:latest
|
||||
pull: always
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
commands:
|
||||
- ./mage-static test:integration
|
||||
depends_on: [ fetch-tags, prepare-build ]
|
||||
when:
|
||||
event: [ push, tag, pull_request ]
|
||||
|
||||
- name: integration-test-sqlite
|
||||
image: vikunja/golang-build:latest
|
||||
pull: always
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
VIKUNJA_TESTS_USE_CONFIG: 1
|
||||
VIKUNJA_DATABASE_TYPE: sqlite
|
||||
VIKUNJA_DATABASE_PATH: /db/vikunja-test.db
|
||||
volumes:
|
||||
- name: tmp-sqlite-integration
|
||||
path: /db
|
||||
commands:
|
||||
- ./mage-static test:integration
|
||||
depends_on: [ fetch-tags, prepare-build ]
|
||||
when:
|
||||
event: [ push, tag, pull_request ]
|
||||
|
||||
- name: integration-test-mysql
|
||||
image: vikunja/golang-build:latest
|
||||
pull: always
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
VIKUNJA_TESTS_USE_CONFIG: 1
|
||||
VIKUNJA_DATABASE_TYPE: mysql
|
||||
VIKUNJA_DATABASE_HOST: test-mysql-integration
|
||||
VIKUNJA_DATABASE_USER: root
|
||||
VIKUNJA_DATABASE_PASSWORD: vikunjatest
|
||||
VIKUNJA_DATABASE_DATABASE: vikunjatest
|
||||
commands:
|
||||
- ./mage-static test:integration
|
||||
depends_on: [ fetch-tags, prepare-build ]
|
||||
when:
|
||||
event: [ push, tag, pull_request ]
|
||||
|
||||
- name: integration-test-postgres
|
||||
image: vikunja/golang-build:latest
|
||||
pull: always
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
VIKUNJA_TESTS_USE_CONFIG: 1
|
||||
VIKUNJA_DATABASE_TYPE: postgres
|
||||
VIKUNJA_DATABASE_HOST: test-postgres-integration
|
||||
VIKUNJA_DATABASE_USER: postgres
|
||||
VIKUNJA_DATABASE_PASSWORD: vikunjatest
|
||||
VIKUNJA_DATABASE_DATABASE: vikunjatest
|
||||
VIKUNJA_DATABASE_SSLMODE: disable
|
||||
commands:
|
||||
- ./mage-static test:integration
|
||||
depends_on: [ fetch-tags, prepare-build ]
|
||||
when:
|
||||
event: [ push, tag, pull_request ]
|
||||
|
||||
---
|
||||
########
|
||||
# Build a release when tagging
|
||||
########
|
||||
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: release
|
||||
|
||||
depends_on:
|
||||
- testing
|
||||
|
||||
workspace:
|
||||
base: /source
|
||||
path: /
|
||||
|
||||
trigger:
|
||||
ref:
|
||||
- refs/heads/main
|
||||
- "refs/tags/**"
|
||||
|
||||
steps:
|
||||
# Needed to get the versions right as they depend on tags
|
||||
- name: fetch-tags
|
||||
image: docker:git
|
||||
commands:
|
||||
- git fetch --tags
|
||||
|
||||
# We're statically compiling the magefile to avoid race condition issues caused by multiple pipeline steps
|
||||
# compiling the same magefile at the same time. It's also faster if each step does not need to compile it first.
|
||||
- name: mage
|
||||
image: vikunja/golang-build:latest
|
||||
pull: always
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
commands:
|
||||
- mage -compile ./mage-static
|
||||
when:
|
||||
event: [ push, tag, pull_request ]
|
||||
|
||||
- name: before-static-build
|
||||
image: techknowlogick/xgo:latest
|
||||
pull: always
|
||||
commands:
|
||||
- export PATH=$PATH:$GOPATH/bin
|
||||
- go install github.com/magefile/mage
|
||||
- ./mage-static release:dirs
|
||||
- ./mage-static do-the-swag
|
||||
depends_on: [ fetch-tags, mage ]
|
||||
|
||||
- name: static-build-windows
|
||||
image: techknowlogick/xgo:latest
|
||||
pull: always
|
||||
environment:
|
||||
# This path does not exist. However, when we set the gopath to /go, the build fails. Not sure why.
|
||||
# Leaving this here until we know how to resolve this properly.
|
||||
GOPATH: /srv/app
|
||||
commands:
|
||||
- export PATH=$PATH:$GOPATH/bin
|
||||
- go install github.com/magefile/mage
|
||||
- ./mage-static release:windows
|
||||
depends_on: [ before-static-build ]
|
||||
|
||||
- name: static-build-linux
|
||||
image: techknowlogick/xgo:latest
|
||||
pull: always
|
||||
environment:
|
||||
# This path does not exist. However, when we set the gopath to /go, the build fails. Not sure why.
|
||||
# Leaving this here until we know how to resolve this properly.
|
||||
GOPATH: /srv/app
|
||||
commands:
|
||||
- export PATH=$PATH:$GOPATH/bin
|
||||
- go install github.com/magefile/mage
|
||||
- ./mage-static release:linux
|
||||
depends_on: [ before-static-build ]
|
||||
|
||||
- name: static-build-darwin
|
||||
image: techknowlogick/xgo:latest
|
||||
pull: always
|
||||
environment:
|
||||
# This path does not exist. However, when we set the gopath to /go, the build fails. Not sure why.
|
||||
# Leaving this here until we know how to resolve this properly.
|
||||
GOPATH: /srv/app
|
||||
commands:
|
||||
- export PATH=$PATH:$GOPATH/bin
|
||||
- go install github.com/magefile/mage
|
||||
- ./mage-static release:darwin
|
||||
depends_on: [ before-static-build ]
|
||||
|
||||
- name: after-build-compress
|
||||
image: kolaente/upx
|
||||
pull: always
|
||||
depends_on:
|
||||
- static-build-windows
|
||||
- static-build-linux
|
||||
- static-build-darwin
|
||||
commands:
|
||||
- ./mage-static release:compress
|
||||
|
||||
- name: after-build-static
|
||||
image: techknowlogick/xgo:latest
|
||||
pull: always
|
||||
depends_on:
|
||||
- after-build-compress
|
||||
commands:
|
||||
- go install github.com/magefile/mage
|
||||
- ./mage-static release:copy
|
||||
- ./mage-static release:check
|
||||
- ./mage-static release:os-package
|
||||
- ./mage-static release:zip
|
||||
|
||||
- name: sign-release
|
||||
image: plugins/gpgsign:1
|
||||
pull: always
|
||||
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
|
||||
pull: always
|
||||
settings:
|
||||
bucket: vikunja-releases
|
||||
access_key:
|
||||
from_secret: aws_access_key_id
|
||||
secret_key:
|
||||
from_secret: aws_secret_access_key
|
||||
endpoint: https://s3.fr-par.scw.cloud
|
||||
region: fr-par
|
||||
path_style: true
|
||||
strip_prefix: dist/zip/
|
||||
source: dist/zip/*
|
||||
target: /api/unstable/
|
||||
when:
|
||||
branch:
|
||||
- main
|
||||
event:
|
||||
- push
|
||||
depends_on: [ sign-release ]
|
||||
|
||||
- name: release-version
|
||||
image: plugins/s3
|
||||
pull: always
|
||||
settings:
|
||||
bucket: vikunja-releases
|
||||
access_key:
|
||||
from_secret: aws_access_key_id
|
||||
secret_key:
|
||||
from_secret: aws_secret_access_key
|
||||
endpoint: https://s3.fr-par.scw.cloud
|
||||
region: fr-par
|
||||
path_style: true
|
||||
strip_prefix: dist/zip/
|
||||
source: dist/zip/*
|
||||
target: /api/${DRONE_TAG##v}/
|
||||
when:
|
||||
event:
|
||||
- tag
|
||||
depends_on: [ sign-release ]
|
||||
|
||||
# Build os packages and push it to our bucket
|
||||
- name: build-os-packages-unstable
|
||||
image: goreleaser/nfpm:v2.28.0
|
||||
pull: always
|
||||
commands:
|
||||
- apk add git go
|
||||
- ./mage-static release:packages
|
||||
- mv dist/os-packages/vikunja*.x86_64.rpm dist/os-packages/vikunja-unstable-x86_64.rpm
|
||||
- mv dist/os-packages/vikunja*_amd64.deb dist/os-packages/vikunja-unstable-amd64.deb
|
||||
- mv dist/os-packages/vikunja*_x86_64.apk dist/os-packages/vikunja-unstable-x86_64.apk
|
||||
when:
|
||||
branch:
|
||||
- main
|
||||
event:
|
||||
- push
|
||||
depends_on: [ after-build-compress ]
|
||||
|
||||
- name: build-os-packages-version
|
||||
image: goreleaser/nfpm:v2.28.0
|
||||
pull: always
|
||||
commands:
|
||||
- apk add git go
|
||||
- ./mage-static release:packages
|
||||
- mv dist/os-packages/vikunja*.x86_64.rpm dist/os-packages/vikunja-${DRONE_TAG##v}-x86_64.rpm
|
||||
- mv dist/os-packages/vikunja*_amd64.deb dist/os-packages/vikunja-${DRONE_TAG##v}-amd64.deb
|
||||
- mv dist/os-packages/vikunja*_x86_64.apk dist/os-packages/vikunja-${DRONE_TAG##v}-x86_64.apk
|
||||
when:
|
||||
event:
|
||||
- tag
|
||||
depends_on: [ after-build-compress ]
|
||||
|
||||
# Push the os releases to our pseudo-s3-bucket
|
||||
- name: release-os-latest
|
||||
image: plugins/s3
|
||||
pull: always
|
||||
settings:
|
||||
bucket: vikunja-releases
|
||||
access_key:
|
||||
from_secret: aws_access_key_id
|
||||
secret_key:
|
||||
from_secret: aws_secret_access_key
|
||||
endpoint: https://s3.fr-par.scw.cloud
|
||||
region: fr-par
|
||||
path_style: true
|
||||
strip_prefix: dist/os-packages/
|
||||
source: dist/os-packages/*
|
||||
target: /api/unstable/
|
||||
when:
|
||||
branch:
|
||||
- main
|
||||
event:
|
||||
- push
|
||||
depends_on: [ build-os-packages-unstable ]
|
||||
|
||||
- name: release-os-version
|
||||
image: plugins/s3
|
||||
pull: always
|
||||
settings:
|
||||
bucket: vikunja-releases
|
||||
access_key:
|
||||
from_secret: aws_access_key_id
|
||||
secret_key:
|
||||
from_secret: aws_secret_access_key
|
||||
endpoint: https://s3.fr-par.scw.cloud
|
||||
region: fr-par
|
||||
path_style: true
|
||||
strip_prefix: dist/os-packages/
|
||||
source: dist/os-packages/*
|
||||
target: /api/${DRONE_TAG##v}/
|
||||
when:
|
||||
event:
|
||||
- tag
|
||||
depends_on: [ build-os-packages-version ]
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: deploy-docs
|
||||
|
||||
workspace:
|
||||
base: /go
|
||||
path: src/code.vikunja.io/api
|
||||
|
||||
clone:
|
||||
depth: 50
|
||||
|
||||
trigger:
|
||||
event:
|
||||
- push
|
||||
branch:
|
||||
- main
|
||||
|
||||
steps:
|
||||
- name: theme
|
||||
image: kolaente/toolbox
|
||||
pull: always
|
||||
commands:
|
||||
- mkdir docs/themes/vikunja -p
|
||||
- cd docs/themes/vikunja
|
||||
- wget https://dl.vikunja.io/theme/vikunja-theme.tar.gz
|
||||
- tar -xzf vikunja-theme.tar.gz
|
||||
|
||||
- name: build
|
||||
image: klakegg/hugo:0.107.0
|
||||
pull: always
|
||||
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: always
|
||||
settings:
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
repo: vikunja/docs
|
||||
context: docs/
|
||||
dockerfile: docs/Dockerfile
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: docker-release
|
||||
|
||||
depends_on:
|
||||
- testing
|
||||
|
||||
trigger:
|
||||
ref:
|
||||
- refs/heads/main
|
||||
- "refs/tags/**"
|
||||
|
||||
steps:
|
||||
- name: fetch-tags
|
||||
image: docker:git
|
||||
commands:
|
||||
- git fetch --tags
|
||||
|
||||
- name: docker-unstable
|
||||
image: thegeeklab/drone-docker-buildx
|
||||
privileged: true
|
||||
pull: always
|
||||
settings:
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
repo: vikunja/api
|
||||
tags: unstable
|
||||
platforms:
|
||||
- linux/386
|
||||
- linux/amd64
|
||||
- linux/arm/v6
|
||||
- linux/arm/v7
|
||||
- linux/arm64/v8
|
||||
depends_on: [ fetch-tags ]
|
||||
when:
|
||||
ref:
|
||||
- refs/heads/main
|
||||
|
||||
- name: generate-tags
|
||||
image: thegeeklab/docker-autotag
|
||||
environment:
|
||||
DOCKER_AUTOTAG_VERSION: ${DRONE_TAG}
|
||||
DOCKER_AUTOTAG_EXTRA_TAGS: latest
|
||||
DOCKER_AUTOTAG_OUTPUT_FILE: .tags
|
||||
depends_on: [ fetch-tags ]
|
||||
when:
|
||||
ref:
|
||||
- "refs/tags/**"
|
||||
|
||||
- name: docker-release
|
||||
image: thegeeklab/drone-docker-buildx
|
||||
privileged: true
|
||||
pull: always
|
||||
settings:
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
repo: vikunja/api
|
||||
platforms:
|
||||
- linux/386
|
||||
- linux/amd64
|
||||
- linux/arm/v6
|
||||
- linux/arm/v7
|
||||
- linux/arm64/v8
|
||||
depends_on: [ generate-tags ]
|
||||
when:
|
||||
ref:
|
||||
- "refs/tags/**"
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: notify
|
||||
|
||||
trigger:
|
||||
ref:
|
||||
- refs/heads/main
|
||||
- "refs/tags/**"
|
||||
|
||||
depends_on:
|
||||
- testing
|
||||
- release
|
||||
- deploy-docs
|
||||
- docker-release
|
||||
|
||||
steps:
|
||||
- name: notify
|
||||
image: plugins/matrix
|
||||
settings:
|
||||
homeserver: https://matrix.org
|
||||
roomid: WqBDCxzghKcNflkErL:matrix.org
|
||||
username:
|
||||
from_secret: matrix_username
|
||||
password:
|
||||
from_secret: matrix_password
|
||||
when:
|
||||
status:
|
||||
- success
|
||||
- failure
|
||||
---
|
||||
kind: signature
|
||||
hmac: 7bade2fc44072cf5730f7f15b5be18058ce47d216853c8c39c692f967c640e20
|
||||
|
||||
...
|
615
.drone1.yml
615
.drone1.yml
|
@ -1,615 +0,0 @@
|
|||
kind: pipeline
|
||||
name: testing
|
||||
|
||||
workspace:
|
||||
base: /go
|
||||
path: src/code.vikunja.io/api
|
||||
|
||||
services:
|
||||
- name: test-mysql-unit
|
||||
image: mariadb:10
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: vikunjatest
|
||||
MYSQL_DATABASE: vikunjatest
|
||||
- name: test-mysql-integration
|
||||
image: mariadb:10
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: vikunjatest
|
||||
MYSQL_DATABASE: vikunjatest
|
||||
- name: test-postgres-unit
|
||||
image: postgres:12
|
||||
environment:
|
||||
POSTGRES_PASSWORD: vikunjatest
|
||||
POSTGRES_DB: vikunjatest
|
||||
- name: test-postgres-integration
|
||||
image: postgres:12
|
||||
environment:
|
||||
POSTGRES_PASSWORD: vikunjatest
|
||||
POSTGRES_DB: vikunjatest
|
||||
|
||||
trigger:
|
||||
branch:
|
||||
include:
|
||||
- master
|
||||
event:
|
||||
include:
|
||||
- push
|
||||
- pull_request
|
||||
|
||||
steps:
|
||||
- name: fetch-tags
|
||||
image: docker:git
|
||||
commands:
|
||||
- git fetch --tags
|
||||
|
||||
- name: build
|
||||
image: vikunja/golang-build:latest
|
||||
pull: true
|
||||
environment:
|
||||
GOFLAGS: '-mod=vendor'
|
||||
commands:
|
||||
- make generate
|
||||
- make lint
|
||||
- make fmt-check
|
||||
# - make got-swag # Commented out until we figured out how to get this working on drone
|
||||
- make ineffassign-check
|
||||
- make misspell-check
|
||||
- make goconst-check
|
||||
- make gocyclo-check
|
||||
- make static-check
|
||||
- make build
|
||||
when:
|
||||
event: [ push, tag, pull_request ]
|
||||
|
||||
- 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-mysql-unit
|
||||
VIKUNJA_DATABASE_USER: root
|
||||
VIKUNJA_DATABASE_PASSWORD: vikunjatest
|
||||
VIKUNJA_DATABASE_DATABASE: vikunjatest
|
||||
commands:
|
||||
- make test
|
||||
depends_on: [ build ]
|
||||
when:
|
||||
event: [ push, tag, pull_request ]
|
||||
|
||||
- name: test-postgres
|
||||
image: vikunja/golang-build:latest
|
||||
pull: true
|
||||
environment:
|
||||
VIKUNJA_TESTS_USE_CONFIG: 1
|
||||
VIKUNJA_DATABASE_TYPE: postgres
|
||||
VIKUNJA_DATABASE_HOST: test-postgres-unit
|
||||
VIKUNJA_DATABASE_USER: postgres
|
||||
VIKUNJA_DATABASE_PASSWORD: vikunjatest
|
||||
VIKUNJA_DATABASE_DATABASE: vikunjatest
|
||||
VIKUNJA_DATABASE_SSLMODE: disable
|
||||
commands:
|
||||
- make test
|
||||
depends_on: [ build ]
|
||||
when:
|
||||
event: [ push, tag, pull_request ]
|
||||
|
||||
- name: integration-test
|
||||
image: vikunja/golang-build:latest
|
||||
pull: true
|
||||
commands:
|
||||
- make integration-test
|
||||
depends_on: [ build ]
|
||||
when:
|
||||
event: [ push, tag, pull_request ]
|
||||
|
||||
- name: integration-test-sqlite
|
||||
image: vikunja/golang-build:latest
|
||||
pull: true
|
||||
environment:
|
||||
VIKUNJA_TESTS_USE_CONFIG: 1
|
||||
VIKUNJA_DATABASE_TYPE: sqlite
|
||||
commands:
|
||||
- make integration-test
|
||||
depends_on: [ build ]
|
||||
when:
|
||||
event: [ push, tag, pull_request ]
|
||||
|
||||
- name: integration-test-mysql
|
||||
image: vikunja/golang-build:latest
|
||||
pull: true
|
||||
environment:
|
||||
VIKUNJA_TESTS_USE_CONFIG: 1
|
||||
VIKUNJA_DATABASE_TYPE: mysql
|
||||
VIKUNJA_DATABASE_HOST: test-mysql-integration
|
||||
VIKUNJA_DATABASE_USER: root
|
||||
VIKUNJA_DATABASE_PASSWORD: vikunjatest
|
||||
VIKUNJA_DATABASE_DATABASE: vikunjatest
|
||||
commands:
|
||||
- make integration-test
|
||||
depends_on: [ build ]
|
||||
when:
|
||||
event: [ push, tag, pull_request ]
|
||||
|
||||
- name: integration-test-postgres
|
||||
image: vikunja/golang-build:latest
|
||||
pull: true
|
||||
environment:
|
||||
VIKUNJA_TESTS_USE_CONFIG: 1
|
||||
VIKUNJA_DATABASE_TYPE: postgres
|
||||
VIKUNJA_DATABASE_HOST: test-postgres-integration
|
||||
VIKUNJA_DATABASE_USER: postgres
|
||||
VIKUNJA_DATABASE_PASSWORD: vikunjatest
|
||||
VIKUNJA_DATABASE_DATABASE: vikunjatest
|
||||
VIKUNJA_DATABASE_SSLMODE: disable
|
||||
commands:
|
||||
- make integration-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: /go
|
||||
path: src/code.vikunja.io/api
|
||||
|
||||
trigger:
|
||||
branch:
|
||||
- master
|
||||
event:
|
||||
- push
|
||||
|
||||
steps:
|
||||
# Needed to get the versions right as they depend on tags
|
||||
- name: fetch-tags
|
||||
image: docker:git
|
||||
commands:
|
||||
- git fetch --tags
|
||||
|
||||
- name: before-static-build
|
||||
image: techknowlogick/xgo:latest
|
||||
pull: true
|
||||
commands:
|
||||
- export PATH=$PATH:$GOPATH/bin
|
||||
- make generate
|
||||
- make release-dirs
|
||||
depends_on: [ fetch-tags ]
|
||||
|
||||
- name: static-build-windows
|
||||
image: techknowlogick/xgo:latest
|
||||
pull: true
|
||||
environment:
|
||||
# This path does not exist. However, when we set the gopath to /go, the build fails. Not sure why.
|
||||
# Leaving this here until we know how to resolve this properly.
|
||||
GOPATH: /srv/app
|
||||
commands:
|
||||
- export PATH=$PATH:$GOPATH/bin
|
||||
- make release-windows
|
||||
depends_on: [ before-static-build ]
|
||||
|
||||
- name: static-build-linux
|
||||
image: techknowlogick/xgo:latest
|
||||
pull: true
|
||||
environment:
|
||||
# This path does not exist. However, when we set the gopath to /go, the build fails. Not sure why.
|
||||
# Leaving this here until we know how to resolve this properly.
|
||||
GOPATH: /srv/app
|
||||
commands:
|
||||
- export PATH=$PATH:$GOPATH/bin
|
||||
- make release-linux
|
||||
depends_on: [ before-static-build ]
|
||||
|
||||
- name: static-build-darwin
|
||||
image: techknowlogick/xgo:latest
|
||||
pull: true
|
||||
environment:
|
||||
# This path does not exist. However, when we set the gopath to /go, the build fails. Not sure why.
|
||||
# Leaving this here until we know how to resolve this properly.
|
||||
GOPATH: /srv/app
|
||||
commands:
|
||||
- export PATH=$PATH:$GOPATH/bin
|
||||
- make release-darwin
|
||||
depends_on: [ before-static-build ]
|
||||
|
||||
- name: after-build-compress
|
||||
image: kolaente/upx
|
||||
pull: true
|
||||
depends_on:
|
||||
- static-build-windows
|
||||
- static-build-linux
|
||||
- static-build-darwin
|
||||
commands:
|
||||
- make release-compress
|
||||
|
||||
- name: after-build-static
|
||||
image: techknowlogick/xgo:latest
|
||||
pull: true
|
||||
depends_on:
|
||||
- after-build-compress
|
||||
commands:
|
||||
- make release-copy
|
||||
- make release-check
|
||||
- make release-os-package
|
||||
- make release-zip
|
||||
|
||||
- name: sign-release
|
||||
image: plugins/gpgsign:1
|
||||
pull: true
|
||||
depends_on: [ after-build-static ]
|
||||
settings:
|
||||
key:
|
||||
from_secret: gpg_privkey
|
||||
passphrase:
|
||||
from_secret: gpg_password
|
||||
files:
|
||||
- dist/zip/*
|
||||
detach_sign: true
|
||||
|
||||
# Push the releases to our pseudo-s3-bucket
|
||||
- name: release-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: /api/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
|
||||
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: /deb/
|
||||
depends_on: [ deb-structure ]
|
||||
|
||||
# Build the docker image and push it to docker hub
|
||||
- name: docker
|
||||
image: plugins/docker
|
||||
pull: true
|
||||
settings:
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
repo: vikunja/api
|
||||
auto_tag: true
|
||||
depends_on: [ fetch-tags ]
|
||||
|
||||
- name: telegram
|
||||
image: appleboy/drone-telegram
|
||||
depends_on:
|
||||
- release-latest
|
||||
settings:
|
||||
token:
|
||||
from_secret: TELEGRAM_TOKEN
|
||||
to:
|
||||
from_secret: TELEGRAM_TO
|
||||
message: >
|
||||
{{repo.owner}}/{{repo.name}}: \[{{build.status}}] Build {{build.number}}
|
||||
{{commit.author}} pushed to {{commit.branch}} {{commit.sha}}: `{{commit.message}}`
|
||||
Build started at {{datetime build.started "2006-Jan-02T15:04:05Z" "GMT+2"}} finished at {{datetime build.finished "2006-Jan-02T15:04:05Z" "GMT+2"}}.
|
||||
when:
|
||||
status:
|
||||
- success
|
||||
- failure
|
||||
---
|
||||
########
|
||||
# Build a release when tagging
|
||||
# We need to copy this because drone currently can't run a pipeline on either a push to master or a tag.
|
||||
########
|
||||
|
||||
kind: pipeline
|
||||
name: deploy-version
|
||||
depends_on:
|
||||
- testing
|
||||
|
||||
workspace:
|
||||
base: /go
|
||||
path: src/code.vikunja.io/api
|
||||
|
||||
trigger:
|
||||
event:
|
||||
- tag
|
||||
|
||||
steps:
|
||||
- name: fetch-tags
|
||||
image: docker:git
|
||||
commands:
|
||||
- git fetch --tags
|
||||
|
||||
- name: before-static-build
|
||||
image: techknowlogick/xgo:latest
|
||||
pull: true
|
||||
commands:
|
||||
- make generate
|
||||
- make release-dirs
|
||||
depends_on: [ fetch-tags ]
|
||||
|
||||
- name: static-build-windows
|
||||
image: techknowlogick/xgo:latest
|
||||
pull: true
|
||||
environment:
|
||||
# This path does not exist. However, when we set the gopath to /go, the build fails. Not sure why.
|
||||
# Leaving this here until we know how to resolve this properly.
|
||||
GOPATH: /srv/app
|
||||
commands:
|
||||
- export PATH=$PATH:$GOPATH/bin
|
||||
- make release-windows
|
||||
depends_on: [ before-static-build ]
|
||||
|
||||
- name: static-build-linux
|
||||
image: techknowlogick/xgo:latest
|
||||
pull: true
|
||||
environment:
|
||||
# This path does not exist. However, when we set the gopath to /go, the build fails. Not sure why.
|
||||
# Leaving this here until we know how to resolve this properly.
|
||||
GOPATH: /srv/app
|
||||
commands:
|
||||
- export PATH=$PATH:$GOPATH/bin
|
||||
- make release-linux
|
||||
depends_on: [ before-static-build ]
|
||||
|
||||
- name: static-build-darwin
|
||||
image: techknowlogick/xgo:latest
|
||||
pull: true
|
||||
environment:
|
||||
# This path does not exist. However, when we set the gopath to /go, the build fails. Not sure why.
|
||||
# Leaving this here until we know how to resolve this properly.
|
||||
GOPATH: /srv/app
|
||||
commands:
|
||||
- export PATH=$PATH:$GOPATH/bin
|
||||
- make release-darwin
|
||||
depends_on: [ before-static-build ]
|
||||
|
||||
- name: after-build-compress
|
||||
image: kolaente/upx
|
||||
pull: true
|
||||
depends_on:
|
||||
- static-build-windows
|
||||
- static-build-linux
|
||||
- static-build-darwin
|
||||
commands:
|
||||
- make release-compress
|
||||
|
||||
- name: after-build-static
|
||||
image: techknowlogick/xgo:latest
|
||||
pull: true
|
||||
depends_on:
|
||||
- after-build-compress
|
||||
commands:
|
||||
- make release-copy
|
||||
- make release-check
|
||||
- make release-os-package
|
||||
- make release-zip
|
||||
|
||||
- name: sign-release
|
||||
image: plugins/gpgsign:1
|
||||
pull: true
|
||||
depends_on: [ after-build-static ]
|
||||
settings:
|
||||
key:
|
||||
from_secret: gpg_privkey
|
||||
passphrase:
|
||||
from_secret: gpg_password
|
||||
files:
|
||||
- dist/zip/*
|
||||
detach_sign: true
|
||||
|
||||
# Push the releases to our pseudo-s3-bucket
|
||||
- name: release-version
|
||||
image: plugins/s3:1
|
||||
pull: true
|
||||
settings:
|
||||
bucket: vikunja
|
||||
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: /api/${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
|
||||
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: /deb/
|
||||
depends_on: [ deb-structure ]
|
||||
|
||||
# Build the docker image and push it to docker hub
|
||||
- name: docker
|
||||
image: plugins/docker
|
||||
pull: true
|
||||
settings:
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
repo: vikunja/api
|
||||
auto_tag: true
|
||||
depends_on: [ fetch-tags ]
|
||||
|
||||
- name: telegram
|
||||
image: appleboy/drone-telegram
|
||||
depends_on:
|
||||
- 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: /go
|
||||
path: src/code.vikunja.io/api
|
||||
|
||||
clone:
|
||||
depth: 50
|
||||
|
||||
trigger:
|
||||
event:
|
||||
- push
|
||||
branch:
|
||||
- master
|
||||
|
||||
steps:
|
||||
- name: submodules
|
||||
image: docker:git
|
||||
commands:
|
||||
- git submodule update --init
|
||||
- git submodule update --recursive --remote
|
||||
|
||||
- name: theme
|
||||
image: kolaente/yarn
|
||||
pull: true
|
||||
group: build-static
|
||||
commands:
|
||||
- cd docs/themes/vikunja
|
||||
- yarn --production=false
|
||||
- ./node_modules/.bin/gulp
|
||||
|
||||
- name: build
|
||||
image: monachus/hugo:v0.54.0
|
||||
pull: true
|
||||
commands:
|
||||
- cd docs
|
||||
- hugo
|
||||
- mv public/docs/* public # Hugo seems to be not capable of setting a different theme for a home page, so we do this ugly hack to fix it.
|
||||
|
||||
- name: docker
|
||||
image: plugins/docker
|
||||
pull: true
|
||||
settings:
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
repo: vikunja/docs
|
||||
context: docs/
|
||||
dockerfile: docs/Dockerfile
|
|
@ -0,0 +1,22 @@
|
|||
# EditorConfig is awesome: https://EditorConfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = tab
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = false
|
||||
insert_final_newline = false
|
||||
|
||||
[*.go]
|
||||
indent_style = tab
|
||||
|
||||
[*.{yaml,yml}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.json]
|
||||
indent_style = space
|
||||
indent_size = 4
|
|
@ -1,12 +0,0 @@
|
|||
# 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
|
|
@ -0,0 +1,3 @@
|
|||
github: kolaente
|
||||
open_collective: vikunja
|
||||
custom: ["https://vikunja.cloud", "https://www.buymeacoffee.com/kolaente"]
|
|
@ -0,0 +1,58 @@
|
|||
name: Bug Report
|
||||
description: Found something you weren't expecting? Report it here!
|
||||
labels: kind/bug
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
NOTE: If your issue is a security concern, please send an email to security@vikunja.io instead of opening a public issue.
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Please fill out this issue template to report a bug.
|
||||
|
||||
1. If you want to propose a new feature, please open a discussion thread in the forum: https://community.vikunja.io
|
||||
2. Please ask questions or configuration/deploy problems on our [Matrix Room](https://matrix.to/#/#vikunja:matrix.org) or forum (https://community.vikunja.io).
|
||||
3. Make sure you are using the latest release and
|
||||
take a moment to check that your issue hasn't been reported before.
|
||||
4. Please give all relevant information below for bug reports, because
|
||||
incomplete details will be handled as an invalid report and closed.
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
description: |
|
||||
Please provide a description of your issue here, with a URL if you were able to reproduce the issue (see below).
|
||||
- type: input
|
||||
id: frontend-version
|
||||
attributes:
|
||||
label: Vikunja Frontend Version
|
||||
description: Vikunja frontend version (or commit reference) of your instance
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: api-version
|
||||
attributes:
|
||||
label: Vikunja API Version
|
||||
description: Vikunja API version (or commit reference) of your instance
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: browser-version
|
||||
attributes:
|
||||
label: Browser and version
|
||||
description: If your issue is related to a frontend problem, please provide the browser and version you used to reproduce it.
|
||||
- type: dropdown
|
||||
id: can-reproduce
|
||||
attributes:
|
||||
label: Can you reproduce the bug on the Vikunja demo site?
|
||||
options:
|
||||
- "Yes"
|
||||
- "No"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: screenshots
|
||||
attributes:
|
||||
label: Screenshots
|
||||
description: If this issue involves the Web Interface, please provide one or more screenshots
|
|
@ -0,0 +1,17 @@
|
|||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Frontend issues
|
||||
url: https://code.vikunja.io/frontend/issues
|
||||
about: This is the API repo. Please open frontend-related bug reports and discussions in the frontend repo. Not sure if you issue is frontend or api? Ask in Matrix or the forum first.
|
||||
- name: Forum
|
||||
url: https://community.vikunja.io/
|
||||
about: Feature Requests, Questions, configuration or deployment problems should be discussed in the forum.
|
||||
- name: Security-related issues
|
||||
url: https://vikunja.io/contact/#security
|
||||
about: For security concerns, please send a mail to security@vikunja.io instead of opening a public issue.
|
||||
- name: Chat on Matrix
|
||||
url: https://matrix.to/#/#vikunja:matrix.org
|
||||
about: Please ask any quick questions here.
|
||||
- name: Translations
|
||||
url: https://crowdin.com/project/vikunja
|
||||
about: Any problems or requests for new languages about translations should be handled in crowdin.
|
|
@ -4,6 +4,9 @@
|
|||
config.yml
|
||||
config.yaml
|
||||
!docs/config.yml
|
||||
!.github/ISSUE_TEMPLATE/config.yml
|
||||
!.gitea/ISSUE_TEMPLATE/config.yml
|
||||
docs/themes/
|
||||
*.db
|
||||
Run
|
||||
dist/
|
||||
|
@ -20,3 +23,8 @@ docs/resources/
|
|||
pkg/static/templates_vfsdata.go
|
||||
files/
|
||||
!pkg/files/
|
||||
vikunja-dump*
|
||||
vendor/
|
||||
os-packages/
|
||||
mage_output_file.go
|
||||
pkg/swagger/*
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
[submodule "docs/themes/vikunja"]
|
||||
path = docs/themes/vikunja
|
||||
url = ../theme.git
|
|
@ -0,0 +1,94 @@
|
|||
run:
|
||||
timeout: 15m
|
||||
tests: true
|
||||
|
||||
linters:
|
||||
enable:
|
||||
- megacheck
|
||||
- govet
|
||||
- goconst
|
||||
- gocritic
|
||||
- gocyclo
|
||||
- goerr113
|
||||
- goheader
|
||||
- gofmt
|
||||
- goimports
|
||||
- revive
|
||||
- misspell
|
||||
disable:
|
||||
- scopelint # Obsolete, using exportloopref instead
|
||||
- durationcheck
|
||||
presets:
|
||||
- bugs
|
||||
- unused
|
||||
fast: false
|
||||
|
||||
linters-settings:
|
||||
nestif:
|
||||
min-complexity: 6
|
||||
goheader:
|
||||
template-path: code-header-template.txt
|
||||
|
||||
issues:
|
||||
exclude-rules:
|
||||
# Exclude some linters from running on tests files.
|
||||
- path: _test\.go
|
||||
linters:
|
||||
- gocyclo
|
||||
- deadcode
|
||||
- errorlint
|
||||
- path: pkg/integrations/*
|
||||
linters:
|
||||
- gocyclo
|
||||
- deadcode
|
||||
- varcheck
|
||||
- unparam
|
||||
- bodyclose
|
||||
- path: pkg/integrations/*
|
||||
text: "unlambda"
|
||||
linters:
|
||||
- gocritic
|
||||
- path: pkg/modules/background/unsplash/unsplash\.go
|
||||
linters:
|
||||
- bodyclose
|
||||
- path: pkg/migration/*
|
||||
linters:
|
||||
- exhaustive
|
||||
- goconst
|
||||
- goerr113
|
||||
- path: pkg/models/task_collection_filter\.go
|
||||
linters:
|
||||
- exhaustive
|
||||
- path: pkg/utils/random_string\.go
|
||||
text: "G404:" # We don't care about cryptographically secure randomness when we're using that utility function.
|
||||
linters:
|
||||
- gosec
|
||||
- path: pkg/modules/dump/*
|
||||
linters:
|
||||
- goerr113
|
||||
- path: pkg/
|
||||
text: "err113: do not define dynamic errors, use wrapped static errors instead:"
|
||||
linters:
|
||||
- goerr113
|
||||
- text: "commentFormatting: put a space between `//` and comment text"
|
||||
linters:
|
||||
- gocritic
|
||||
- path: pkg/modules/migration
|
||||
linters:
|
||||
- gocyclo
|
||||
- path: pkg/routes/api/v1/docs.go
|
||||
linters:
|
||||
- goheader
|
||||
- misspell
|
||||
- text: "Missed string"
|
||||
linters:
|
||||
- goheader
|
||||
- path: pkg/.*/error.go
|
||||
linters:
|
||||
- errorlint
|
||||
- path: pkg/models/favorites\.go
|
||||
linters:
|
||||
- nilerr
|
||||
- path: pkg/models/events\.go
|
||||
linters:
|
||||
- musttag
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Launch",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "auto",
|
||||
"program": "${workspaceRoot}",
|
||||
"env": {},
|
||||
"args": []
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"go.testEnvVars": {
|
||||
"VIKUNJA_SERVICE_ROOTPATH": "${workspaceRoot}"
|
||||
}
|
||||
}
|
1638
CHANGELOG.md
1638
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
66
Dockerfile
66
Dockerfile
|
@ -1,45 +1,43 @@
|
|||
# syntax=docker/dockerfile:1
|
||||
# ┬─┐┬ ┐o┬ ┬─┐
|
||||
# │─││ │││ │ │
|
||||
# ┘─┘┘─┘┘┘─┘┘─┘
|
||||
|
||||
##############
|
||||
# Build stage
|
||||
FROM golang:1-alpine AS build-env
|
||||
FROM --platform=$BUILDPLATFORM techknowlogick/xgo:go-1.20.x AS builder
|
||||
|
||||
ARG VIKUNJA_VERSION
|
||||
ENV TAGS "sqlite"
|
||||
ENV GO111MODULE=on
|
||||
ENV GOFLAGS=-mod=vendor
|
||||
RUN go install github.com/magefile/mage@latest && \
|
||||
mv /go/bin/mage /usr/local/go/bin
|
||||
|
||||
# Build deps
|
||||
RUN apk --no-cache add build-base git
|
||||
WORKDIR /go/src/code.vikunja.io/api
|
||||
COPY . ./
|
||||
|
||||
# Setup repo
|
||||
COPY . ${GOPATH}/src/code.vikunja.io/api
|
||||
WORKDIR ${GOPATH}/src/code.vikunja.io/api
|
||||
ARG TARGETOS TARGETARCH TARGETVARIANT
|
||||
|
||||
# Checkout version if set
|
||||
RUN if [ -n "${VIKUNJA_VERSION}" ]; then git checkout "${VIKUNJA_VERSION}"; fi \
|
||||
&& make clean generate build
|
||||
RUN export PATH=$PATH:$GOPATH/bin && \
|
||||
mage build:clean && \
|
||||
mage release:xgo "${TARGETOS}/${TARGETARCH}/${TARGETVARIANT}"
|
||||
|
||||
# ┬─┐┬ ┐┌┐┐┌┐┐┬─┐┬─┐
|
||||
# │┬┘│ │││││││├─ │┬┘
|
||||
# ┘└┘┘─┘┘└┘┘└┘┴─┘┘└┘
|
||||
|
||||
###################
|
||||
# The actual image
|
||||
# Note: I wanted to use the scratch image here, but unfortunatly the go-sqlite bindings require cgo and
|
||||
# because of this, the container would not start when I compiled the image without cgo.
|
||||
FROM alpine:3.11
|
||||
FROM alpine:3.16 AS runner
|
||||
LABEL maintainer="maintainers@vikunja.io"
|
||||
|
||||
WORKDIR /app/vikunja/
|
||||
COPY --from=build-env /go/src/code.vikunja.io/api/vikunja .
|
||||
RUN adduser -S -D vikunja -h /app/vikunja -H \
|
||||
&& chown vikunja -R /app/vikunja
|
||||
ENV VIKUNJA_SERVICE_ROOTPATH=/app/vikunja/
|
||||
|
||||
# Fix time zone settings not working
|
||||
RUN apk --no-cache add tzdata
|
||||
|
||||
# Files permissions
|
||||
RUN mkdir /app/vikunja/files && \
|
||||
chown -R vikunja /app/vikunja/files
|
||||
VOLUME /app/vikunja/files
|
||||
|
||||
USER vikunja
|
||||
CMD ["/app/vikunja/vikunja"]
|
||||
WORKDIR /app/vikunja
|
||||
ENTRYPOINT [ "/sbin/tini", "-g", "--", "/entrypoint.sh" ]
|
||||
EXPOSE 3456
|
||||
|
||||
ENV VIKUNJA_SERVICE_ROOTPATH=/app/vikunja/
|
||||
ENV PUID 1000
|
||||
ENV PGID 1000
|
||||
|
||||
RUN apk --update --no-cache add tzdata tini shadow && \
|
||||
addgroup vikunja && \
|
||||
adduser -s /bin/sh -D -G vikunja vikunja -h /app/vikunja -H
|
||||
COPY docker/entrypoint.sh /entrypoint.sh
|
||||
RUN chmod 0755 /entrypoint.sh && mkdir files
|
||||
|
||||
COPY --from=builder /build/vikunja-* vikunja
|
||||
|
|
143
LICENSE
143
LICENSE
|
@ -1,5 +1,5 @@
|
|||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
|
@ -7,17 +7,15 @@
|
|||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
our General Public Licenses are intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
software for all its users.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
|
@ -26,44 +24,34 @@ them if you wish), that you receive source code or can get it if you
|
|||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing under
|
||||
this license.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
@ -72,7 +60,7 @@ modification follow.
|
|||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
@ -549,35 +537,45 @@ to collect a royalty for further conveying from those to whom you convey
|
|||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your version
|
||||
supports such interaction) an opportunity to receive the Corresponding
|
||||
Source of your version by providing access to the Corresponding Source
|
||||
from a network server at no charge, through some standard or customary
|
||||
means of facilitating copying of software. This Corresponding Source
|
||||
shall include the Corresponding Source for any work covered by version 3
|
||||
of the GNU General Public License that is incorporated pursuant to the
|
||||
following paragraph.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
under version 3 of the GNU General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
but the work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
the GNU Affero General Public License from time to time. Such new versions
|
||||
will be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Program specifies that a certain numbered version of the GNU Affero General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
GNU Affero General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
versions of the GNU Affero General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
|
@ -635,40 +633,29 @@ the "copyright" line and a pointer to where the full notice is found.
|
|||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for the
|
||||
specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
245
Makefile
245
Makefile
|
@ -1,245 +0,0 @@
|
|||
DIST := dist
|
||||
IMPORT := code.vikunja.io/api
|
||||
|
||||
SED_INPLACE := sed -i
|
||||
|
||||
ifeq ($(OS), Windows_NT)
|
||||
EXECUTABLE := vikunja.exe
|
||||
else
|
||||
EXECUTABLE := vikunja
|
||||
UNAME_S := $(shell uname -s)
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
SED_INPLACE := sed -i ''
|
||||
endif
|
||||
endif
|
||||
|
||||
GOFILES := $(shell find . -name "*.go" -type f ! -path "./vendor/*" ! -path "*/bindata.go")
|
||||
GOFMT ?= gofmt -s
|
||||
|
||||
GOFLAGS := -v -mod=vendor
|
||||
EXTRA_GOFLAGS ?=
|
||||
|
||||
LDFLAGS := -X "code.vikunja.io/api/pkg/version.Version=$(shell git describe --tags --always --abbrev=10 | sed 's/-/+/' | sed 's/^v//' | sed 's/-g/-/')" -X "main.Tags=$(TAGS)" -X "code.vikunja.io/api/pkg/version.BuildTime=$(shell date -R)"
|
||||
|
||||
PACKAGES ?= $(filter-out code.vikunja.io/api/pkg/integrations,$(shell go list -mod=vendor ./... | grep -v /vendor/))
|
||||
SOURCES ?= $(shell find . -name "*.go" -type f)
|
||||
|
||||
TAGS ?=
|
||||
|
||||
ifeq ($(OS), Windows_NT)
|
||||
EXECUTABLE := vikunja.exe
|
||||
else
|
||||
EXECUTABLE := vikunja
|
||||
endif
|
||||
|
||||
ifneq ($(DRONE_TAG),)
|
||||
VERSION ?= $(subst v,,$(DRONE_TAG))
|
||||
else
|
||||
ifneq ($(DRONE_BRANCH),)
|
||||
VERSION ?= $(subst release/v,,$(DRONE_BRANCH))
|
||||
else
|
||||
VERSION ?= master
|
||||
endif
|
||||
endif
|
||||
|
||||
ifeq ($(DRONE_WORKSPACE),'')
|
||||
BINLOCATION := $(EXECUTABLE)
|
||||
else
|
||||
BINLOCATION := $(DIST)/binaries/$(EXECUTABLE)-$(VERSION)-linux-amd64
|
||||
endif
|
||||
|
||||
ifeq ($(VERSION),master)
|
||||
PKGVERSION := $(shell git describe --tags --always --abbrev=10 | sed 's/-/+/' | sed 's/^v//' | sed 's/-g/-/')
|
||||
else
|
||||
PKGVERSION := $(VERSION)
|
||||
endif
|
||||
|
||||
.PHONY: all
|
||||
all: build
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
go clean ./...
|
||||
rm -rf $(EXECUTABLE) $(DIST) $(BINDATA)
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
# We run everything sequentially and not in parallel to prevent issues with real test databases
|
||||
VIKUNJA_SERVICE_ROOTPATH=$(shell pwd) go test $(GOFLAGS) -p 1 -cover -coverprofile cover.out $(PACKAGES)
|
||||
|
||||
.PHONY: test-coverage
|
||||
test-coverage: test
|
||||
go tool cover -html=cover.out -o cover.html
|
||||
|
||||
.PHONY: integration-test
|
||||
integration-test:
|
||||
# We run everything sequentially and not in parallel to prevent issues with real test databases
|
||||
VIKUNJA_SERVICE_ROOTPATH=$(shell pwd) go test $(GOFLAGS) -p 1 code.vikunja.io/api/pkg/integrations
|
||||
|
||||
.PHONY: lint
|
||||
lint:
|
||||
@hash golint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
go install $(GOFLAGS) golang.org/x/lint/golint; \
|
||||
fi
|
||||
for PKG in $(PACKAGES); do golint -set_exit_status $$PKG || exit 1; done;
|
||||
|
||||
.PHONY: fmt
|
||||
fmt:
|
||||
$(GOFMT) -w $(GOFILES)
|
||||
|
||||
.PHONY: fmt-check
|
||||
fmt-check:
|
||||
# get all go files and run go fmt on them
|
||||
@diff=$$($(GOFMT) -d $(GOFILES)); \
|
||||
if [ -n "$$diff" ]; then \
|
||||
echo "Please run 'make fmt' and commit the result:"; \
|
||||
echo "$${diff}"; \
|
||||
exit 1; \
|
||||
fi;
|
||||
|
||||
.PHONY: build
|
||||
build: generate $(EXECUTABLE)
|
||||
|
||||
.PHONY: generate
|
||||
generate:
|
||||
go generate code.vikunja.io/api/pkg/static
|
||||
|
||||
$(EXECUTABLE): $(SOURCES)
|
||||
go build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)' -o $@
|
||||
|
||||
.PHONY: compress-build
|
||||
compress-build:
|
||||
upx -9 $(EXECUTABLE)
|
||||
|
||||
.PHONY: release
|
||||
release: release-dirs release-windows release-linux release-darwin release-copy release-check release-os-package release-zip
|
||||
|
||||
.PHONY: release-dirs
|
||||
release-dirs:
|
||||
mkdir -p $(DIST)/binaries $(DIST)/release $(DIST)/zip
|
||||
|
||||
.PHONY: release-windows
|
||||
release-windows:
|
||||
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
go install $(GOFLAGS) src.techknowlogick.com/xgo; \
|
||||
fi
|
||||
xgo -dest $(DIST)/binaries -tags 'netgo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out vikunja-$(VERSION) .
|
||||
ifneq ($(DRONE_WORKSPACE),'')
|
||||
mv /build/* $(DIST)/binaries
|
||||
endif
|
||||
|
||||
.PHONY: release-linux
|
||||
release-linux:
|
||||
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
go install $(GOFLAGS) src.techknowlogick.com/xgo; \
|
||||
fi
|
||||
xgo -dest $(DIST)/binaries -tags 'netgo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'linux/*' -out vikunja-$(VERSION) .
|
||||
ifneq ($(DRONE_WORKSPACE),'')
|
||||
mv /build/* $(DIST)/binaries
|
||||
endif
|
||||
|
||||
.PHONY: release-darwin
|
||||
release-darwin:
|
||||
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
go install $(GOFLAGS) src.techknowlogick.com/xgo; \
|
||||
fi
|
||||
xgo -dest $(DIST)/binaries -tags 'netgo $(TAGS)' -ldflags '$(LDFLAGS)' -targets 'darwin/*' -out vikunja-$(VERSION) .
|
||||
ifneq ($(DRONE_WORKSPACE),'')
|
||||
mv /build/* $(DIST)/binaries
|
||||
endif
|
||||
|
||||
# Compresses all releases made by make release-* but not mips* releases since upx can't handle these.
|
||||
.PHONY: release-compress
|
||||
release-compress:
|
||||
$(foreach file,$(filter-out $(wildcard $(wildcard $(DIST)/binaries/$(EXECUTABLE)-*mips*)),$(wildcard $(DIST)/binaries/$(EXECUTABLE)-*)), upx -9 $(file);)
|
||||
|
||||
.PHONY: release-copy
|
||||
release-copy:
|
||||
$(foreach file,$(wildcard $(DIST)/binaries/$(EXECUTABLE)-*),cp $(file) $(DIST)/release/$(notdir $(file));)
|
||||
|
||||
.PHONY: release-check
|
||||
release-check:
|
||||
cd $(DIST)/release; $(foreach file,$(wildcard $(DIST)/release/$(EXECUTABLE)-*),sha256sum $(notdir $(file)) > $(notdir $(file)).sha256;)
|
||||
|
||||
.PHONY: release-os-package
|
||||
release-os-package:
|
||||
$(foreach file,$(filter-out %.sha256,$(wildcard $(DIST)/release/$(EXECUTABLE)-*)),mkdir $(file)-full;mv $(file) $(file)-full/; mv $(file).sha256 $(file)-full/; cp config.yml.sample $(file)-full/config.yml; cp LICENSE $(file)-full/; )
|
||||
|
||||
.PHONY: release-zip
|
||||
release-zip:
|
||||
$(foreach file,$(wildcard $(DIST)/release/$(EXECUTABLE)-*),cd $(file); zip -r ../../zip/$(shell basename $(file)).zip *; cd ../../../; )
|
||||
|
||||
# Builds a deb package using fpm from a previously created binary (using make build)
|
||||
.PHONY: build-deb
|
||||
build-deb:
|
||||
fpm -s dir -t deb --url https://vikunja.io -n vikunja -v $(PKGVERSION) --license GPLv3 --directories /opt/vikunja --after-install ./build/after-install.sh --description 'Vikunja is an open-source todo application, written in Go. It lets you create lists,tasks and share them via teams or directly between users.' -m maintainers@vikunja.io ./$(BINLOCATION)=/opt/vikunja/vikunja ./config.yml.sample=/etc/vikunja/config.yml;
|
||||
|
||||
.PHONY: reprepro
|
||||
reprepro:
|
||||
reprepro_expect debian includedeb strech ./$(EXECUTABLE)_$(PKGVERSION)_amd64.deb
|
||||
|
||||
.PHONY: got-swag
|
||||
got-swag: do-the-swag
|
||||
@diff=$$(git diff docs/swagger/swagger.json); \
|
||||
if [ -n "$$diff" ]; then \
|
||||
echo "Please run 'make do-the-swag' and commit the result:"; \
|
||||
echo "$${diff}"; \
|
||||
exit 1; \
|
||||
fi;
|
||||
|
||||
.PHONY: do-the-swag
|
||||
do-the-swag:
|
||||
@hash swag > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
go install $(GOFLAGS) github.com/swaggo/swag/cmd/swag; \
|
||||
fi
|
||||
swag init -g pkg/routes/routes.go -o ./pkg/swagger;
|
||||
# Fix the generated swagger file, currently a workaround until swaggo can properly use go mod
|
||||
sed -i '/"definitions": {/a "code.vikunja.io.web.HTTPError": {"type": "object","properties": {"code": {"type": "integer"},"message": {"type": "string"}}},' pkg/swagger/docs.go;
|
||||
sed -i 's/code.vikunja.io\/web.HTTPError/code.vikunja.io.web.HTTPError/g' pkg/swagger/docs.go;
|
||||
sed -i 's/package\ docs/package\ swagger/g' pkg/swagger/docs.go;
|
||||
sed -i 's/` + \\"`\\" + `/` + "`" + `/g' pkg/swagger/docs.go;
|
||||
|
||||
.PHONY: misspell-check
|
||||
misspell-check:
|
||||
@hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
go install $(GOFLAGS) github.com/client9/misspell/cmd/misspell; \
|
||||
fi
|
||||
for S in $(GOFILES); do misspell -error $$S || exit 1; done;
|
||||
|
||||
.PHONY: ineffassign-check
|
||||
ineffassign-check:
|
||||
@hash ineffassign > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
go install $(GOFLAGS) github.com/gordonklaus/ineffassign; \
|
||||
fi
|
||||
for S in $(GOFILES); do ineffassign $$S || exit 1; done;
|
||||
|
||||
.PHONY: gocyclo-check
|
||||
gocyclo-check:
|
||||
@hash gocyclo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
go get -u github.com/fzipp/gocyclo; \
|
||||
go install $(GOFLAGS) github.com/fzipp/gocyclo; \
|
||||
fi
|
||||
for S in $(GOFILES); do gocyclo -over 24 $$S || exit 1; done;
|
||||
|
||||
.PHONY: static-check
|
||||
static-check:
|
||||
@hash staticcheck > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
go get -u honnef.co/go/tools; \
|
||||
go install $(GOFLAGS) honnef.co/go/tools/cmd/staticcheck; \
|
||||
fi
|
||||
staticcheck $(PACKAGES);
|
||||
|
||||
.PHONY: gosec-check
|
||||
gosec-check:
|
||||
@hash ./bin/gosec > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
curl -sfL https://raw.githubusercontent.com/securego/gosec/master/install.sh | sh -s 1.2.0; \
|
||||
fi
|
||||
for S in $(PACKAGES); do ./bin/gosec $$S || exit 1; done;
|
||||
|
||||
.PHONY: goconst-check
|
||||
goconst-check:
|
||||
@hash goconst > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
|
||||
go get -u github.com/jgautheron/goconst/cmd/goconst; \
|
||||
go install $(GOFLAGS) github.com/jgautheron/goconst/cmd/goconst; \
|
||||
fi
|
||||
for S in $(PACKAGES); do goconst $$S || exit 1; done;
|
53
README.md
53
README.md
|
@ -1,11 +1,11 @@
|
|||
<img src="https://vikunja.io/images/vikunja-logo.svg" alt="" style="display: block;width: 50%;margin: 0 auto;" width="50%"/>
|
||||
|
||||
[![Build Status](https://drone1.kolaente.de/api/badges/vikunja/api/status.svg)](https://drone1.kolaente.de/vikunja/api)
|
||||
[![License: GPL v3](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](LICENSE)
|
||||
[![Download](https://img.shields.io/badge/download-v0.12-brightgreen.svg)](https://dl.vikunja.io)
|
||||
[![Build Status](https://drone.kolaente.de/api/badges/vikunja/api/status.svg)](https://drone.kolaente.de/vikunja/api)
|
||||
[![License: AGPL v3](https://img.shields.io/badge/License-AGPL%20v3-blue.svg)](LICENSE)
|
||||
[![Download](https://img.shields.io/badge/download-v0.20.4-brightgreen.svg)](https://dl.vikunja.io)
|
||||
[![Docker Pulls](https://img.shields.io/docker/pulls/vikunja/api.svg)](https://hub.docker.com/r/vikunja/api/)
|
||||
[![Swagger Docs](https://img.shields.io/badge/swagger-docs-brightgreen.svg)](https://try.vikunja.io/api/v1/docs)
|
||||
[![Go Report Card](https://goreportcard.com/badge/git.kolaente.de/vikunja/api)](https://goreportcard.com/report/git.kolaente.de/vikunja/api)
|
||||
[![Go Report Card](https://goreportcard.com/badge/kolaente.dev/vikunja/api)](https://goreportcard.com/report/kolaente.dev/vikunja/api)
|
||||
|
||||
# Vikunja API
|
||||
|
||||
|
@ -13,17 +13,22 @@
|
|||
|
||||
# Table of contents
|
||||
|
||||
* [Security Reports](#security-reports)
|
||||
* [Features](#features)
|
||||
* [Docs](#docs)
|
||||
* [Roadmap](#roadmap)
|
||||
* [Contributing](#contributing)
|
||||
* [License](#license)
|
||||
|
||||
## Security Reports
|
||||
|
||||
If you find any security-related issues you don't want to disclose publicly, please use [the contact information on our website](https://vikunja.io/contact/#security).
|
||||
|
||||
## Features
|
||||
|
||||
* Create TODO lists with tasks
|
||||
* Reminder for tasks
|
||||
* Namespaces: A "group" which bundels multiple lists
|
||||
* Namespaces: A "group" which bundles multiple lists
|
||||
* Share lists and namespaces with teams and users with granular permissions
|
||||
* Plenty of details for tasks
|
||||
|
||||
|
@ -35,46 +40,22 @@ try it on [try.vikunja.io](https://try.vikunja.io)!
|
|||
* [Installing](https://vikunja.io/docs/installing/)
|
||||
* [Build from source](https://vikunja.io/docs/build-from-sources/)
|
||||
* [Development setup](https://vikunja.io/docs/development/)
|
||||
* [Makefile](https://vikunja.io/docs/makefile/)
|
||||
* [Magefile](https://vikunja.io/docs/magefile/)
|
||||
* [Testing](https://vikunja.io/docs/testing/)
|
||||
|
||||
All docs can be found on [the vikunja home page](https://vikunja.io/docs/).
|
||||
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.
|
||||
See [the roadmap](https://my.vikunja.cloud/share/QFyzYEmEYfSyQfTOmIRSwLUpkFjboaBqQCnaPmWd/auth) (hosted on Vikunja!) for more!
|
||||
|
||||
* [x] Prioritize tasks
|
||||
* [x] Subtasks
|
||||
* [x] Repeating tasks
|
||||
* [x] Get tasks via caldav
|
||||
* [x] Get all your tasks for an interval (day/month/period)
|
||||
* [x] Labels for tasks
|
||||
* [x] Assign users to tasks
|
||||
* [x] Attachments on tasks
|
||||
* [x] More sharing features
|
||||
* [x] Share with individual users
|
||||
* [x] Share via a world-readable link with or without password, like Nextcloud
|
||||
* [x] Disable registration, making an instance "invite-only"
|
||||
* [ ] SSE to notify multiple clients of updates when something was changed
|
||||
* [ ] "Smart Lists" - Create lists based on filters
|
||||
* [ ] IMAP-Integration - Send an email to Vikunja to create a new task
|
||||
* [ ] Webhooks - Trigger other events when an action is done (like completing a task)
|
||||
* [ ] Performace statistics - Get an overview and beautiful charts about what you got done this month
|
||||
* [ ] Activity feeds - Get a quick overview about who did what
|
||||
* [ ] Bulk-edit multiple tasks at once
|
||||
* [ ] Team-efforts - Requiring a task to be marked as done by multiple members until it's done
|
||||
* [ ] Global limits for namespaces/lists/tasks
|
||||
|
||||
See [our roadmap](https://my.vikunja.cloud/share/QFyzYEmEYfSyQfTOmIRSwLUpkFjboaBqQCnaPmWd/auth) (hosted on Vikunja!) for even more!
|
||||
|
||||
* [ ] [Mobile apps](https://code.vikunja.io/app) (seperate repo) *In Progress*
|
||||
* [ ] [Webapp](https://code.vikunja.io/frontend) (seperate repo) *In Progress*
|
||||
* [ ] [Mobile apps](https://code.vikunja.io/app) (separate repo) *In Progress*
|
||||
* [ ] [Webapp](https://code.vikunja.io/frontend) (separate repo) *In Progress*
|
||||
|
||||
## Contributing
|
||||
|
||||
Fork -> Push -> Pull-Request. Also see the [dev docs](https://vikunja.io/docs/development/) for more infos.
|
||||
Fork -> Push -> Pull-Request. Also see the [dev docs](https://vikunja.io/docs/development/) for more info.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the GPLv3 License. See the [LICENSE](LICENSE) file for the full license text.
|
||||
This project is licensed under the AGPLv3 License. See the [LICENSE](LICENSE) file for the full license text.
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#!/bin/bash
|
||||
ln -s /opt/vikunja/vikunja /usr/bin/vikunja
|
||||
|
||||
systemctl enable vikunja.service
|
||||
|
||||
# Fix the config to contain proper values
|
||||
NEW_SECRET=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1)
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
Origin: dl.vikunja.io
|
||||
Label: Vikunja
|
||||
Codename: strech
|
||||
Codename: buster
|
||||
Architectures: amd64
|
||||
Components: main
|
||||
Description: The debian repo for Vikunja builds.
|
||||
SignWith: yes
|
||||
Pull: strech
|
||||
Pull: buster
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
[changelog]
|
||||
body = """
|
||||
{% if version %}\
|
||||
## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }}
|
||||
{% else %}\
|
||||
## [unreleased]
|
||||
{% endif %}\
|
||||
|
||||
|
||||
{% for group, commits in commits | group_by(attribute="group") %}
|
||||
### {{ group | upper_first }}
|
||||
{% for commit in commits
|
||||
| filter(attribute="scope")
|
||||
| sort(attribute="scope") %}
|
||||
* *({{commit.scope}})* {{ commit.message | upper_first }}
|
||||
{%- if commit.breaking %}
|
||||
{% raw %} {% endraw %}- **BREAKING**: {{commit.breaking_description}}
|
||||
{%- endif -%}
|
||||
{%- endfor -%}
|
||||
{%- for commit in commits %}
|
||||
{%- if commit.scope -%}
|
||||
{% else -%}
|
||||
* {{ commit.message | upper_first }} ([{{ commit.id | truncate(length=7, end="") }}]({{ commit.id }}))
|
||||
{% if commit.breaking -%}
|
||||
{% raw %} {% endraw %}- **BREAKING**: {{commit.breaking_description}}
|
||||
{% endif -%}
|
||||
{% endif -%}
|
||||
{% endfor -%}
|
||||
{% raw %}\n{% endraw %}\
|
||||
{% endfor %}\n
|
||||
|
||||
"""
|
||||
#{% for group, commits in commits | group_by(attribute="group") %}
|
||||
# ### {{ group | upper_first }}
|
||||
# {% for commit in commits %}\
|
||||
# - {% if commit.breaking %}[**breaking**] {% endif %}{{ commit.message | upper_first }} ([{{ commit.id | truncate(length=7, end="") }}]({{ commit.id }}))
|
||||
# {% endfor %}\
|
||||
#{% endfor %}\n
|
||||
# remove the leading and trailing whitespace from the template
|
||||
trim = true
|
||||
|
||||
[git]
|
||||
conventional_commits = true
|
||||
filter_unconventional = false
|
||||
commit_parsers = [
|
||||
{ message = ".*(deps).*", group = "Dependencies"},
|
||||
{ message = "^feat", group = "Features"},
|
||||
{ message = "^fix", group = "Bug Fixes"},
|
||||
{ message = "^doc", group = "Documentation"},
|
||||
{ message = "^perf", group = "Performance"},
|
||||
{ message = "^refactor", group = "Refactor"},
|
||||
{ message = "^style", group = "Styling"},
|
||||
{ message = "^test", group = "Testing"},
|
||||
{ message = "^chore\\(release\\): prepare for", skip = true},
|
||||
{ message = "^chore", group = "Miscellaneous Tasks"},
|
||||
{ body = ".*security", group = "Security"},
|
||||
{ message = ".*", group = "Other", default_scope = "other"}, # Everything that's not a conventional commit goes into the "Other" category
|
||||
]
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
Vikunja is a to-do list application to facilitate your life.
|
||||
Copyright 2018-2021 Vikunja and contributors. All rights reserved.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public Licensee as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public Licensee for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public Licensee
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
@ -3,62 +3,101 @@ service:
|
|||
# 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: "<jwt-secret>"
|
||||
# The duration of the issued JWT tokens in seconds.
|
||||
# The default is 259200 seconds (3 Days).
|
||||
jwtttl: 259200
|
||||
# The duration of the "remember me" time in seconds. When the login request is made with
|
||||
# the long param set, the token returned will be valid for this period.
|
||||
# The default is 2592000 seconds (30 Days).
|
||||
jwtttllong: 2592000
|
||||
# The interface on which to run the webserver
|
||||
interface: ":3456"
|
||||
# Path to Unix socket. If set, it will be created and used instead of tcp
|
||||
unixsocket:
|
||||
# Permission bits for the Unix socket. Note that octal values must be prefixed by "0o", e.g. 0o660
|
||||
unixsocketmode:
|
||||
# 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>
|
||||
# Path on the file system to serve static files from. Set to the path of the frontend files to host frontend alongside the api.
|
||||
staticpath: ""
|
||||
# The max number of items which can be returned per page
|
||||
maxitemsperpage: 50
|
||||
# If set to true, enables a /metrics endpoint for prometheus to collect metrics about the system
|
||||
# You'll need to use redis for this in order to enable common metrics over multiple nodes
|
||||
enablemetrics: false
|
||||
# Enable the caldav endpoint, see the docs for more details
|
||||
enablecaldav: true
|
||||
# Set the motd message, available from the /info endpoint
|
||||
motd: ""
|
||||
# Enable sharing of lists via a link
|
||||
# Enable sharing of project via a link
|
||||
enablelinksharing: true
|
||||
# Whether to let new users registering themselves or not
|
||||
enableregistration: true
|
||||
# Whether to enable task attachments or not
|
||||
enabletaskattachments: true
|
||||
# The time zone all timestamps are in
|
||||
# The time zone all timestamps are in. Please note that time zones have to use [the official tz database names](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones). UTC or GMT offsets won't work.
|
||||
timezone: GMT
|
||||
# Whether task comments should be enabled or not
|
||||
enabletaskcomments: true
|
||||
# Whether totp is enabled. In most cases you want to leave that enabled.
|
||||
enabletotp: true
|
||||
# If not empty, enables logging of crashes and unhandled errors in sentry.
|
||||
sentrydsn: ''
|
||||
# If not empty, this will enable `/test/{table}` endpoints which allow to put any content in the database.
|
||||
# Used to reset the db before frontend tests. Because this is quite a dangerous feature allowing for lots of harm,
|
||||
# each request made to this endpoint needs to provide an `Authorization: <token>` header with the token from below. <br/>
|
||||
# **You should never use this unless you know exactly what you're doing**
|
||||
testingtoken: ''
|
||||
# If enabled, vikunja will send an email to everyone who is either assigned to a task or created it when a task reminder
|
||||
# is due.
|
||||
enableemailreminders: true
|
||||
# If true, will allow users to request the complete deletion of their account. When using external authentication methods
|
||||
# it may be required to coordinate with them in order to delete the account. This setting will not affect the cli commands
|
||||
# for user deletion.
|
||||
enableuserdeletion: true
|
||||
# The maximum size clients will be able to request for user avatars.
|
||||
# If clients request a size bigger than this, it will be changed on the fly.
|
||||
maxavatarsize: 1024
|
||||
|
||||
database:
|
||||
# Database type to use. Supported types are mysql, postgres and sqlite.
|
||||
type: "sqlite"
|
||||
# Database user which is used to connect to the database.
|
||||
user: "vikunja"
|
||||
# Databse password
|
||||
# Database password
|
||||
password: ""
|
||||
# Databse host
|
||||
# Database host
|
||||
host: "localhost"
|
||||
# Databse to use
|
||||
# Database to use
|
||||
database: "vikunja"
|
||||
# When using sqlite, this is the path where to store the data
|
||||
Path: "./vikunja.db"
|
||||
path: "./vikunja.db"
|
||||
# Sets the max open connections to the database. Only used when using mysql and postgres.
|
||||
maxopenconnections: 100
|
||||
# Sets the maximum number of idle connections to the db.
|
||||
maxidleconnections: 50
|
||||
# The maximum lifetime of a single db connection in miliseconds.
|
||||
# The maximum lifetime of a single db connection in milliseconds.
|
||||
maxconnectionlifetime: 10000
|
||||
# Secure connection mode. Only used with postgres.
|
||||
# (see https://pkg.go.dev/github.com/lib/pq?tab=doc#hdr-Connection_String_Parameters)
|
||||
sslmode: disable
|
||||
# The path to the client cert. Only used with postgres.
|
||||
sslcert: ""
|
||||
# The path to the client key. Only used with postgres.
|
||||
sslkey: ""
|
||||
# The path to the ca cert. Only used with postgres.
|
||||
sslrootcert: ""
|
||||
# Enable SSL/TLS for mysql connections. Options: false, true, skip-verify, preferred
|
||||
tls: false
|
||||
|
||||
cache:
|
||||
# If cache is enabled or not
|
||||
enabled: false
|
||||
# Cache type. Possible values are memory or redis, you'll need to enable redis below when using redis
|
||||
type: memory
|
||||
# Cache type. Possible values are "keyvalue", "memory" or "redis".
|
||||
# When choosing "keyvalue" this setting follows the one configured in the "keyvalue" section.
|
||||
# When choosing "redis" you will need to configure the redis connection separately.
|
||||
type: keyvalue
|
||||
# When using memory this defines the maximum size an element can take
|
||||
maxelementsize: 1000
|
||||
|
||||
|
@ -67,15 +106,17 @@ redis:
|
|||
enabled: false
|
||||
# The host of the redis server including its port.
|
||||
host: 'localhost:6379'
|
||||
# The password used to authenicate against the redis server
|
||||
# The password used to authenticate against the redis server
|
||||
password: ''
|
||||
# 0 means default database
|
||||
db: 0
|
||||
|
||||
cors:
|
||||
# Whether to enable or disable cors headers.
|
||||
# Note: If you want to put the frontend and the api on separate domains or ports, you will need to enable this.
|
||||
# Otherwise the frontend won't be able to make requests to the api through the browser.
|
||||
enable: true
|
||||
# A list of origins which may access the api.
|
||||
# A list of origins which may access the api. These need to include the protocol (`http://` or `https://`) and port, if any.
|
||||
origins:
|
||||
- "*"
|
||||
# How long (in seconds) the results of a preflight request can be cached.
|
||||
|
@ -86,8 +127,11 @@ mailer:
|
|||
enabled: false
|
||||
# SMTP Host
|
||||
host: ""
|
||||
# SMTP Host port
|
||||
# SMTP Host port.
|
||||
# **NOTE:** If you're unable to send mail and the only error you see in the logs is an `EOF`, try setting the port to `25`.
|
||||
port: 587
|
||||
# SMTP Auth Type. Can be either `plain`, `login` or `cram-md5`.
|
||||
authtype: "plain"
|
||||
# SMTP username
|
||||
username: "user"
|
||||
# SMTP password
|
||||
|
@ -100,24 +144,34 @@ mailer:
|
|||
queuelength: 100
|
||||
# The timeout in seconds after which the current open connection to the mailserver will be closed.
|
||||
queuetimeout: 30
|
||||
# By default, vikunja will try to connect with starttls, use this option to force it to use ssl.
|
||||
forcessl: false
|
||||
|
||||
log:
|
||||
# A folder where all the logfiles should go.
|
||||
path: <rootpath>logs
|
||||
# Whether to show any logging at all or none
|
||||
enabled: true
|
||||
# Where the error log should go. Possible values are stdout, stderr, file or off to disable error logging.
|
||||
errors: "stdout"
|
||||
# Where the normal log should go. Possible values are stdout, stderr, file or off to disable standard logging.
|
||||
standard: "stdout"
|
||||
# Change the log level. Possible values (case-insensitive) are CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG.
|
||||
level: "INFO"
|
||||
# Whether or not to log database queries. Useful for debugging. Possible values are stdout, stderr, file or off to disable database logging.
|
||||
database: "off"
|
||||
# The log level for database log messages. Possible values are CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG.
|
||||
databaselevel: "DEBUG"
|
||||
# The log level for database log messages. Possible values (case-insensitive) are CRITICAL, ERROR, WARNING, NOTICE, INFO, DEBUG.
|
||||
databaselevel: "WARNING"
|
||||
# Whether to log http requests or not. Possible values are stdout, stderr, file or off to disable http logging.
|
||||
http: "stdout"
|
||||
# Echo has its own logging which usually is unnessecary, which is why it is disabled by default. Possible values are stdout, stderr, file or off to disable standard logging.
|
||||
# Echo has its own logging which usually is unnecessary, which is why it is disabled by default. Possible values are stdout, stderr, file or off to disable standard logging.
|
||||
echo: "off"
|
||||
# Whether or not to log events. Useful for debugging. Possible values are stdout, stderr, file or off to disable events logging.
|
||||
events: "off"
|
||||
# The log level for event log messages. Possible values (case-insensitive) are ERROR, INFO, DEBUG.
|
||||
eventslevel: "info"
|
||||
# Whether or not to log mail log messages. This will not log mail contents. Possible values are stdout, stderr, file or off to disable mail-related logging.
|
||||
mail: "off"
|
||||
# The log level for mail log messages. Possible values (case-insensitive) are ERROR, WARNING, INFO, DEBUG.
|
||||
maillevel: "info"
|
||||
|
||||
ratelimit:
|
||||
# whether or not to enable the rate limit
|
||||
|
@ -128,8 +182,10 @@ ratelimit:
|
|||
period: 60
|
||||
# The max number of requests a user is allowed to do in the configured time period
|
||||
limit: 100
|
||||
# The store where the limit counter for each user is stored. Possible values are "memory" or "redis"
|
||||
store: memory
|
||||
# The store where the limit counter for each user is stored.
|
||||
# Possible values are "keyvalue", "memory" or "redis".
|
||||
# When choosing "keyvalue" this setting follows the one configured in the "keyvalue" section.
|
||||
store: keyvalue
|
||||
|
||||
files:
|
||||
# The path where files are stored
|
||||
|
@ -139,26 +195,149 @@ files:
|
|||
maxsize: 20MB
|
||||
|
||||
migration:
|
||||
# These are the settings for the wunderlist migrator
|
||||
wunderlist:
|
||||
# Wheter to enable the wunderlist migrator or not
|
||||
todoist:
|
||||
# Wheter to enable the todoist migrator or not
|
||||
enable: false
|
||||
# The client id, required for making requests to the wunderlist api
|
||||
# You need to register your vikunja instance at https://developer.wunderlist.com/apps/new to get this
|
||||
# The client id, required for making requests to the todoist api
|
||||
# You need to register your vikunja instance at https://developer.todoist.com/appconsole.html to get this
|
||||
clientid:
|
||||
# The client secret, also required for making requests to the wunderlist api
|
||||
# The client secret, also required for making requests to the todoist api
|
||||
clientsecret:
|
||||
# The url where clients are redirected after they authorized Vikunja to access their wunderlist stuff.
|
||||
# This needs to match the url you entered when registering your Vikunja instance at wunderlist.
|
||||
# This is usually the frontend url where the frontend then makes a request to /migration/wunderlist/migrate
|
||||
# with the code obtained from the wunderlist api.
|
||||
# Note that the vikunja frontend expects this to be /migrate/wunderlist
|
||||
redirecturl:
|
||||
# The url where clients are redirected after they authorized Vikunja to access their todoist items.
|
||||
# This needs to match the url you entered when registering your Vikunja instance at todoist.
|
||||
# This is usually the frontend url where the frontend then makes a request to /migration/todoist/migrate
|
||||
# with the code obtained from the todoist api.
|
||||
# Note that the vikunja frontend expects this to be /migrate/todoist
|
||||
redirecturl: <frontend url>/migrate/todoist
|
||||
trello:
|
||||
# Whether to enable the trello migrator or not
|
||||
enable: false
|
||||
# The client id, required for making requests to the trello api
|
||||
# You need to register your vikunja instance at https://trello.com/app-key (log in before you visit that link) to get this
|
||||
key:
|
||||
# The url where clients are redirected after they authorized Vikunja to access their trello cards.
|
||||
# This needs to match the url you entered when registering your Vikunja instance at trello.
|
||||
# This is usually the frontend url where the frontend then makes a request to /migration/trello/migrate
|
||||
# with the code obtained from the trello api.
|
||||
# Note that the vikunja frontend expects this to end on /migrate/trello.
|
||||
redirecturl: <frontend url>/migrate/trello
|
||||
microsofttodo:
|
||||
# Wheter to enable the microsoft todo migrator or not
|
||||
enable: false
|
||||
# The client id, required for making requests to the microsoft graph api
|
||||
# See https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app#register-an-application
|
||||
# for information about how to register your Vikunja instance.
|
||||
clientid:
|
||||
# The client secret, also required for making requests to the microsoft graph api
|
||||
clientsecret:
|
||||
# The url where clients are redirected after they authorized Vikunja to access their microsoft todo tasks.
|
||||
# This needs to match the url you entered when registering your Vikunja instance at microsoft.
|
||||
# This is usually the frontend url where the frontend then makes a request to /migration/microsoft-todo/migrate
|
||||
# with the code obtained from the microsoft graph api.
|
||||
# Note that the vikunja frontend expects this to be /migrate/microsoft-todo
|
||||
redirecturl: <frontend url>/migrate/microsoft-todo
|
||||
|
||||
avatar:
|
||||
# Switch between avatar providers. Possible values are gravatar and default.
|
||||
# gravatar will fetch the avatar based on the user email.
|
||||
# default will return a default avatar for every request.
|
||||
provider: gravatar
|
||||
# When using gravatar, this is the duration in seconds until a cached gravatar user avatar expires
|
||||
gravatarexpiration: 3600
|
||||
|
||||
backgrounds:
|
||||
# Whether to enable backgrounds for projects at all.
|
||||
enabled: true
|
||||
providers:
|
||||
upload:
|
||||
# Whether to enable uploaded project backgrounds
|
||||
enabled: true
|
||||
unsplash:
|
||||
# Whether to enable setting backgrounds from unsplash as project backgrounds
|
||||
enabled: false
|
||||
# You need to create an application for your installation at https://unsplash.com/oauth/applications/new
|
||||
# and set the access token below.
|
||||
accesstoken:
|
||||
# The unsplash application id is only used for pingback and required as per their api guidelines.
|
||||
# You can find the Application ID in the dashboard for your API application. It should be a numeric ID.
|
||||
# It will only show in the UI if your application has been approved for Enterprise usage, therefore if
|
||||
# you’re in Demo mode, you can also find the ID in the URL at the end: https://unsplash.com/oauth/applications/:application_id
|
||||
applicationid:
|
||||
|
||||
# Legal urls
|
||||
# Will be shown in the frontend if configured here
|
||||
legal:
|
||||
imprinturl:
|
||||
privacyurl:
|
||||
|
||||
# Key Value Storage settings
|
||||
# The Key Value Storage is used for different kinds of things like metrics and a few cache systems.
|
||||
keyvalue:
|
||||
# The type of the storage backend. Can be either "memory" or "redis". If "redis" is chosen it needs to be configured separately.
|
||||
type: "memory"
|
||||
|
||||
auth:
|
||||
# Local authentication will let users log in and register (if enabled) through the db.
|
||||
# This is the default auth mechanism and does not require any additional configuration.
|
||||
local:
|
||||
# Enable or disable local authentication
|
||||
enabled: true
|
||||
# OpenID configuration will allow users to authenticate through a third-party OpenID Connect compatible provider.<br/>
|
||||
# The provider needs to support the `openid`, `profile` and `email` scopes.<br/>
|
||||
# **Note:** Some openid providers (like gitlab) only make the email of the user available through openid claims if they have set it to be publicly visible.
|
||||
# If the email is not public in those cases, authenticating will fail.
|
||||
# **Note 2:** The frontend expects to be redirected after authentication by the third party
|
||||
# to <frontend-url>/auth/openid/<auth key>. Please make sure to configure the redirect url with your third party
|
||||
# auth service accordingly if you're using the default vikunja frontend.
|
||||
# Take a look at the [default config file](https://kolaente.dev/vikunja/api/src/branch/main/config.yml.sample) for more information about how to configure openid authentication.
|
||||
openid:
|
||||
# Enable or disable OpenID Connect authentication
|
||||
enabled: false
|
||||
# The url to redirect clients to. Defaults to the configured frontend url. If you're using Vikunja with the official
|
||||
# frontend, you don't need to change this value.
|
||||
# **Note:** The redirect url must exactly match the configured redirect url with the third party provider.
|
||||
# This includes all slashes at the end or protocols.
|
||||
redirecturl: <frontend url>
|
||||
# A list of enabled providers
|
||||
providers:
|
||||
# The name of the provider as it will appear in the frontend.
|
||||
- name:
|
||||
# The auth url to send users to if they want to authenticate using OpenID Connect.
|
||||
authurl:
|
||||
# The oidc logouturl that users will be redirected to on logout.
|
||||
# Leave empty or delete key, if you do not want to be redirected.
|
||||
logouturl:
|
||||
# The client ID used to authenticate Vikunja at the OpenID Connect provider.
|
||||
clientid:
|
||||
# The client secret used to authenticate Vikunja at the OpenID Connect provider.
|
||||
clientsecret:
|
||||
|
||||
# Prometheus metrics endpoint
|
||||
metrics:
|
||||
# If set to true, enables a /metrics endpoint for prometheus to collect metrics about Vikunja. You can query it from `/api/v1/metrics`.
|
||||
enabled: false
|
||||
# If set to a non-empty value the /metrics endpoint will require this as a username via basic auth in combination with the password below.
|
||||
username:
|
||||
# If set to a non-empty value the /metrics endpoint will require this as a password via basic auth in combination with the username below.
|
||||
password:
|
||||
|
||||
# Provide default settings for new users. When a new user is created, these settings will automatically be set for the user. If you change them in the config file afterwards they will not be changed back for existing users.
|
||||
defaultsettings:
|
||||
# The avatar source for the user. Can be `gravatar`, `initials`, `upload` or `marble`. If you set this to `upload` you'll also need to specify `defaultsettings.avatar_file_id`.
|
||||
avatar_provider: initials
|
||||
# The id of the file used as avatar.
|
||||
avatar_file_id: 0
|
||||
# If set to true users will get task reminders via email.
|
||||
email_reminders_enabled: false
|
||||
# If set to true will allow other users to find this user when searching for parts of their name.
|
||||
discoverable_by_name: false
|
||||
# If set to true will allow other users to find this user when searching for their exact email.
|
||||
discoverable_by_email: false
|
||||
# If set to true will send an email every day with all overdue tasks at a configured time.
|
||||
overdue_tasks_reminders_enabled: true
|
||||
# When to send the overdue task reminder email.
|
||||
overdue_tasks_reminders_time: 9:00
|
||||
# The id of the default project. Make sure users actually have access to this project when setting this value.
|
||||
default_project_id: 0
|
||||
# Start of the week for the user. `0` is sunday, `1` is monday and so on.
|
||||
week_start: 0
|
||||
# The language of the user interface. Must be an ISO 639-1 language code. Will default to the browser language the user uses when signing up.
|
||||
language: <unset>
|
||||
# The time zone of each individual user. This will affect when users get reminders and overdue task emails.
|
||||
timezone: <time zone set at service.timezone>
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
#!/usr/bin/env sh
|
||||
set -e
|
||||
|
||||
if [ -n "$PUID" ] && [ "$PUID" -ne 0 ] && \
|
||||
[ -n "$PGID" ] && [ "$PGID" -ne 0 ] ; then
|
||||
echo "info: creating the new user vikunja with $PUID:$PGID"
|
||||
groupmod -g "$PGID" -o vikunja
|
||||
usermod -u "$PUID" -o vikunja
|
||||
chown -R vikunja:vikunja ./files/
|
||||
chown vikunja:vikunja .
|
||||
exec su vikunja -c /app/vikunja/vikunja "$@"
|
||||
else
|
||||
echo "info: creation of non-root user is skipped"
|
||||
exec /app/vikunja/vikunja "$@"
|
||||
fi
|
|
@ -2,7 +2,7 @@ baseurl: https://vikunja.io/docs/
|
|||
title: Vikunja
|
||||
theme: vikunja
|
||||
enableRobotsTXT: true
|
||||
canonifyURLs: true
|
||||
canonifyURLs: false
|
||||
|
||||
pygmentsUseClasses: true
|
||||
|
||||
|
@ -16,24 +16,48 @@ params:
|
|||
description: The to-do app to organize your life
|
||||
author: The Vikunja Authors
|
||||
website: https://vikunja.io
|
||||
fanthomEnabled: false
|
||||
fathomUrl: fathom.kolaente.de
|
||||
fathomSiteID: RYKSD
|
||||
plausibleEnabled: true
|
||||
plausibleDomain: vikunja.io
|
||||
plausibleURL: https://analytics.kolaente.de
|
||||
|
||||
markup:
|
||||
goldmark:
|
||||
renderer:
|
||||
unsafe: true
|
||||
|
||||
menu:
|
||||
page:
|
||||
- name: Home
|
||||
url: https://vikunja.io/en/
|
||||
url: https://vikunja.io/
|
||||
weight: 10
|
||||
- name: Features
|
||||
url: https://vikunja.io/en/features
|
||||
url: https://vikunja.io/features
|
||||
weight: 20
|
||||
- name: Download
|
||||
url: https://vikunja.io/en/download
|
||||
url: https://vikunja.io/download
|
||||
weight: 30
|
||||
- name: Blog
|
||||
url: https://vikunja.io/blog/
|
||||
weight: 35
|
||||
- name: Docs
|
||||
url: https://vikunja.io/docs
|
||||
weight: 40
|
||||
- name: Code
|
||||
url: https://code.vikunja.io/
|
||||
weight: 50
|
||||
- name: Community
|
||||
url: https://community.vikunja.io/
|
||||
weight: 60
|
||||
- name: Stickers
|
||||
url: https://vikunja.cloud/stickers?utm_source=io&utm_medium=io&utm_campaign=menu
|
||||
weight: 65
|
||||
- name: Get it Hosted
|
||||
url: https://vikunja.cloud/?utm_source=io&utm_medium=io&utm_campaign=menu
|
||||
weight: 70
|
||||
sidebar:
|
||||
- name: setup
|
||||
weight: 10
|
||||
- name: usage
|
||||
weight: 20
|
||||
- name: development
|
||||
weight: 30
|
|
@ -17,9 +17,9 @@ To learn more about the what, why and how, take a look at [the features page](ht
|
|||
|
||||
## Start
|
||||
|
||||
A good starting point if you want to install and host Vikunja on your server are [the install documentation](installing)
|
||||
and [available configuration options](config-options).
|
||||
A good starting point if you want to install and host Vikunja on your server are [the install documentation]({{< ref "./setup/install.md">}})
|
||||
and [available configuration options]({{< ref "./setup/config.md">}}).
|
||||
|
||||
## Developing
|
||||
|
||||
If you want to start contributing to Vikunja, take a look at [the development docs](development).
|
||||
If you want to start contributing to Vikunja, take a look at [the development docs]({{< ref "./development/development.md">}}).
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
date: "2019-03-31:00:00+01:00"
|
||||
title: "Adding new cli commands"
|
||||
title: "Cli Commands"
|
||||
draft: false
|
||||
type: "doc"
|
||||
menu:
|
||||
|
@ -12,10 +12,10 @@ menu:
|
|||
|
||||
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()`.
|
||||
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.
|
||||
Please refer to its documentation for information about how to use flags etc.
|
||||
|
||||
To add a new cli command, add something like the following:
|
||||
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
---
|
||||
date: "2019-02-12:00:00+02:00"
|
||||
title: "Configuration Options"
|
||||
draft: false
|
||||
type: "doc"
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "development"
|
||||
---
|
||||
|
||||
# Configuration options
|
||||
|
||||
All configuration variables are declared in the `config` package.
|
||||
It uses [viper](https://github.com/spf13/viper) under the hood to handle setting defaults and parsing config files.
|
||||
Viper handles parsing all different configuration sources.
|
||||
|
||||
## Adding new config options
|
||||
|
||||
To make handling configuration parameters a bit easier, we introduced a `Key` string type in the `config` package which
|
||||
you can call directly to get a config value.
|
||||
|
||||
To add a new config option, you should add a new key const to `pkg/config/config.go` and possibly a default value.
|
||||
Default values should always enable the feature to work or turn it off completely if it always needs
|
||||
additional configuration.
|
||||
|
||||
Make sure to also add the new config option to the default config file (`config.yml.sample` at the root of the repository)
|
||||
with an explanatory comment to make sure it is well documented.
|
||||
Then run `mage generate-docs` to generate the configuration docs from the sample file.
|
||||
|
||||
## Getting Configuration Values
|
||||
|
||||
To retrieve a configured value call the key with a getter for the type you need.
|
||||
For example:
|
||||
|
||||
{{< highlight golang >}}
|
||||
if config.CacheEnabled.GetBool() {
|
||||
// Do something with enabled caches
|
||||
}
|
||||
{{< /highlight >}}
|
||||
|
||||
Take a look at the methods declared on the type to see what's available.
|
|
@ -0,0 +1,33 @@
|
|||
---
|
||||
title: "Cron Tasks"
|
||||
date: 2021-07-13T23:21:52+02:00
|
||||
draft: false
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "development"
|
||||
---
|
||||
|
||||
# How to add a cron job task
|
||||
|
||||
Cron jobs are tasks which run on a predefined schedule.
|
||||
Vikunja uses these through a light wrapper package around the excellent [github.com/robfig/cron](https://github.com/robfig/cron) package.
|
||||
|
||||
The package exposes a `cron.Schedule` method with two arguments: The first one to define the schedule when the cron task should run, and the second one with the actual function to run at the schedule. You would then create a new function to register your the actual cron task in your package.
|
||||
|
||||
A basic function to register a cron task looks like this:
|
||||
|
||||
{{< highlight golang >}}
|
||||
func RegisterSomeCronTask() {
|
||||
err := cron.Schedule("0 * * * *", func() {
|
||||
// Do something every hour
|
||||
}
|
||||
}
|
||||
{{< /highlight >}}
|
||||
|
||||
Call the register method in the `FullInit()` method of the `init` package to actually register it.
|
||||
|
||||
## Schedule Syntax
|
||||
|
||||
The cron syntax uses the same on you may know from unix systems.
|
||||
|
||||
It is described in detail [here](https://pkg.go.dev/github.com/robfig/cron#hdr-CRON_Expression_Format).
|
|
@ -0,0 +1,38 @@
|
|||
---
|
||||
date: "2019-02-12:00:00+02:00"
|
||||
title: "Database"
|
||||
draft: false
|
||||
type: "doc"
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "development"
|
||||
---
|
||||
|
||||
# Database
|
||||
|
||||
Vikunja uses [xorm](https://xorm.io/) as an abstraction layer to handle the database connection.
|
||||
Please refer to [their](https://xorm.io/docs/) documentation on how to exactly use it.
|
||||
|
||||
{{< table_of_contents >}}
|
||||
|
||||
## Using the database
|
||||
|
||||
When using the common web handlers, you get an `xorm.Session` to do database manipulations.
|
||||
In other packages, use the `db.NewSession()` method to get a new database session.
|
||||
|
||||
## Adding new database tables
|
||||
|
||||
To add a new table to the database, create the struct and [add a migration for it]({{< ref "db-migrations.md" >}}).
|
||||
|
||||
To learn more about how to configure your struct to create "good" tables, refer to [the xorm documentaion](https://xorm.io/docs/).
|
||||
|
||||
In most cases you will also need to implement the `TableName() string` method on the new struct to make sure the table name matches the rest of the tables - plural.
|
||||
|
||||
## Adding data to test fixtures
|
||||
|
||||
Adding data for test fixtures can be done via `yaml` files in `pkg/models/fixtures`.
|
||||
|
||||
The name of the yaml file should match the table name in the database.
|
||||
Adding values to it is done via array definition inside it.
|
||||
|
||||
**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.
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
date: "2019-03-29:00:00+02:00"
|
||||
title: "Database migrations"
|
||||
title: "Database Migrations"
|
||||
draft: false
|
||||
type: "doc"
|
||||
menu:
|
||||
|
@ -16,6 +16,8 @@ Additionally, they can also be run directly by using the `migrate` command.
|
|||
We use [xormigrate](https://github.com/techknowlogick/xormigrate) to handle migrations,
|
||||
which is based on gormigrate.
|
||||
|
||||
{{< table_of_contents >}}
|
||||
|
||||
## Add a new migration
|
||||
|
||||
All migrations are stored in `pkg/migrations` and files should have the same name as their id.
|
||||
|
@ -23,7 +25,7 @@ All migrations are stored in `pkg/migrations` and files should have the same nam
|
|||
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:
|
||||
To easily get a new id, run the following on any unix system:
|
||||
|
||||
{{< highlight bash >}}
|
||||
date +%Y%m%d%H%M%S
|
||||
|
@ -35,6 +37,11 @@ All migrations are sorted before being executed, since `init()` does not guarant
|
|||
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.
|
||||
|
||||
### Generating a new migration stub
|
||||
|
||||
You can easily generate a pre-filled migration stub by running `mage dev:make-migration`.
|
||||
It will ask you for a table name and generate an empty migration similar to the example shown below.
|
||||
|
||||
### Example
|
||||
|
||||
{{< highlight golang >}}
|
||||
|
@ -68,4 +75,4 @@ func init() {
|
|||
}
|
||||
{{< /highlight >}}
|
||||
|
||||
You should always copy the changed parts of the struct you're changing when adding migraitons.
|
||||
You should always copy the changed parts of the struct you're changing when adding migrations.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
date: "2019-02-12:00:00+02:00"
|
||||
date: "2022-09-21:00:00+02:00"
|
||||
title: "Development"
|
||||
toc: true
|
||||
draft: false
|
||||
|
@ -12,57 +12,44 @@ menu:
|
|||
|
||||
# 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.
|
||||
{{< table_of_contents >}}
|
||||
|
||||
To contribute to Vikunja, fork the project and work on the master branch.
|
||||
## General
|
||||
|
||||
A lot of developing tasks are automated using a Makefile, so make sure to [take a look at it]({{< ref "make.md">}}).
|
||||
To contribute to Vikunja, fork the project and work on the main branch.
|
||||
Once you feel like your changes are ready, open a PR in the respective repo.
|
||||
A maintainer will take a look and give you feedback. Once everyone is happy, the PR gets merged and released.
|
||||
|
||||
## Libraries
|
||||
If you plan to do a bigger change, it is better to open an issue for discussion first.
|
||||
|
||||
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).
|
||||
## API
|
||||
|
||||
## Tests
|
||||
The code for the api is located at [code.vikunja.io/api](https://code.vikunja.io/api).
|
||||
|
||||
See [testing]({{< ref "test.md">}}).
|
||||
We use go modules to manage third-party libraries for Vikunja, so you'll need at least go `1.17` to use these.
|
||||
|
||||
#### Development using go modules
|
||||
A lot of developing tasks are automated using a Magefile, so make sure to [take a look at it]({{< ref "mage.md">}}).
|
||||
|
||||
If you're able to use go modules, you can clone the project wherever you want to and work from there.
|
||||
Make sure to check the other doc articles for specific development tasks like [testing]({{< ref "test.md">}}),
|
||||
[database migrations]({{< ref "db-migrations.md" >}}) and the [project structure]({{< ref "structure.md" >}}).
|
||||
|
||||
However, when building or running tests, please supply the `-mod=vendor` flag to go so it builds using the
|
||||
dependencies from the `vendor/` folder.
|
||||
## Frontend requirements
|
||||
|
||||
#### Development-setup without go modules
|
||||
The code for the frontend is located at [code.vikunja.io/frontend](https://code.vikunja.io/frontend).
|
||||
More instructions can be found in the repo's README.
|
||||
|
||||
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.
|
||||
You need to have [pnpm](https://pnpm.io/) and nodejs in version 16 or 18 installed.
|
||||
|
||||
{{< highlight bash >}}
|
||||
cd $GOPATH/src/code.vikunja.io/api
|
||||
{{< /highlight >}}
|
||||
## Git flow
|
||||
|
||||
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.
|
||||
The `main` branch is the latest and bleeding edge branch with all changes. Unstable releases are automatically created from this branch.
|
||||
|
||||
{{< highlight bash >}}
|
||||
git remote rename origin upstream
|
||||
git remote add origin git@git.kolaente.de:<USERNAME>/api.git
|
||||
git fetch --all --prune
|
||||
{{< /highlight >}}
|
||||
A release gets tagged from the main branch with the version name as tag name.
|
||||
|
||||
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.
|
||||
Backports and point-releases should go to a `release/version` branch, based on the tag they are building on top of.
|
||||
|
||||
That’s it! You are ready to hack on Vikunja. Test changes, push them to the repository, and open a pull request.
|
||||
## Conventional Commits
|
||||
|
||||
## Static assets
|
||||
We're using [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) because they greatly simplify generating release notes.
|
||||
|
||||
Each Vikunja release contains all static assets directly compiled into the binary.
|
||||
To prevent this during development, use the `dev` tag when developing.
|
||||
|
||||
See the [make docs](make.md#statically-compile-all-templates-into-the-binary) about how to compile with static assets for a release.
|
||||
It is not required to use them when creating a PR, but appreciated.
|
||||
|
|
|
@ -5,7 +5,7 @@ draft: false
|
|||
type: "doc"
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "practical instructions"
|
||||
parent: "development"
|
||||
---
|
||||
|
||||
# Custom Errors
|
||||
|
@ -13,14 +13,14 @@ menu:
|
|||
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">}})
|
||||
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.
|
||||
// In this case, it contains the user ID of a nonexistent user.
|
||||
// This type should always be a struct, even if it has no values in it.
|
||||
|
||||
// ErrUserDoesNotExist represents a "UserDoesNotExist" kind of error.
|
||||
|
@ -44,21 +44,21 @@ 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
|
||||
// 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.
|
||||
// project errors are 3000-something and so on.
|
||||
// * New error codes should be the current max error code + 1. Don't take free numbers to prevent old errors
|
||||
// which are depricated and removed from being "new ones". For example, if there are error codes 1001, 1002, 1004,
|
||||
// which are deprecated 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
|
||||
// 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
|
|
@ -0,0 +1,211 @@
|
|||
---
|
||||
date: 2018-10-13T19:26:34+02:00
|
||||
title: "Events and Listeners"
|
||||
draft: false
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "development"
|
||||
---
|
||||
|
||||
# Events and Listeners
|
||||
|
||||
Vikunja provides a simple observer pattern mechanism through events and listeners.
|
||||
The basic principle of events is always the same: Something happens (=An event is fired) and something reacts to it (=A listener is called).
|
||||
|
||||
Vikunja supports this principle through the `events` package.
|
||||
It is built upon the excellent [watermill](https://watermill.io) library.
|
||||
|
||||
Currently, it only supports dispatching events through Go Channels which makes it configuration-less.
|
||||
More methods of dispatching events (like kafka or rabbitmq) are available in watermill and could be enabled with a PR.
|
||||
|
||||
This document explains how events and listeners work in Vikunja, how to use them and how to create new ones.
|
||||
|
||||
{{< table_of_contents >}}
|
||||
|
||||
## Events
|
||||
|
||||
### Definition
|
||||
|
||||
Each event has to implement this interface:
|
||||
|
||||
{{< highlight golang >}}
|
||||
type Event interface {
|
||||
Name() string
|
||||
}
|
||||
{{< /highlight >}}
|
||||
|
||||
An event can contain whatever data you need.
|
||||
|
||||
When an event is dispatched, all of the data it contains will be marshaled into json for dispatching.
|
||||
You then get the event with all its data back in the listener, see below.
|
||||
|
||||
#### Naming Convention
|
||||
|
||||
Event names should roughly have the entity they're dealing with on the left and the action on the right of the name, separated by `.`.
|
||||
There's no limit to how "deep" or specific an event name can be.
|
||||
|
||||
The name should have the most general concept it's describing at the left, getting more specific on the right of it.
|
||||
|
||||
#### Location
|
||||
|
||||
All events for a package should be declared in the `events.go` file of that package.
|
||||
|
||||
### Creating a New Event
|
||||
|
||||
The easiest way to create a new event is to generate it with mage:
|
||||
|
||||
```
|
||||
mage dev:make-event <event-name> <package>
|
||||
```
|
||||
|
||||
The function takes the name of the event as the first argument and the package where the event should be created as the second argument.
|
||||
Events will be appended to the `pkg/<module>/events.go` file.
|
||||
Both parameters are mandatory.
|
||||
|
||||
The event type name is automatically camel-cased and gets the `Event` suffix if the provided name does not already have one.
|
||||
The event name is derived from the type name and stripped of the `.event` suffix.
|
||||
|
||||
The generated event will look something like the example below.
|
||||
|
||||
### Dispatching events
|
||||
|
||||
To dispatch an event, simply call the `events.Dispatch` method and pass in the event as parameter.
|
||||
|
||||
### Example
|
||||
|
||||
The `TaskCreatedEvent` is declared in the `pkg/models/events.go` file as follows:
|
||||
|
||||
{{< highlight golang >}}
|
||||
// TaskCreatedEvent represents an event where a task has been created
|
||||
type TaskCreatedEvent struct {
|
||||
Task *Task
|
||||
Doer web.Auth
|
||||
}
|
||||
|
||||
// Name defines the name for TaskCreatedEvent
|
||||
func (t *TaskCreatedEvent) Name() string {
|
||||
return "task.created"
|
||||
}
|
||||
{{< /highlight >}}
|
||||
|
||||
It is dispatched in the `createTask` function of the `models` package:
|
||||
|
||||
{{< highlight golang >}}
|
||||
func createTask(s *xorm.Session, t *Task, a web.Auth, updateAssignees bool) (err error) {
|
||||
|
||||
// ...
|
||||
|
||||
err = events.Dispatch(&TaskCreatedEvent{
|
||||
Task: t,
|
||||
Doer: a,
|
||||
})
|
||||
|
||||
// ...
|
||||
}
|
||||
{{< /highlight >}}
|
||||
|
||||
As you can see, the current task and doer are injected into it.
|
||||
|
||||
### Special Events
|
||||
|
||||
#### `BootedEvent`
|
||||
|
||||
Once Vikunja is fully initialized, right before the api web server is started, this event is fired.
|
||||
|
||||
## Listeners
|
||||
|
||||
A listener is a piece of code that gets executed asynchronously when an event is dispatched.
|
||||
|
||||
A single event can have multiple listeners who are independent of each other.
|
||||
|
||||
### Definition
|
||||
|
||||
All listeners must implement this interface:
|
||||
|
||||
{{< highlight golang >}}
|
||||
// Listener represents something that listens to events
|
||||
type Listener interface {
|
||||
Handle(msg *message.Message) error
|
||||
Name() string
|
||||
}
|
||||
{{< /highlight >}}
|
||||
|
||||
The `Handle` method is executed when the event this listener listens on is dispatched.
|
||||
* As the single parameter, it gets the payload of the event, which is the event struct when it was dispatched decoded as json object and passed as a slice of bytes.
|
||||
To use it you'll need to unmarshal it. Unfortunately there's no way to pass an already populated event object to the function because we would not know what type it has when parsing it.
|
||||
* If the handler returns an error, the listener is retried 5 times, with an exponential back-off period in between retries.
|
||||
If it still fails after the fifth retry, the event is nack'd and it's up to the event dispatcher to resend it.
|
||||
You can learn more about this mechanism in the [watermill documentation](https://watermill.io/docs/middlewares/#retry).
|
||||
|
||||
The `Name` method needs to return a unique listener name for this listener.
|
||||
It should follow the same convention as event names, see above.
|
||||
|
||||
### Creating a New Listener
|
||||
|
||||
The easiest way to create a new listener for an event is with mage:
|
||||
|
||||
```
|
||||
mage dev:make-listener <listener-name> <event-name> <package>
|
||||
```
|
||||
|
||||
This will create a new listener type in the `pkg/<package>/listeners.go` file and implement the `Handle` and `Name` methods.
|
||||
It will also pre-generate some boilerplate code to unmarshal the event from the payload.
|
||||
|
||||
Furthermore, it will register the listener for its event in the `RegisterListeners()` method of the same file.
|
||||
This function is called at startup and has to contain all events you want to listen for.
|
||||
|
||||
### Listening for Events
|
||||
|
||||
To listen for an event, you need to register the listener for the event it should be called for.
|
||||
This usually happens in the `RegisterListeners()` method in `pkg/<package>/listeners.go` which is called at start up.
|
||||
|
||||
The listener will never be executed if it hasn't been registered.
|
||||
|
||||
See the example below.
|
||||
|
||||
### Example
|
||||
|
||||
{{< highlight golang >}}
|
||||
// RegisterListeners registers all event listeners
|
||||
func RegisterListeners() {
|
||||
events.RegisterListener((&ListCreatedEvent{}).Name(), &IncreaseListCounter{})
|
||||
}
|
||||
|
||||
// IncreaseTaskCounter represents a listener
|
||||
type IncreaseTaskCounter struct {}
|
||||
|
||||
// Name defines the name for the IncreaseTaskCounter listener
|
||||
func (s *IncreaseTaskCounter) Name() string {
|
||||
return "task.counter.increase"
|
||||
}
|
||||
|
||||
// Handle is executed when the event IncreaseTaskCounter listens on is fired
|
||||
func (s *IncreaseTaskCounter) Handle(payload message.Payload) (err error) {
|
||||
return keyvalue.IncrBy(metrics.TaskCountKey, 1)
|
||||
}
|
||||
{{< /highlight >}}
|
||||
|
||||
## Testing
|
||||
|
||||
When testing, you should call the `events.Fake()` method in the `TestMain` function of the package you want to test.
|
||||
This prevents any events from being fired and lets you assert an event has been dispatched like so:
|
||||
|
||||
{{< highlight golang >}}
|
||||
events.AssertDispatched(t, &TaskCreatedEvent{})
|
||||
{{< /highlight >}}
|
||||
|
||||
### Testing a listener
|
||||
|
||||
You can call an event listener manually with the `events.TestListener` method like so:
|
||||
|
||||
{{< highlight golang >}}
|
||||
ev := &TaskCommentCreatedEvent{
|
||||
Task: &task,
|
||||
Doer: u,
|
||||
Comment: tc,
|
||||
}
|
||||
|
||||
events.TestListener(t, ev, &SendTaskCommentNotification{})
|
||||
{{< /highlight >}}
|
||||
|
||||
This will call the listener's `Handle` method and assert it did not return an error when calling.
|
|
@ -1,11 +1,11 @@
|
|||
---
|
||||
date: "2019-02-12:00:00+02:00"
|
||||
title: "Add a new api endpoint"
|
||||
title: "New API Endpoints"
|
||||
draft: false
|
||||
type: "doc"
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "practical instructions"
|
||||
parent: "development"
|
||||
---
|
||||
|
||||
# Add a new api endpoint/feature
|
||||
|
@ -26,8 +26,8 @@ It returns the `limit` (max-length) and `offset` parameters needed for SQL-Queri
|
|||
You can feed this function directly into xorm's `Limit`-Function like so:
|
||||
|
||||
{{< highlight golang >}}
|
||||
lists := []List{}
|
||||
err := x.Limit(getLimitFromPageIndex(pageIndex, itemsPerPage)).Find(&lists)
|
||||
projects := []*Project{}
|
||||
err := x.Limit(getLimitFromPageIndex(pageIndex, itemsPerPage)).Find(&projects)
|
||||
{{< /highlight >}}
|
||||
|
||||
// TODO: Add a full example from start to finish, like a tutorial on how to create a new endpoint?
|
|
@ -0,0 +1,192 @@
|
|||
---
|
||||
date: "2019-02-12:00:00+02:00"
|
||||
title: "Magefile"
|
||||
draft: false
|
||||
type: "doc"
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "development"
|
||||
---
|
||||
|
||||
# Mage
|
||||
|
||||
Vikunja uses [Mage](https://magefile.org/) to script common development tasks and even releasing.
|
||||
Mage is a pure go solution which allows for greater flexibility and things like better parallelization.
|
||||
|
||||
This document explains what tasks are available and what they do.
|
||||
|
||||
{{< table_of_contents >}}
|
||||
|
||||
## Installation
|
||||
|
||||
To use mage, you'll need to install the mage cli.
|
||||
To install it, run the following command:
|
||||
|
||||
```
|
||||
go install github.com/magefile/mage
|
||||
```
|
||||
|
||||
## Categories
|
||||
|
||||
There are multiple categories of subcommands in the magefile:
|
||||
|
||||
* `build`: Contains commands to build a single binary
|
||||
* `check`: Contains commands to statically check the source code
|
||||
* `release`: Contains commands to release Vikunja with everything that's required
|
||||
* `test`: Contains commands to run all kinds of tests
|
||||
* `dev`: Contains commands to run development tasks
|
||||
* `misc`: Commands which do not belong in either of the other categories
|
||||
|
||||
## CI
|
||||
|
||||
These tasks are automatically run in our CI every time someone pushes to main or you update a pull request:
|
||||
|
||||
* `mage check:lint`
|
||||
* `mage check:fmt`
|
||||
* `mage check:ineffassign`
|
||||
* `mage check:misspell`
|
||||
* `mage check:goconst`
|
||||
* `mage build:generate`
|
||||
* `mage build:build`
|
||||
|
||||
## Build
|
||||
|
||||
### Build Vikunja
|
||||
|
||||
{{< highlight bash >}}
|
||||
mage build:build
|
||||
{{< /highlight >}}
|
||||
|
||||
or
|
||||
|
||||
{{< highlight bash >}}
|
||||
mage build
|
||||
{{< /highlight >}}
|
||||
|
||||
Builds a `vikunja`-binary in the root directory of the repo for the platform it is run on.
|
||||
|
||||
### clean
|
||||
|
||||
{{< highlight bash >}}
|
||||
mage build:clean
|
||||
{{< /highlight >}}
|
||||
|
||||
Cleans all build and executable files
|
||||
|
||||
## Check
|
||||
|
||||
All check sub-commands exit with a status code of 1 if the check fails.
|
||||
|
||||
Various code-checks are available:
|
||||
|
||||
* `mage check:all`: Runs fmt-check, lint, got-swag, misspell-check, ineffasign-check, gocyclo-check, static-check, gosec-check, goconst-check all in parallel
|
||||
* `mage check:fmt`: Checks if the code is properly formatted with go fmt
|
||||
* `mage check:go-sec`: Checks the source code for potential security issues by scanning the Go AST using the [gosec tool](https://github.com/securego/gosec)
|
||||
* `mage check:goconst`: Checks for repeated strings that could be replaced by a constant using [goconst](https://github.com/jgautheron/goconst/)
|
||||
* `mage check:gocyclo`: Checks for the cyclomatic complexity of the source code using [gocyclo](https://github.com/fzipp/gocyclo)
|
||||
* `mage check:got-swag`: Checks if the swagger docs need to be re-generated from the code annotations
|
||||
* `mage check:ineffassign`: Checks the source code for ineffectual assigns using [ineffassign](https://github.com/gordonklaus/ineffassign)
|
||||
* `mage check:lint`: Runs golint on all packages
|
||||
* `mage check:misspell`: Checks the source code for misspellings
|
||||
* `mage check:static`: Statically analyzes the source code about a range of different problems using [staticcheck](https://staticcheck.io/docs/)
|
||||
|
||||
## Release
|
||||
|
||||
### Build Releases
|
||||
|
||||
{{< highlight bash >}}
|
||||
mage release
|
||||
{{< /highlight >}}
|
||||
|
||||
Builds binaries for all platforms and zips them with a copy of the `templates/` folder.
|
||||
All built zip files are stored into `dist/zips/`. Binaries are stored in `dist/binaries/`,
|
||||
binaries bundled with `templates` are stored in `dist/releases/`.
|
||||
|
||||
All cross-platform binaries built using this series of commands are built with the help of
|
||||
[xgo](https://github.com/techknowlogick/xgo). The mage command will automatically install the
|
||||
binary to be able to use it.
|
||||
|
||||
`mage release:release` is a shortcut to execute `mage release:dirs release:windows release:linux release:darwin release:copy release:check release:os-package release:zip`.
|
||||
|
||||
* `mage release:dirs` creates all directories needed
|
||||
* `mage release:windows`/`release:linux`/`release:darwin` execute xgo to build for their respective platforms
|
||||
* `mage release:copy` bundles binaries with a copy of the `LICENSE` and sample config files to then be zipped
|
||||
* `mage release:check` creates sha256 checksums for each binary which will be included in the zip file
|
||||
* `mage release:os-package` bundles a binary with the `sha256` checksum file, a sample `config.yml` and a copy of the license in a folder for each architecture
|
||||
* `mage release:compress` compresses all build binaries with `upx` to save space
|
||||
* `mage release:zip` packages a zip file for the files created by `release:os-package`
|
||||
|
||||
### Build os packages
|
||||
|
||||
{{< highlight bash >}}
|
||||
mage release:packages
|
||||
{{< /highlight >}}
|
||||
|
||||
Will build `.deb`, `.rpm` and `.apk` packages to `dist/os-packages`.
|
||||
|
||||
### Make a debian repo
|
||||
|
||||
{{< highlight bash >}}
|
||||
mage release:reprepro
|
||||
{{< /highlight >}}
|
||||
|
||||
Takes an already built debian package and creates a debian repo structure around it.
|
||||
|
||||
Used to be run inside a [docker container](https://git.kolaente.de/konrad/reprepro-docker) in the CI process when releasing.
|
||||
|
||||
## Test
|
||||
|
||||
### unit
|
||||
|
||||
{{< highlight bash >}}
|
||||
mage test:unit
|
||||
{{< /highlight >}}
|
||||
|
||||
Runs all tests except integration tests.
|
||||
|
||||
### coverage
|
||||
|
||||
{{< highlight bash >}}
|
||||
mage test:coverage
|
||||
{{< /highlight >}}
|
||||
|
||||
Runs all tests except integration tests and generates a `coverage.html` file to inspect the code coverage.
|
||||
|
||||
### integration
|
||||
|
||||
{{< highlight bash >}}
|
||||
mage test:integration
|
||||
{{< /highlight >}}
|
||||
|
||||
Runs all integration tests.
|
||||
|
||||
## Dev
|
||||
|
||||
### Create a new migration
|
||||
|
||||
{{< highlight bash >}}
|
||||
mage dev:create-migration
|
||||
{{< /highlight >}}
|
||||
|
||||
Creates a new migration with the current date.
|
||||
Will ask for the name of the struct you want to create a migration for.
|
||||
|
||||
See also [migration docs]({{< ref "mage.md" >}}).
|
||||
|
||||
## Misc
|
||||
|
||||
### Format the code
|
||||
|
||||
{{< highlight bash >}}
|
||||
mage fmt
|
||||
{{< /highlight >}}
|
||||
|
||||
Formats all source code using `go fmt`.
|
||||
|
||||
### Generate swagger definitions from code comments
|
||||
|
||||
{{< highlight bash >}}
|
||||
mage do-the-swag
|
||||
{{< /highlight >}}
|
||||
|
||||
Generates swagger definitions from the comment annotations in the code.
|
|
@ -1,151 +0,0 @@
|
|||
---
|
||||
date: "2019-02-12:00:00+02:00"
|
||||
title: "Makefile"
|
||||
draft: false
|
||||
type: "doc"
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "development"
|
||||
---
|
||||
|
||||
# Makefile
|
||||
|
||||
We scripted a lot of tasks used mostly for developing into the makefile. This documents explains what
|
||||
taks are available and what they do.
|
||||
|
||||
## CI
|
||||
|
||||
These tasks are automatically run in our CI every time someone pushes to master or you update a pull request:
|
||||
|
||||
* `make lint`
|
||||
* `make fmt-check`
|
||||
* `make ineffassign-check`
|
||||
* `make misspell-check`
|
||||
* `make goconst-check`
|
||||
* `make generate`
|
||||
* `make build`
|
||||
|
||||
### clean
|
||||
|
||||
{{< highlight bash >}}
|
||||
make clean
|
||||
{{< /highlight >}}
|
||||
|
||||
Clears all builds and binaries.
|
||||
|
||||
### test
|
||||
|
||||
{{< highlight bash >}}
|
||||
make test
|
||||
{{< /highlight >}}
|
||||
|
||||
Runs all tests in Vikunja.
|
||||
|
||||
### Format the code
|
||||
|
||||
{{< highlight bash >}}
|
||||
make fmt
|
||||
{{< /highlight >}}
|
||||
|
||||
Formats all source code using `go fmt`.
|
||||
|
||||
#### Check formatting
|
||||
|
||||
{{< highlight bash >}}
|
||||
make fmt-check
|
||||
{{< /highlight >}}
|
||||
|
||||
Checks if the code needs to be formatted. Fails if it does.
|
||||
|
||||
### Build Vikunja
|
||||
|
||||
{{< highlight bash >}}
|
||||
make build
|
||||
{{< /highlight >}}
|
||||
|
||||
Builds a `vikunja`-binary in the root directory of the repo for the platform it is run on.
|
||||
|
||||
### Statically compile all templates into the binary
|
||||
|
||||
{{< highlight bash >}}
|
||||
make generate
|
||||
{{< /highlight >}}
|
||||
|
||||
This generates static code with all templates, meaning no template need to be referenced at runtime.
|
||||
|
||||
### Compress the built binary
|
||||
|
||||
{{< highlight bash >}}
|
||||
make compress-build
|
||||
{{< /highlight >}}
|
||||
|
||||
Go binaries are very big.
|
||||
To make the vikunja binary smaller, we can compress it using [upx](https://upx.github.io/).
|
||||
|
||||
### Build Releases
|
||||
|
||||
{{< highlight bash >}}
|
||||
make release
|
||||
{{< /highlight >}}
|
||||
|
||||
Builds binaries for all platforms and zips them with a copy of the `templates/` folder.
|
||||
All built zip files are stored into `dist/zips/`. Binaries are stored in `dist/binaries/`,
|
||||
binaries bundled with `templates` are stored in `dist/releases/`.
|
||||
|
||||
All cross-platform binaries built using this series of commands are built with the help of
|
||||
[xgo](https://github.com/techknowlogick/xgo). The make command will automatically install the
|
||||
binary to be able to use it.
|
||||
|
||||
`make release` is actually just a shortcut to execute `make release-dirs release-windows release-linux release-darwin release-copy release-check release-os-package release-zip`.
|
||||
|
||||
* `release-dirs` creates all directories needed
|
||||
* `release-windows`/`release-linux`/`release-darwin` execute xgo to build for their respective platforms
|
||||
* `release-copy` bundles binaries with a copy of `templates/` to then be zipped
|
||||
* `release-check` creates sha256 checksums for each binary which will be included in the zip file
|
||||
* `release-os-package` bundles a binary with a copy of the `templates/` folder, the `sha256` checksum file, a sample `config.yml` and a copy of the license in a folder for each architecture
|
||||
* `release-compress` compresses all build binaries, see `compress-build`
|
||||
* `release-zip` makes a zip file for the files created by `release-os-package`
|
||||
|
||||
### Build debian packages
|
||||
|
||||
{{< highlight bash >}}
|
||||
make build-deb
|
||||
{{< /highlight >}}
|
||||
|
||||
Will build a `.deb` package into the current folder. You need to have [fpm](https://fpm.readthedocs.io/en/latest/intro.html) installed to be able to do this.
|
||||
|
||||
#### Make a debian repo
|
||||
|
||||
{{< highlight bash >}}
|
||||
make reprepro
|
||||
{{< /highlight >}}
|
||||
|
||||
Takes an already built debian package and creates a debian repo structure around it.
|
||||
|
||||
Used to be run inside a [docker container](https://git.kolaente.de/konrad/reprepro-docker) in the CI process when releasing.
|
||||
|
||||
### Generate swagger definitions from code comments
|
||||
|
||||
{{< highlight bash >}}
|
||||
make do-the-swag
|
||||
{{< /highlight >}}
|
||||
|
||||
Generates swagger definitions from the comments in the code.
|
||||
|
||||
#### Check if swagger generation is needed
|
||||
|
||||
{{< highlight bash >}}
|
||||
make got-swag
|
||||
{{< /highlight >}}
|
||||
|
||||
This command is currently more an experiment, use it with caution.
|
||||
It may bring up wrong results.
|
||||
|
||||
### Code-Checks
|
||||
|
||||
* `misspell-check`: Checks for commonly misspelled words
|
||||
* `ineffassign-check`: Checks for ineffectual assignments in the code using [ineffassign](https://github.com/gordonklaus/ineffassign).
|
||||
* `gocyclo-check`: Calculates cyclomatic complexities of functions using [gocyclo](https://github.com/fzipp/gocyclo).
|
||||
* `static-check`: Analyzes the code for bugs, improvements and more using [staticcheck](https://staticcheck.io/docs/).
|
||||
* `gosec-check`: Inspects source code for security problems by scanning the Go AST using the [gosec tool](https://github.com/securego/gosec).
|
||||
* `goconst-check`: Finds repeated strings that could be replaced by a constant using [goconst](https://github.com/jgautheron/goconst/).
|
|
@ -5,7 +5,7 @@ draft: false
|
|||
type: "doc"
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "practical instructions"
|
||||
parent: "development"
|
||||
---
|
||||
|
||||
# Metrics
|
||||
|
@ -15,7 +15,9 @@ Metrics work by exposing a `/metrics` endpoint which can then be accessed by pro
|
|||
To keep the load on the database minimal, metrics are stored and updated in redis.
|
||||
The `metrics` package provides several functions to create and update metrics.
|
||||
|
||||
## New metrics
|
||||
{{< table_of_contents >}}
|
||||
|
||||
## Exposing New Metrics
|
||||
|
||||
First, define a `const` with the metric key in redis. This is done in `pkg/metrics/metrics.go`.
|
||||
|
||||
|
@ -32,15 +34,14 @@ promauto.NewGaugeFunc(prometheus.GaugeOpts{
|
|||
})
|
||||
{{< /highlight >}}
|
||||
|
||||
Then you'll need to set the metrics initial value on every startup of vikunja.
|
||||
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.
|
||||
To do this, use `metrics.UpdateCount(value, key)` where `value` is the amount you want to change it (you can pass negative values to decrease it) and `key` it the redis key used to define the metric.
|
||||
|
||||
# Using it
|
||||
## Using it
|
||||
|
||||
A Prometheus config with a Grafana template is available at [our git repo](https://git.kolaente.de/vikunja/monitoring).
|
|
@ -14,34 +14,57 @@ It is possible to migrate data from other to-do services to Vikunja.
|
|||
To make this easier, we have put together a few helpers which are documented on this page.
|
||||
|
||||
In general, each migrator implements a migrator interface which is then called from a client.
|
||||
The interface makes it possible to use helper methods which handle http an focus only on the implementation of the migrator itself.
|
||||
The interface makes it possible to use helper methods which handle http and focus only on the implementation of the migrator itself.
|
||||
|
||||
### Structure
|
||||
There are two ways of migrating data from another service:
|
||||
|
||||
1. Through the auth-based flow where the user gives you access to their data at the third-party service through an oauth flow. You can then call the service's api on behalf of your user to get all the data. The Todoist, Trello and Microsoft To-Do Migrators use this pattern.
|
||||
2. A file migration where the user uploads a file obtained from some third-party service. In your migrator, you need to parse the file and create the projects, tasks etc. The Vikunja File Import uses this pattern.
|
||||
|
||||
To differentiate the two, there are two different interfaces you must implement.
|
||||
|
||||
{{< table_of_contents >}}
|
||||
|
||||
## Structure
|
||||
|
||||
All migrator implementations live in their own package in `pkg/modules/migration/<name-of-the-service>`.
|
||||
When creating a new migrator, you should place all related code inside that module.
|
||||
|
||||
### Migrator interface
|
||||
## Migrator Interface
|
||||
|
||||
The migrator interface is defined as follows:
|
||||
|
||||
```go
|
||||
// Migrator is the basic migrator interface which is shared among all migrators
|
||||
type Migrator interface {
|
||||
// Migrate is the interface used to migrate a user's tasks from another platform to vikunja.
|
||||
// Name holds the name of the migration.
|
||||
// This is used to show the name to users and to keep track of users who already migrated.
|
||||
Name() string
|
||||
// Migrate is the interface used to migrate a user's tasks from another platform to Vikunja.
|
||||
// The user object is the user who's tasks will be migrated.
|
||||
Migrate(user *models.User) error
|
||||
// AuthURL returns a url for clients to authenticate against.
|
||||
// The use case for this are Oauth flows, where the server token should remain hidden and not
|
||||
// known to the frontend.
|
||||
AuthURL() string
|
||||
// Name holds the name of the migration.
|
||||
// This is used to show the name to users and to keep track of users who already migrated.
|
||||
Name() string
|
||||
}
|
||||
```
|
||||
|
||||
### Defining http routes
|
||||
## File Migrator Interface
|
||||
|
||||
```go
|
||||
// FileMigrator handles importing Vikunja data from a file. The implementation of it determines the format.
|
||||
type FileMigrator interface {
|
||||
// Name holds the name of the migration.
|
||||
// This is used to show the name to users and to keep track of users who already migrated.
|
||||
Name() string
|
||||
// Migrate is the interface used to migrate a user's tasks, projects and other things from a file to Vikunja.
|
||||
// The user object is the user who's tasks will be migrated.
|
||||
Migrate(user *user.User, file io.ReaderAt, size int64) error
|
||||
}
|
||||
```
|
||||
|
||||
## Defining http routes
|
||||
|
||||
Once your migrator implements the migration interface, it becomes possible to use the helper http handlers.
|
||||
Their usage is very similar to the [general web handler](https://kolaente.dev/vikunja/web#user-content-defining-routes-using-the-standard-web-handler):
|
||||
|
@ -52,45 +75,56 @@ authUrl, Status and Migrate methods.
|
|||
```go
|
||||
// This is an example for the Wunderlist migrator
|
||||
if config.MigrationWunderlistEnable.GetBool() {
|
||||
wunderlistMigrationHandler := &migrationHandler.MigrationWeb{
|
||||
wunderlistMigrationHandler := &migrationHandler.MigrationWeb{
|
||||
MigrationStruct: func() migration.Migrator {
|
||||
return &wunderlist.Migration{}
|
||||
},
|
||||
}
|
||||
wunderlistMigrationHandler.RegisterRoutes(m)
|
||||
wunderlistMigrationHandler.RegisterRoutes(m)
|
||||
}
|
||||
```
|
||||
|
||||
You should also document the routes with [swagger annotations]({{< ref "../practical-instructions/swagger-docs.md" >}}).
|
||||
And for the file migrator:
|
||||
|
||||
### Insertion helper method
|
||||
```go
|
||||
vikunjaFileMigrationHandler := &migrationHandler.FileMigratorWeb{
|
||||
MigrationStruct: func() migration.FileMigrator {
|
||||
return &vikunja_file.FileMigrator{}
|
||||
},
|
||||
}
|
||||
vikunjaFileMigrationHandler.RegisterRoutes(m)
|
||||
```
|
||||
|
||||
There is a method available in the `migration` package which takes a fully nested Vikunja structure and creates it with all relations.
|
||||
This means you start by adding a namespace, then add lists inside of that namespace, then tasks in the lists and so on.
|
||||
You should also document the routes with [swagger annotations]({{< ref "swagger-docs.md" >}}).
|
||||
|
||||
The root structure must be present as `[]*models.NamespaceWithLists`.
|
||||
## Insertion helper method
|
||||
|
||||
There is a method available in the `migration` package which takes a fully nested Vikunja structure and creates it with all relations.
|
||||
This means you start by adding a namespace, then add projects inside that namespace, then tasks in the lists and so on.
|
||||
|
||||
The root structure must be present as `[]*models.NamespaceWithProjectsAndTasks`. It allows to represent all of Vikunja's hierarchy as a single data structure.
|
||||
|
||||
Then call the method like so:
|
||||
|
||||
```go
|
||||
fullVikunjaHierachie, err := convertWunderlistToVikunja(wContent)
|
||||
fullVikunjaHierarchy, err := convertWunderlistToVikunja(wContent)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = migration.InsertFromStructure(fullVikunjaHierachie, user)
|
||||
err = migration.InsertFromStructure(fullVikunjaHierarchy, user)
|
||||
```
|
||||
|
||||
### Configuration
|
||||
## Configuration
|
||||
|
||||
You should add at least an option to enable or disable the migration.
|
||||
Chances are, you'll need some more options for things like client ID and secret
|
||||
(if the other service uses oAuth as an authentication flow).
|
||||
If your migrator is an oauth-based one, you should add at least an option to enable or disable it.
|
||||
Chances are, you'll need some more options for things like client ID and secret (if the other service uses oAuth as an authentication flow).
|
||||
|
||||
The easiest way to implement an on/off switch is to check whether your migration service is enabled or not when
|
||||
registering the routes, and then simply don't registering the routes in the case it is disabled.
|
||||
The easiest way to implement an on/off switch is to check whether your migration service is enabled or not when registering the routes, and then simply don't registering the routes in case it is disabled.
|
||||
|
||||
#### Making the migrator public in `/info`
|
||||
File based migrators can always be enabled.
|
||||
|
||||
### Making the migrator public in `/info`
|
||||
|
||||
You should make your migrator available in the `/info` endpoint so that frontends can display options to enable them or not.
|
||||
To do this, add an entry to `pkg/routes/api/v1/info.go`.
|
||||
To do this, add an entry to the `AvailableMigrators` field in `pkg/routes/api/v1/info.go`.
|
||||
|
|
|
@ -0,0 +1,119 @@
|
|||
---
|
||||
date: 2021-02-07T19:26:34+02:00
|
||||
title: "Notifications"
|
||||
toc: true
|
||||
draft: false
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "development"
|
||||
---
|
||||
|
||||
# Notifications
|
||||
|
||||
Vikunja provides a simple abstraction to send notifications per mail and in the database.
|
||||
|
||||
{{< table_of_contents >}}
|
||||
|
||||
## Definition
|
||||
|
||||
Each notification has to implement this interface:
|
||||
|
||||
{{< highlight golang >}}
|
||||
type Notification interface {
|
||||
ToMail() *Mail
|
||||
ToDB() interface{}
|
||||
Name() string
|
||||
}
|
||||
{{< /highlight >}}
|
||||
|
||||
Both functions return the formatted messages for mail and database.
|
||||
|
||||
A notification will only be sent or recorded for those of the two methods which don't return `nil`.
|
||||
For example, if your notification should not be recorded in the database but only sent out per mail, it is enough to let the `ToDB` function return `nil`.
|
||||
|
||||
### Mail notifications
|
||||
|
||||
A list of chainable functions is available to compose a mail:
|
||||
|
||||
{{< highlight golang >}}
|
||||
mail := NewMail().
|
||||
// The optional sender of the mail message.
|
||||
From("test@example.com").
|
||||
// The optional recipient of the mail message. Uses the mail address of the notifiable if omitted.
|
||||
To("test@otherdomain.com").
|
||||
// The subject of the mail to send.
|
||||
Subject("Testmail").
|
||||
// The greeting, or "intro" line of the mail.
|
||||
Greeting("Hi there,").
|
||||
// A line of text
|
||||
Line("This is a line of text").
|
||||
// An action can contain a title and a url. It gets rendered as a big button in the mail.
|
||||
// Note that you can have only one action per mail.
|
||||
// All lines added before an action will appear in the mail before the button, all lines
|
||||
// added afterwards will appear after it.
|
||||
Action("The Action", "https://example.com").
|
||||
// Another line of text.
|
||||
Line("This should be an outro line").
|
||||
{{< /highlight >}}
|
||||
|
||||
If not provided, the `from` field of the mail contains the value configured in [`mailer.fromemail`](https://vikunja.io/docs/config-options/#fromemail).
|
||||
|
||||
### Database notifications
|
||||
|
||||
All data returned from the `ToDB()` method is serialized to json and saved into the database, along with the id of the notifiable, the name of the notification and a time stamp.
|
||||
If you don't use the database notification, the `Name()` function can return an empty string.
|
||||
|
||||
## Creating a new notification
|
||||
|
||||
The easiest way to generate a mail is by using the `mage dev:make-notification` command.
|
||||
|
||||
It takes the name of the notification and the package where the notification will be created.
|
||||
|
||||
## Notifiables
|
||||
|
||||
Notifiables can receive a notification.
|
||||
A notifiable is defined with this interface:
|
||||
|
||||
{{< highlight golang >}}
|
||||
type Notifiable interface {
|
||||
// Should return the email address this notifiable has.
|
||||
RouteForMail() string
|
||||
// Should return the id of the notifiable entity
|
||||
RouteForDB() int64
|
||||
}
|
||||
{{< /highlight >}}
|
||||
|
||||
The `User` type from the `user` package implements this interface.
|
||||
|
||||
## Sending a notification
|
||||
|
||||
Sending a notification is done with the `Notify` method from the `notifications` package.
|
||||
It takes a notifiable and a notification as input.
|
||||
|
||||
For example, the email confirm notification when a new user registers is sent like this:
|
||||
|
||||
{{< highlight golang >}}
|
||||
n := &EmailConfirmNotification{
|
||||
User: update.User,
|
||||
IsNew: false,
|
||||
}
|
||||
|
||||
err = notifications.Notify(update.User, n)
|
||||
return
|
||||
{{< /highlight >}}
|
||||
|
||||
## Testing
|
||||
|
||||
The `mail` package provides a `Fake()` method which you should call in the `MainTest` functions of your package.
|
||||
If it was called, no mails are being sent and you can instead assert they have been sent with the `AssertSent` method.
|
||||
|
||||
When testing, you should call the `notifications.Fake()` method in the `TestMain` function of the package you want to test.
|
||||
This prevents any notifications from being sent and lets you assert a notifications has been sent like this:
|
||||
|
||||
{{< highlight golang >}}
|
||||
notifications.AssertSent(t, &ReminderDueNotification{})
|
||||
{{< /highlight >}}
|
||||
|
||||
## Example
|
||||
|
||||
Take a look at the [pkg/user/notifications.go](https://code.vikunja.io/api/src/branch/main/pkg/user/notifications.go) file for a good example.
|
|
@ -0,0 +1,38 @@
|
|||
---
|
||||
title: "Releasing a new Vikunja version"
|
||||
date: 2022-10-28T13:06:05+02:00
|
||||
draft: false
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "development"
|
||||
---
|
||||
|
||||
# Releasing a new Vikunja version
|
||||
|
||||
This checklist is a collection of all steps usually involved when releasing a new version of Vikunja.
|
||||
Not all steps are necessary for every release.
|
||||
|
||||
* Website update
|
||||
* New Features: If there are new features worth mentioning the feature page should be updated.
|
||||
* New Screenshots: If an overhaul of an existing feature happened so that it now looks different from the existing screenshot, a new one is required.
|
||||
* Generate changelogs (with git-cliff)
|
||||
* Frontend
|
||||
* API
|
||||
* Desktop
|
||||
* Tag a new version: Include the changelog for that version as the tag message
|
||||
* Frontend
|
||||
* API
|
||||
* Desktop
|
||||
* Once built: Prune the cloudflare cache so that the new versions show up at [dl.vikunja.io](https://dl.vikunja.io/)
|
||||
* Release Highlights Blogpost
|
||||
* Include a section about Vikunja in general (totally fine to copy one from the earlier blog posts)
|
||||
* New Features & Improvements: Mention bigger features, potentially with screenshots. Things like refactoring are sometimes also worth mentioning.
|
||||
* Publish
|
||||
* Reddit
|
||||
* Twitter
|
||||
* Mastodon
|
||||
* Chat
|
||||
* Newsletter
|
||||
* Forum
|
||||
* If features in the release were sponsored, send an email to relevant stakeholders
|
||||
* Update Vikunja Cloud version and other instances
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
date: "2019-02-12:00:00+02:00"
|
||||
title: "Project structure"
|
||||
title: "Project Structure"
|
||||
draft: false
|
||||
type: "doc"
|
||||
menu:
|
||||
|
@ -10,58 +10,22 @@ menu:
|
|||
|
||||
# Project structure
|
||||
|
||||
In general, this api repo has the following structure:
|
||||
This document explains what each package does.
|
||||
|
||||
* `docker`
|
||||
* `docs`
|
||||
* `pkg`
|
||||
* `caldav`
|
||||
* `cmd`
|
||||
* `config`
|
||||
* `db`
|
||||
* `fixtures`
|
||||
* `files`
|
||||
* `integration`
|
||||
* `log`
|
||||
* `mail`
|
||||
* `metrics`
|
||||
* `migration`
|
||||
* `models`
|
||||
* `modules`
|
||||
* `migration`
|
||||
* `handler`
|
||||
* `wunderlist`
|
||||
* `red`
|
||||
* `routes`
|
||||
* `api/v1`
|
||||
* `static`
|
||||
* `swagger`
|
||||
* `user`
|
||||
* `utils`
|
||||
* `version`
|
||||
* `REST-Tests`
|
||||
* `templates`
|
||||
* `vendor`
|
||||
|
||||
This document will explain what these mean and what you can find where.
|
||||
{{< table_of_contents >}}
|
||||
|
||||
## Root level
|
||||
|
||||
The root directory is where [the config file]({{< ref "../setup/config.md">}}), [Makefile]({{< ref "make.md">}}), license, drone config,
|
||||
The root directory is where [the config file]({{< ref "../setup/config.md">}}), [Magefile]({{< ref "mage.md">}}), license, drone config,
|
||||
application entry point (`main.go`) and so on are located.
|
||||
|
||||
## docker
|
||||
|
||||
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.
|
||||
This folder holds a simple caldav implementation which is responsible for the caldav feature.
|
||||
|
||||
### cmd
|
||||
|
||||
|
@ -73,10 +37,15 @@ To learn more about how to use this cli, see [the cli usage docs]({{< ref "../us
|
|||
|
||||
### 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.
|
||||
This package configures handling of Vikunja's runtime configuration.
|
||||
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.
|
||||
See also the [docs about adding a new configuration parameter]({{< ref "config.md" >}}).
|
||||
|
||||
### cron
|
||||
|
||||
See [how to add a cron task]({{< ref "cron.md" >}}).
|
||||
|
||||
### db
|
||||
|
||||
|
@ -95,22 +64,22 @@ See [integration tests]({{< ref "test.md" >}}#integration-tests) for more detail
|
|||
|
||||
### log
|
||||
|
||||
Similar to `config`, this will set up the logging, based on differen logging backends.
|
||||
Similar to `config`, this will set up the logging, based on different 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">}}).
|
||||
This package handles all mail sending. To learn how to send a mail, see [notifications]({{< ref "notifications.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">}}).
|
||||
To learn how it works and how to add new metrics, take a look at [how metrics work]({{< ref "metrics.md">}}).
|
||||
|
||||
### migration
|
||||
|
||||
This package handles all migrations.
|
||||
All migrations are stored and executed here.
|
||||
All migrations are stored and executed in this package.
|
||||
|
||||
To learn more, take a look at the [migrations docs]({{< ref "../development/db-migrations.md">}}).
|
||||
|
||||
|
@ -121,11 +90,35 @@ When adding new features or upgrading existing ones, that most likely happens he
|
|||
|
||||
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">}})
|
||||
* [Adding a feature]({{< ref "feature.md">}})
|
||||
* [Making calls to the database]({{< ref "database.md">}})
|
||||
|
||||
### modules
|
||||
|
||||
Everything that can have multiple implementations (like a task migrator from a third-party task provider) lives in a
|
||||
respective sub package in this package.
|
||||
|
||||
#### auth
|
||||
|
||||
Contains openid related authentication.
|
||||
|
||||
#### avatar
|
||||
|
||||
Contains all possible avatar providers a user can choose to set their avatar.
|
||||
|
||||
#### background
|
||||
|
||||
All project background providers are in sub-packages of this package.
|
||||
|
||||
#### dump
|
||||
|
||||
Handles everything related to the `dump` and `restore` commands of Vikunja.
|
||||
|
||||
#### keyvalue
|
||||
|
||||
A simple key-value store with an implementation for memory and redis.
|
||||
Can be used to cache values.
|
||||
|
||||
#### migration
|
||||
|
||||
See [writing a migrator]({{< ref "migration.md" >}}).
|
||||
|
@ -133,30 +126,29 @@ See [writing a migrator]({{< ref "migration.md" >}}).
|
|||
### red (redis)
|
||||
|
||||
This package initializes a connection to a redis server.
|
||||
This inizialization is automatically done at the startup of vikunja.
|
||||
This initialization 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.
|
||||
|
||||
**Note**: Only use this package directly if you have to use a direct redis connection.
|
||||
In most cases, using the `keyvalue` package is a better fit.
|
||||
|
||||
### 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">}}).
|
||||
This package defines all routes which are available for Vikunja clients to use.
|
||||
To add a new route, see [adding a new route]({{< ref "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.
|
||||
|
||||
### static
|
||||
|
||||
All static files generated by `make generate` live here.
|
||||
|
||||
### swagger
|
||||
|
||||
This is where the [generated]({{< ref "make.md#generate-swagger-definitions-from-code-comments">}} [api docs]({{< ref "../usage/api.md">}}) live.
|
||||
This is where the [generated]({{< ref "mage.md#generate-swagger-definitions-from-code-comments">}}) [api docs]({{< ref "../usage/api.md">}}) live.
|
||||
You usually don't need to touch this package.
|
||||
|
||||
### user
|
||||
|
@ -170,30 +162,9 @@ 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.
|
||||
See their function definitions for instructions on how to use them.
|
||||
|
||||
### version
|
||||
|
||||
The single purpouse of this package is to hold the current vikunja version which gets overridden through build flags
|
||||
each time `make release` or `make build` is run.
|
||||
It is a seperate package to avoid import cycles with other packages.
|
||||
|
||||
## 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.
|
||||
The single purpose of this package is to hold the current Vikunja version which gets overridden through build flags each time `mage release` or `mage build` is run.
|
||||
It is a separate package to avoid import cycles with other packages.
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
---
|
||||
date: "2019-02-12:00:00+02:00"
|
||||
title: "Modifying Swagger API Docs"
|
||||
draft: false
|
||||
type: "doc"
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "development"
|
||||
---
|
||||
|
||||
# Modifying swagger api docs
|
||||
|
||||
The api documentation is generated using [swaggo](https://github.com/swaggo/swag) from comments.
|
||||
|
||||
{{< table_of_contents >}}
|
||||
|
||||
## 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 project with all comments:
|
||||
|
||||
{{< highlight golang >}}
|
||||
type Project struct {
|
||||
// The unique, numeric id of this project.
|
||||
ID int64 `xorm:"bigint autoincr not null unique pk" json:"id" param:"project"`
|
||||
// The title of the project. You'll see this in the namespace overview.
|
||||
Title string `xorm:"varchar(250) not null" json:"title" valid:"required,runelength(1|250)" minLength:"1" maxLength:"250"`
|
||||
// The description of the project.
|
||||
Description string `xorm:"longtext null" json:"description"`
|
||||
// The unique project short identifier. Used to build task identifiers.
|
||||
Identifier string `xorm:"varchar(10) null" json:"identifier" valid:"runelength(0|10)" minLength:"0" maxLength:"10"`
|
||||
// The hex color of this project
|
||||
HexColor string `xorm:"varchar(6) null" json:"hex_color" valid:"runelength(0|6)" maxLength:"6"`
|
||||
|
||||
OwnerID int64 `xorm:"bigint INDEX not null" json:"-"`
|
||||
NamespaceID int64 `xorm:"bigint INDEX not null" json:"namespace_id" param:"namespace"`
|
||||
|
||||
// The user who created this project.
|
||||
Owner *user.User `xorm:"-" json:"owner" valid:"-"`
|
||||
|
||||
// Whether or not a project is archived.
|
||||
IsArchived bool `xorm:"not null default false" json:"is_archived" query:"is_archived"`
|
||||
|
||||
// The id of the file this project has set as background
|
||||
BackgroundFileID int64 `xorm:"null" json:"-"`
|
||||
// Holds extra information about the background set since some background providers require attribution or similar. If not null, the background can be accessed at /projects/{projectID}/background
|
||||
BackgroundInformation interface{} `xorm:"-" json:"background_information"`
|
||||
// Contains a very small version of the project background to use as a blurry preview until the actual background is loaded. Check out https://blurha.sh/ to learn how it works.
|
||||
BackgroundBlurHash string `xorm:"varchar(50) null" json:"background_blur_hash"`
|
||||
|
||||
// True if a project is a favorite. Favorite projects show up in a separate namespace. This value depends on the user making the call to the api.
|
||||
IsFavorite bool `xorm:"-" json:"is_favorite"`
|
||||
|
||||
// The subscription status for the user reading this project. You can only read this property, use the subscription endpoints to modify it.
|
||||
// Will only returned when retrieving one project.
|
||||
Subscription *Subscription `xorm:"-" json:"subscription,omitempty"`
|
||||
|
||||
// The position this project has when querying all projects. See the tasks.position property on how to use this.
|
||||
Position float64 `xorm:"double null" json:"position"`
|
||||
|
||||
// A timestamp when this project was created. You cannot change this value.
|
||||
Created time.Time `xorm:"created not null" json:"created"`
|
||||
// A timestamp when this project was last updated. You cannot change this value.
|
||||
Updated time.Time `xorm:"updated not null" json:"updated"`
|
||||
|
||||
web.CRUDable `xorm:"-" json:"-"`
|
||||
web.Rights `xorm:"-" json:"-"`
|
||||
}
|
||||
{{< /highlight >}}
|
||||
|
||||
## Documenting api Endpoints
|
||||
|
||||
All api routes should be documented with a comment above the handler function.
|
||||
When generating the api docs with mage, the swagger cli will pick these up and put them in a neat document.
|
||||
|
||||
A comment looks like this:
|
||||
|
||||
{{< highlight golang >}}
|
||||
// @Summary Login
|
||||
// @Description Logs a user in. Returns a JWT-Token to authenticate further requests.
|
||||
// @tags user
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param credentials body user.Login true "The login credentials"
|
||||
// @Success 200 {object} auth.Token
|
||||
// @Failure 400 {object} models.Message "Invalid user password model."
|
||||
// @Failure 412 {object} models.Message "Invalid totp passcode."
|
||||
// @Failure 403 {object} models.Message "Invalid username or password."
|
||||
// @Router /login [post]
|
||||
func Login(c echo.Context) error {
|
||||
// Handler logic
|
||||
}
|
||||
{{< /highlight >}}
|
|
@ -10,21 +10,50 @@ menu:
|
|||
|
||||
# Testing
|
||||
|
||||
You can run unit tests with [our `Makefile`]({{< ref "make.md">}}) with
|
||||
{{< table_of_contents >}}
|
||||
|
||||
## API Tests
|
||||
|
||||
The following parts are about the kinds of tests in the API package and how to run them.
|
||||
|
||||
### Prerequesites
|
||||
|
||||
To run any kind of test, you need to specify Vikunja's [root path](https://vikunja.io/docs/config-options/#rootpath).
|
||||
This is required to make sure all test fixtures are correctly loaded.
|
||||
|
||||
The easies way to do that is to set the environment variable `VIKUNJA_SERVICE_ROOTPATH` to the path where you cloned the working directory.
|
||||
|
||||
### Unit tests
|
||||
|
||||
To run unit tests with [mage]({{< ref "mage.md">}}), execute
|
||||
|
||||
{{< highlight bash >}}
|
||||
make test
|
||||
mage test:unit
|
||||
{{< /highlight >}}
|
||||
|
||||
In Vikunja, everything that is not an integration test counts as unit test - even if it accesses the db.
|
||||
This definition is a bit blurry, but we haven't found a better one yet.
|
||||
|
||||
### Integration tests
|
||||
|
||||
All integration tests live in `pkg/integrations`.
|
||||
You can run them by executing `mage test:integration`.
|
||||
|
||||
The integration tests use the same config and fixtures as the unit tests and therefor have the same options available,
|
||||
see at the beginning of this document.
|
||||
|
||||
To run integration tests, use `mage test:integration`.
|
||||
|
||||
### 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.
|
||||
You can run tests with all available config variables if you want, enabling you to run tests for a lot of scenarios.
|
||||
We use this in CI to run all tests with different databases.
|
||||
|
||||
To use the normal config set the enviroment variable `VIKUNJA_TESTS_USE_CONFIG=1`.
|
||||
To use the normal config set the environment variable `VIKUNJA_TESTS_USE_CONFIG=1`.
|
||||
|
||||
### Show sql queries
|
||||
### Showing sql queries
|
||||
|
||||
When `UNIT_TESTS_VERBOSE=1` is set, all sql queries will be shown when tests are run.
|
||||
When the environment variable `UNIT_TESTS_VERBOSE=1` is set, all sql queries will be shown during the test run.
|
||||
|
||||
### Fixtures
|
||||
|
||||
|
@ -33,17 +62,7 @@ These fixtures are defined in `pkg/models/fixtures` in YAML-Files which represen
|
|||
|
||||
When you add a new test case which requires new database entries to test against, update these files.
|
||||
|
||||
# Integration tests
|
||||
|
||||
All integration tests live in `pkg/integrations`.
|
||||
You can run them by executing `make integration-test`.
|
||||
|
||||
The integration tests use the same config and fixtures as the unit tests and therefor have the same options available,
|
||||
see at the beginning of this document.
|
||||
|
||||
To run integration tests, use `make integration-test`.
|
||||
|
||||
# Initializing db fixtures when writing tests
|
||||
#### Initializing db fixtures when writing tests
|
||||
|
||||
All db fixtures for all tests live in the `pkg/db/fixtures/` folder as yaml files.
|
||||
Each file has the same name as the table the fixtures are for.
|
||||
|
@ -52,19 +71,39 @@ You should put new fixtures in this folder.
|
|||
When initializing db fixtures, you are responsible for defining which tables your package needs in your test init function.
|
||||
Usually, this is done as follows (this code snippet is taken from the `user` package):
|
||||
|
||||
```go
|
||||
{{< highlight go >}}
|
||||
err = db.InitTestFixtures("users")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
```
|
||||
{{< /highlight >}}
|
||||
|
||||
In your actual tests, you then load the fixtures into the in-memory db like so:
|
||||
|
||||
```go
|
||||
{{< highlight go >}}
|
||||
db.LoadAndAssertFixtures(t)
|
||||
```
|
||||
{{< /highlight >}}
|
||||
|
||||
This will load all fixtures you defined in your test init method.
|
||||
You should always use this method to load fixtures, the only exception is when your package tests require extra test
|
||||
fixtures other than db fixtures (like files).
|
||||
|
||||
## Frontend tests
|
||||
|
||||
The frontend has end to end tests with Cypress that use a Vikunja instance and drive a browser against it.
|
||||
Check out the docs [in the frontend repo](https://kolaente.dev/vikunja/frontend/src/branch/main/cypress/README.md) about how they work and how to get them running.
|
||||
|
||||
### Unit Tests
|
||||
|
||||
To run the frontend unit tests, run
|
||||
|
||||
{{< highlight bash >}}
|
||||
pnpm run test:unit
|
||||
{{< /highlight >}}
|
||||
|
||||
The frontend also has a watcher available that re-runs all unit tests every time you change something.
|
||||
To use it, simply run
|
||||
|
||||
{{< highlight bash >}}
|
||||
pnpm run test:unit-watch
|
||||
{{< /highlight >}}
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
---
|
||||
title: "German Translation Instructions"
|
||||
date: 2021-06-23T23:47:34+02:00
|
||||
draft: false
|
||||
---
|
||||
|
||||
# German Translation Instructions
|
||||
|
||||
<div class="notification is-warning">
|
||||
<b>NOTE:</b> This document contains translation instructions specific to the german translation of Vikunja.
|
||||
For instructions applicable to all languages, check out the <a href="{{< ref "./translations.md">}}">general translation instructions</a>.
|
||||
</div>
|
||||
|
||||
{{< table_of_contents >}}
|
||||
|
||||
## Allgemein
|
||||
|
||||
Anrede: Wenig förmlich:
|
||||
|
||||
* “Du”-Form
|
||||
* Keine “Amtsdeusch“-Umschreibungen, einfach so als ob man den Nutzer direkt persönlich ansprechen würde
|
||||
|
||||
Genauer definiert:
|
||||
|
||||
* “falsch” anstatt “nicht korrekt/inkorrekt”
|
||||
* “Wende dich an …” anstatt “kontaktiere …”
|
||||
* In derselben Zeit übersetzen (sonst wird aus dem englischen “is“ das deutsche “war”)
|
||||
* Richtige Anführungszeichen verwenden. Also `„“` statt `''` oder `'` oder ` oder ´
|
||||
* `„` für beginnende Anführungszeichen, `“` für schließende Anführungszeichen
|
||||
|
||||
Es gelten Artikel und Worttrennungen aus dem [Duden](https://duden.de).
|
||||
|
||||
## Formulierungen
|
||||
|
||||
* `Account` statt `Konto`.
|
||||
* `TOTP` immer als ein Wort und Groß.
|
||||
* `CalDAV` immer so.
|
||||
* `löschen` oder `entfernen` je nach Kontext. Wenn etwas *gelöscht* wird, existiert das gelöschte Objekt und danach
|
||||
nicht mehr und hat evtl. andere Objekte mitgelöscht (z.B. eine Aufgabe). Wird etwas *entfernt*, bezieht sich das
|
||||
meistens auf die Beziehung zu einem anderen Objekt. Das entfernte Objekt existiert danach immernoch, z.B. beim
|
||||
Entfernen eine:r Nutzer:in aus einem Team.
|
||||
* Analog zu `löschen` oder `entfernen` gilt ähnliches für `hinzufügen` oder `erstellen`. Eine Aufgabe wird *erstellt*,
|
||||
aber ein:e Nutzer:in nur zu einem Team *hinzugefügt*.
|
||||
* `Anmeldename` anstatt `Benutzer:innenname`
|
||||
|
||||
## Formulierungen in Modals und Buttons
|
||||
|
||||
Es sollten die gleichen Formulierungen auf Buttons und Modals verwendet werden.
|
||||
|
||||
Beispiel: Wenn der Button mit `löschen` beschriftet ist, sollte im Modal die Frage
|
||||
lauten `Willst du das wirklich löschen?` und nicht `Willst du das wirklich entfernen?`. Gleiches gilt für
|
||||
Erfolgs/Fehlermeldungen nach der Aktion.
|
||||
|
||||
## Gendern
|
||||
|
||||
Wo möglich, sollte eine geschlechtsneutrale Anrede verwendet werden. Falls diese sehr umständlich würden (siehe oben
|
||||
„Amtsdeutsch-Umschreibungen“), soll mit *Doppelpunkt* gegendert werden.
|
||||
|
||||
Beispiel: „Benutzer:in“
|
||||
|
||||
## Trennungen
|
||||
|
||||
* E-Mail-Adresse (siehe Duden)
|
||||
|
||||
## Wörter und Ausdrücke
|
||||
|
||||
| Englisches Original | Verwendung in deutscher Übersetzung |
|
||||
| ------------------- | -------------------- |
|
||||
| Bucket | Spalte |
|
||||
| Namespace | Namespace |
|
||||
| Link Share | Linkfreigabe |
|
||||
| Username | Anmeldename |
|
||||
|
||||
## Weiterführende Links
|
||||
|
||||
* https://docs.translatehouse.org/projects/localization-guide/en/latest/guide/translation_guidelines_german.html
|
|
@ -0,0 +1,54 @@
|
|||
---
|
||||
title: "Translations"
|
||||
date: 2021-06-23T22:52:06+02:00
|
||||
draft: false
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "development"
|
||||
---
|
||||
|
||||
# Translations
|
||||
|
||||
This document provides documentation about how to translate Vikunja.
|
||||
|
||||
{{< table_of_contents >}}
|
||||
|
||||
## Where to translate
|
||||
|
||||
Translation happens at [crowdin](https://crowdin.com/project/vikunja).
|
||||
|
||||
Currently, only the frontend (and by extension, the desktop app) is translatable.
|
||||
|
||||
## Translation Instructions
|
||||
|
||||
> These are the instructions for translating Vikunja in another language.
|
||||
> For information about how to add new translation strings, see below.
|
||||
|
||||
For all languages these translation guidelines should be applied when translating:
|
||||
|
||||
* Use a less-formal style, as if you were talking to a friend.
|
||||
* If the source string contains characters like `&` or `…`, the translated string should contain them as well.
|
||||
|
||||
More specific instructions for some languages can be found below.
|
||||
|
||||
### Wrong translation strings
|
||||
|
||||
If you encounter a wrong original translation string while translating, please don't correct it in the translation.
|
||||
Instead, translate it to reflect the original meaning in the translated string but add a comment under the source string to discuss potential changes.
|
||||
|
||||
### Language-specific instructions
|
||||
|
||||
* [German]({{< ref "./translation-instructions-german.md">}})
|
||||
|
||||
## How to add new translation strings
|
||||
|
||||
All translation strings are stored in `src/i18n/lang/`.
|
||||
New strings should be added only in the `en.json` file.
|
||||
Strings in other languages will be synced through [crowdin](https://crowdin.com/project/vikunja) and should not be added directly as a PR/commit in the frontend repo.
|
||||
|
||||
## Requesting a new language
|
||||
|
||||
If you want to start translating Vikunja in a language not yet available in Vikunja, please request the language through the crowdin interface.
|
||||
If you have issues with this or need a discussion before doing so, please [contact us](https://vikunja.io/contact/) or [start a discussion in the forum](https://community.vikunja.io).
|
||||
|
||||
Once at least 50% of all translation strings are translated and approved, they will be added and distributed with the Vikunja frontend for users to select and use Vikunja with them.
|
|
@ -1,38 +0,0 @@
|
|||
---
|
||||
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.
|
|
@ -1,84 +0,0 @@
|
|||
---
|
||||
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).
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
---
|
||||
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.
|
|
@ -1,47 +0,0 @@
|
|||
---
|
||||
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 >}}
|
|
@ -10,10 +10,17 @@ menu:
|
|||
|
||||
# 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.
|
||||
There are two parts you need to back up: The database and attachment files.
|
||||
|
||||
## MySQL
|
||||
{{< table_of_contents >}}
|
||||
|
||||
## Files
|
||||
|
||||
To back up attachments and other files, it is enough to copy them [from the attachments folder]({{< ref "config.md" >}}#basepath) to some other place.
|
||||
|
||||
## Database
|
||||
|
||||
### MySQL
|
||||
|
||||
To create a backup from mysql use the `mysqldump` command:
|
||||
|
||||
|
@ -29,7 +36,7 @@ To restore it, simply pipe it back into the `mysql` command:
|
|||
mysql -u <user> -p -h <db-host> <database> < vkunja-backup.sql
|
||||
{{< /highlight >}}
|
||||
|
||||
## PostgreSQL
|
||||
### PostgreSQL
|
||||
|
||||
To create a backup from PostgreSQL use the `pg_dump` command:
|
||||
|
||||
|
@ -47,6 +54,6 @@ psql -U <user> -h <db-host> <database> < vikunja-backup.sql
|
|||
|
||||
For more information, please visit the [relevant PostgreSQL documentation](https://www.postgresql.org/docs/12/backup-dump.html).
|
||||
|
||||
## SQLite
|
||||
### SQLite
|
||||
|
||||
To backup sqllite databases, it is enough to copy the database elsewhere.
|
||||
To back up sqllite databases, it is enough to copy the [database file]({{< ref "config.md" >}}#path) to somewhere else.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
---
|
||||
date: "2019-02-12:00:00+02:00"
|
||||
date: "2022-09-21:00:00+02:00"
|
||||
title: "Build from sources"
|
||||
draft: false
|
||||
type: "doc"
|
||||
|
@ -10,20 +10,31 @@ menu:
|
|||
|
||||
# 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:
|
||||
To completely build Vikunja from source, you need to build the api and frontend.
|
||||
|
||||
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.
|
||||
{{< table_of_contents >}}
|
||||
|
||||
*Note:* Static ressources such as email templates are built into the binary.
|
||||
For these to work, you may need to run `make generate` before building the vikunja binary.
|
||||
When builing entirely with `make`, you dont need to do this, `make generate` will be run automatically when running `make build`.
|
||||
## API
|
||||
|
||||
# Build for different architectures
|
||||
The Vikunja API has no other dependencies than go itself.
|
||||
That means compiling it boils down to these steps:
|
||||
|
||||
To build for other platforms and architectures than the one you're currently on, simply run `make release` or `make release-{linux|windows|darwin}`.
|
||||
1. Make sure [Go](https://golang.org/doc/install) is properly installed on your system. You'll need at least Go `1.19`.
|
||||
2. Make sure [Mage](https://magefile.org) is properly installed on your system.
|
||||
3. Clone the repo with `git clone https://code.vikunja.io/api` and switch into the directory.
|
||||
4. Run `mage 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.
|
||||
|
||||
More options are available, please refer to the [makefile docs]({{< ref "../development/make.md">}}) for more details.
|
||||
### Build for different architectures
|
||||
|
||||
To build for other platforms and architectures than the one you're currently on, simply run `mage release:release` or `mage release:{linux|windows|darwin}`.
|
||||
|
||||
More options are available, please refer to the [magefile docs]({{< ref "../development/mage.md">}}) for more details.
|
||||
|
||||
## Frontend
|
||||
|
||||
The code for the frontend is located at [code.vikunja.io/frontend](https://code.vikunja.io/frontend).
|
||||
|
||||
1. Make sure you have [pnpm](https://pnpm.io/installation) properly installed on your system.
|
||||
2. Clone the repo with `git clone https://code.vikunja.io/frontend` and switch into the directory.
|
||||
3. Install all dependencies with `pnpm install`
|
||||
4. Build the frontend with `pnpm run build`. This will result in a static js bundle in the `dist/` folder which you can deploy.
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,234 @@
|
|||
---
|
||||
date: "2020-05-24:00:00+02:00"
|
||||
title: "Docker Walkthrough"
|
||||
draft: false
|
||||
type: "doc"
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "setup"
|
||||
---
|
||||
|
||||
# Setup with docker from start to finish
|
||||
|
||||
This tutorial assumes basic knowledge of docker.
|
||||
It is aimed at beginners and should get you up and running quickly.
|
||||
|
||||
We'll use [docker compose](https://docs.docker.com/compose/) to make handling the bunch of containers easier.
|
||||
|
||||
> If you have any issues setting up vikunja, please don't hesitate to reach out to us via [matrix](https://riot.im/app/#/room/!dCRiCiLaCCFVNlDnYs:matrix.org?via=matrix.org), the [community forum](https://community.vikunja.io/) or even [email](mailto:hello@vikunja.io).
|
||||
|
||||
{{< table_of_contents >}}
|
||||
|
||||
## Preparations (optional)
|
||||
|
||||
Create a directory for the project where all data and the compose file will live in.
|
||||
|
||||
## Create all necessary files
|
||||
|
||||
Create a `docker-compose.yml` file with the following contents in your directory:
|
||||
|
||||
{{< highlight yaml >}}
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
db:
|
||||
image: mariadb:10
|
||||
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: supersecret
|
||||
MYSQL_USER: vikunja
|
||||
MYSQL_PASSWORD: secret
|
||||
MYSQL_DATABASE: vikunja
|
||||
volumes:
|
||||
- ./db:/var/lib/mysql
|
||||
restart: unless-stopped
|
||||
api:
|
||||
image: vikunja/api
|
||||
environment:
|
||||
VIKUNJA_DATABASE_HOST: db
|
||||
VIKUNJA_DATABASE_PASSWORD: secret
|
||||
VIKUNJA_DATABASE_TYPE: mysql
|
||||
VIKUNJA_DATABASE_USER: vikunja
|
||||
VIKUNJA_DATABASE_DATABASE: vikunja
|
||||
VIKUNJA_SERVICE_JWTSECRET: <a super secure random secret>
|
||||
VIKUNJA_SERVICE_FRONTENDURL: https://<your public frontend url with slash>/
|
||||
volumes:
|
||||
- ./files:/app/vikunja/files
|
||||
depends_on:
|
||||
- db
|
||||
restart: unless-stopped
|
||||
frontend:
|
||||
image: vikunja/frontend
|
||||
restart: unless-stopped
|
||||
proxy:
|
||||
image: nginx
|
||||
ports:
|
||||
- 80:80
|
||||
volumes:
|
||||
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
|
||||
depends_on:
|
||||
- api
|
||||
- frontend
|
||||
restart: unless-stopped
|
||||
{{< /highlight >}}
|
||||
|
||||
This defines four services, each with their own container:
|
||||
|
||||
* An api service which runs the vikunja api. Most of the core logic lives here.
|
||||
* The frontend which will make vikunja actually usable for most people.
|
||||
* A database container which will store all projects, tasks, etc. We're using mariadb here, but you're free to use mysql or postgres if you want.
|
||||
* A proxy service which makes the frontend and api available on the same port, redirecting all requests to `/api` to the api container.
|
||||
If you already have a proxy on your host, you may want to check out the [reverse proxy examples]() to use that.
|
||||
By default, it uses port 80 on the host.
|
||||
To change to something different, you'll need to change the `ports` section in the service definition.
|
||||
The number before the colon is the host port - This is where you can reach vikunja from the outside once all is up and running.
|
||||
|
||||
For the proxy service we'll need another bit of configuration.
|
||||
Create an `nginx.conf` in your directory (next to the `docker-compose.yml` file) and add the following contents to it:
|
||||
|
||||
{{< highlight conf >}}
|
||||
server {
|
||||
listen 80;
|
||||
|
||||
location / {
|
||||
proxy_pass http://frontend:80;
|
||||
}
|
||||
|
||||
location ~* ^/(api|dav|\.well-known)/ {
|
||||
proxy_pass http://api:3456;
|
||||
client_max_body_size 20M;
|
||||
}
|
||||
}
|
||||
{{< /highlight >}}
|
||||
|
||||
This is a simple proxy configuration which will forward all requests to `/api/` to the api container and everything else to the frontend.
|
||||
|
||||
<div class="notification is-info">
|
||||
<b>NOTE:</b> Even if you want to make your installation available under a different port, you don't need to change anything in this configuration.
|
||||
</div>
|
||||
|
||||
<div class="notification is-warning">
|
||||
<b>NOTE:</b> If you change the max upload size in Vikunja's settings, you'll need to also change the <code>client_max_body_size</code> in the nginx proxy config.
|
||||
</div>
|
||||
|
||||
## Run it
|
||||
|
||||
Run `sudo docker-compose up` in your directory and take a look at the output you get.
|
||||
When first started, Vikunja will set up the database and run all migrations etc.
|
||||
Once it is ready, you should see a message like this one in your console:
|
||||
|
||||
```
|
||||
api_1 | 2020-05-24T11:15:37.560386009Z: INFO ▶ cmd/func1 025 Vikunja version 0.13.1+19-e9bc3246ce, built at Sun, 24 May 2020 11:10:36 +0000
|
||||
api_1 | ⇨ http server started on [::]:3456
|
||||
```
|
||||
|
||||
This indicates all setup has been successful.
|
||||
If you get any errors, see below:
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
Vikunja might not run on the first try.
|
||||
There are a few potential issues that could be causing this.
|
||||
|
||||
#### No connection to the database
|
||||
|
||||
Indicated by an error message like this one from the api container:
|
||||
|
||||
```
|
||||
2020/05/23 15:37:59 Config File "config" Not Found in "[/app/vikunja /etc/vikunja /app/vikunja/.config/vikunja]"
|
||||
2020/05/23 15:37:59 Using default config.
|
||||
2020-05-23T15:37:59.974435725Z: CRITICAL ▶ migration/Migrate 002 Migration failed: dial tcp 172.19.0.2:3306: connect: connection refused
|
||||
```
|
||||
|
||||
Especially when using mysql, this can happen on first start, because the mysql database container will take a few seconds to start.
|
||||
Vikunja does not know the container is not ready, therefore it will just try to connect to the db, fail since it is not ready and exit.
|
||||
|
||||
If you're using the docker compose example from above, you may notice the `restart: unless-stopped` option at the api service.
|
||||
This tells docker to restart the api container if it exits, unless you explicitly stop it.
|
||||
Therefore, it should "magically fix itself" by automatically restarting the container.
|
||||
|
||||
After a few seconds (or minutes) you should see a log message like this one from the mariadb container:
|
||||
|
||||
```
|
||||
2020-05-24 11:42:15 0 [Note] mysqld: ready for connections.
|
||||
Version: '10.4.12-MariaDB-1:10.4.12+maria~bionic' socket: '/var/run/mysqld/mysqld.sock' port: 3306 mariadb.org binary distribution
|
||||
```
|
||||
|
||||
The next restart of Vikunja should be successful.
|
||||
If not, there might be a different error or a bug with Vikunja, please reach out to us in that case.
|
||||
|
||||
(If you have an idea about how we could improve this, we'd like to hear it!)
|
||||
|
||||
#### "Not a directory"
|
||||
|
||||
If you get an error like this one:
|
||||
|
||||
```
|
||||
ERROR: for vikunja_proxy_1 Cannot start service proxy: OCI runtime create failed: container_linux.go:349: starting container process caused "process_linux.go:449: container init caused \"rootfs_linux.go:58: mounting \\\"vikunja/nginx.conf\\\" to rootfs \\\"/var/lib/docker/overlay2/9c8b8f9419c29dad0d1233fbb0a3c36cf403dabd7a55d6f0a47b0c1dd6029994/merged\\\" at \\\"/var/lib/docker/overlay2/9c8b8f9419c29dad0d1233fbb0a3c36cf403dabd7a55d6f0a47b0c1dd6029994/merged/etc/nginx/conf.d/default.conf\\\" caused \\\"not a directory\\\"\"": unknown: Are you trying to mount a directory onto a file (or vice-versa)? Check if the specified host path exists and is the expected type
|
||||
```
|
||||
|
||||
this means docker tried to mount a directory from the host to a file in the container.
|
||||
This can happen if you did not create the `nginx.conf` file.
|
||||
Because there is a volume mount for it in the `docker-compose.yml`, Docker will create a folder because non exists, assuming you want to mount a folder into the container.
|
||||
|
||||
To fix this, create the file and restart the containers again.
|
||||
|
||||
#### Migration failed: commands out of sync
|
||||
|
||||
If you get an error like this one:
|
||||
|
||||
```
|
||||
2020/05/23 15:53:38 Config File "config" Not Found in "[/app/vikunja /etc/vikunja /app/vikunja/.config/vikunja]"
|
||||
2020/05/23 15:53:38 Using default config.
|
||||
2020-05-23T15:53:38.762747276Z: CRITICAL ▶ migration/Migrate 002 Migration failed: commands out of sync. Did you run multiple statements at once?
|
||||
```
|
||||
|
||||
This is a mysql issue.
|
||||
Currently, we don't have a better solution than to completely wipe the database files and start over.
|
||||
To do this, first stop everything by running `sudo docker-compose down`, then remove the `db/` folder in your current folder with `sudo rm -rf db` and start the whole stack again with `sudo docker-compose up -d`.
|
||||
|
||||
## Try it
|
||||
|
||||
Head over to `http://<host-ip or url>/api/v1/info` in a browser.
|
||||
You should see something like this:
|
||||
|
||||
{{< highlight json >}}
|
||||
{
|
||||
"version": "0.13.1+19-e9bc3246ce",
|
||||
"frontend_url": "http://localhost:8080/",
|
||||
"motd": "test",
|
||||
"link_sharing_enabled": true,
|
||||
"max_file_size": "20MB",
|
||||
"registration_enabled": true,
|
||||
"available_migrators": [
|
||||
"todoist"
|
||||
],
|
||||
"task_attachments_enabled": true
|
||||
}
|
||||
{{< /highlight >}}
|
||||
|
||||
This shows you can reach the api through the api proxy.
|
||||
|
||||
Now head over to `http://<host-ip or url>/` which should show the login mask.
|
||||
|
||||
## Make it persistent
|
||||
|
||||
Currently, Vikunja runs in foreground in your terminal.
|
||||
For a real-world scenario this is not the best way.
|
||||
|
||||
Back in your terminal, stop the stack by pressing `CTRL-C` on your keyboard.
|
||||
Then run `sudo docker-compose up -d` in your again.
|
||||
The `-d` flag at the end of the command will tell docker to run the containers in the background.
|
||||
If you need to check the logs after that, you can run `sudo docker-compose logs`.
|
||||
|
||||
Vikunja does not have any default users, you'll need to register and account.
|
||||
After that, you can use it.
|
||||
|
||||
## Tear it all down
|
||||
|
||||
If you want to completely stop all containers run `sudo docker-compose down` in your terminal.
|
||||
|
||||
## Improve this guide
|
||||
|
||||
We'll happily accept suggestions and improvements for this guide.
|
||||
Please [reach out to us](https://vikunja.io/contact/) if you have any.
|
|
@ -10,16 +10,151 @@ menu:
|
|||
|
||||
# Full docker example
|
||||
|
||||
This docker compose configuration will run Vikunja with backend and frontend with a mariadb as database.
|
||||
This docker compose configuration will run Vikunja with backend and frontend with a mariadb database.
|
||||
It uses an nginx container or traefik on the host to proxy backend and frontend into a single port.
|
||||
|
||||
For all available configuration options, see [configuration]({{< ref "config.md">}}).
|
||||
|
||||
## Example with traefik
|
||||
<div class="notification is-warning">
|
||||
<b>NOTE:</b> If you intend to run Vikunja with mysql and/or to use non-latin characters
|
||||
<a href="{{< ref "utf-8.md">}}">make sure your db is utf-8 compatible</a>.<br/>
|
||||
All examples on this page already reflect this and do not require additional work.
|
||||
</div>
|
||||
|
||||
This example assumes [traefik](https://traefik.io) in version 1 installed and configured to [use docker as a configuration provider](https://docs.traefik.io/v1.7/configuration/backends/docker/).
|
||||
{{< table_of_contents >}}
|
||||
|
||||
### Without redis
|
||||
## Redis
|
||||
|
||||
While Vikunja has support to use redis as a caching backend, you'll probably not need it unless you're using Vikunja with more than a handful of users.
|
||||
|
||||
To use redis, you'll need to add this to the config examples below:
|
||||
|
||||
{{< highlight yaml >}}
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
api:
|
||||
image: vikunja/api
|
||||
environment:
|
||||
VIKUNJA_REDIS_ENABLED: 1
|
||||
VIKUNJA_REDIS_HOST: 'redis:6379'
|
||||
VIKUNJA_CACHE_ENABLED: 1
|
||||
VIKUNJA_CACHE_TYPE: redis
|
||||
volumes:
|
||||
- ./files:/app/vikunja/files
|
||||
redis:
|
||||
image: redis
|
||||
{{< /highlight >}}
|
||||
|
||||
## PostgreSQL
|
||||
|
||||
Vikunja supports postgres, mysql and sqlite as a database backend. The examples on this page use mysql with a mariadb container.
|
||||
To use postgres as a database backend, change the `db` section of the examples to this:
|
||||
|
||||
{{< highlight yaml >}}
|
||||
db:
|
||||
image: postgres:13
|
||||
environment:
|
||||
POSTGRES_PASSWORD: secret
|
||||
POSTGRES_USER: vikunja
|
||||
volumes:
|
||||
- ./db:/var/lib/postgresql/data
|
||||
restart: unless-stopped
|
||||
{{< /highlight >}}
|
||||
|
||||
You'll also need to change the `VIKUNJA_DATABASE_TYPE` to `postgres` on the api container declaration.
|
||||
|
||||
<div class="notification is-warning">
|
||||
<b>NOTE:</b> The mariadb container can sometimes take a while to initialize, especially on the first run. During this time, the api container will fail to start at all. It will automatically restart every few seconds.
|
||||
</div>
|
||||
|
||||
## Sqlite
|
||||
|
||||
Vikunja supports postgres, mysql and sqlite as a database backend. The examples on this page use mysql with a mariadb container.
|
||||
To use sqlite as a database backend, change the `api` section of the examples to this:
|
||||
|
||||
{{< highlight yaml >}}
|
||||
api:
|
||||
image: vikunja/api
|
||||
environment:
|
||||
VIKUNJA_SERVICE_JWTSECRET: <a super secure random secret>
|
||||
VIKUNJA_SERVICE_FRONTENDURL: http://<your public frontend url with slash>/
|
||||
# Note the default path is /app/vikunja/vikunja.db This moves to a different folder so you can use a volume and store this outside of the container so state is persisted even if container destroyed.
|
||||
VIKUNJA_DATABASE_PATH: /db/vikunja.db
|
||||
ports:
|
||||
- 3456:3456
|
||||
volumes:
|
||||
- ./files:/app/vikunja/files
|
||||
- ./db:/db
|
||||
restart: unless-stopped
|
||||
{{< /highlight >}}
|
||||
|
||||
The default path Vikunja uses for sqlite is relative to the binary, which in the docker container would be `/app/vikunja/vikunja.db`. This config moves to a different folder using `VIKUNJA_DATABASE_PATH` so you can use a volume at `/db` and store this outside of the container so state is persisted even if container destroyed.
|
||||
|
||||
You'll also need to remove or change the `VIKUNJA_DATABASE_TYPE` to `sqlite` on the api container declaration.
|
||||
|
||||
You can also remove the db section.
|
||||
|
||||
<div class="notification is-warning">
|
||||
<b>NOTE:</b> If you'll use your instance with more than a handful of users, we recommend using mysql or postgres.
|
||||
</div>
|
||||
|
||||
## Example without any proxy
|
||||
|
||||
This example lets you host Vikunja without any reverse proxy in front of it. This is the absolute minimum configuration you need to get something up and running. If you want to host Vikunja on one single port instead of two different ones or need tls termination, check out one of the other examples.
|
||||
|
||||
Note that you need to change the `VIKUNJA_API_URL` environment variable to the ip (the docker host you're running this on) is reachable at. Because the browser you'll use to access the Vikunja frontend uses that url to make the requests, it has to be able to reach that ip + port from the outside. Putting everything in a private network won't work.
|
||||
|
||||
{{< highlight yaml >}}
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
db:
|
||||
image: mariadb:10
|
||||
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: supersecret
|
||||
MYSQL_USER: vikunja
|
||||
MYSQL_PASSWORD: secret
|
||||
MYSQL_DATABASE: vikunja
|
||||
volumes:
|
||||
- ./db:/var/lib/mysql
|
||||
restart: unless-stopped
|
||||
api:
|
||||
image: vikunja/api
|
||||
environment:
|
||||
VIKUNJA_DATABASE_HOST: db
|
||||
VIKUNJA_DATABASE_PASSWORD: secret
|
||||
VIKUNJA_DATABASE_TYPE: mysql
|
||||
VIKUNJA_DATABASE_USER: vikunja
|
||||
VIKUNJA_DATABASE_DATABASE: vikunja
|
||||
VIKUNJA_SERVICE_JWTSECRET: <a super secure random secret>
|
||||
VIKUNJA_SERVICE_FRONTENDURL: http://<your public frontend url with slash>/
|
||||
ports:
|
||||
- 3456:3456
|
||||
volumes:
|
||||
- ./files:/app/vikunja/files
|
||||
depends_on:
|
||||
- db
|
||||
restart: unless-stopped
|
||||
frontend:
|
||||
image: vikunja/frontend
|
||||
ports:
|
||||
- 80:80
|
||||
environment:
|
||||
VIKUNJA_API_URL: http://<your-ip-here>:3456/api/v1
|
||||
restart: unless-stopped
|
||||
{{< /highlight >}}
|
||||
|
||||
## Example with traefik 2
|
||||
|
||||
This example assumes [traefik](https://traefik.io) version 2 installed and configured to [use docker as a configuration provider](https://docs.traefik.io/providers/docker/).
|
||||
|
||||
We also make a few assumptions here which you'll most likely need to adjust for your traefik setup:
|
||||
|
||||
* Your domain is `vikunja.example.com`
|
||||
* The entrypoint you want to make vikunja available from is called `https`
|
||||
* The tls cert resolver is called `acme`
|
||||
|
||||
{{< highlight yaml >}}
|
||||
version: '3'
|
||||
|
@ -33,6 +168,8 @@ services:
|
|||
VIKUNJA_DATABASE_TYPE: mysql
|
||||
VIKUNJA_DATABASE_USER: vikunja
|
||||
VIKUNJA_DATABASE_DATABASE: vikunja
|
||||
VIKUNJA_SERVICE_JWTSECRET: <a super secure random secret>
|
||||
VIKUNJA_SERVICE_FRONTENDURL: https://<your public frontend url with slash>/
|
||||
volumes:
|
||||
- ./files:/app/vikunja/files
|
||||
networks:
|
||||
|
@ -42,25 +179,24 @@ services:
|
|||
- db
|
||||
restart: unless-stopped
|
||||
labels:
|
||||
- "traefik.docker.network=web"
|
||||
- "traefik.enable=true"
|
||||
- "traefik.frontend.rule=Host:vikunja.example.com;PathPrefix:/api/v1"
|
||||
- "traefik.port=3456"
|
||||
- "traefik.protocol=http"
|
||||
- "traefik.http.routers.vikunja-api.rule=Host(`vikunja.example.com`) && (PathPrefix(`/api/v1`) || PathPrefix(`/dav/`) || PathPrefix(`/.well-known/`))"
|
||||
- "traefik.http.routers.vikunja-api.entrypoints=https"
|
||||
- "traefik.http.routers.vikunja-api.tls.certResolver=acme"
|
||||
frontend:
|
||||
image: vikunja/frontend
|
||||
labels:
|
||||
- "traefik.docker.network=web"
|
||||
- "traefik.enable=true"
|
||||
- "traefik.frontend.rule=Host:vikunja.example.com;PathPrefix:/"
|
||||
- "traefik.port=80"
|
||||
- "traefik.protocol=http"
|
||||
- "traefik.http.routers.vikunja-frontend.rule=Host(`vikunja.example.com`)"
|
||||
- "traefik.http.routers.vikunja-frontend.entrypoints=https"
|
||||
- "traefik.http.routers.vikunja-frontend.tls.certResolver=acme"
|
||||
networks:
|
||||
- web
|
||||
- default
|
||||
restart: unless-stopped
|
||||
db:
|
||||
image: mariadb:10
|
||||
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: supersupersecret
|
||||
MYSQL_USER: vikunja
|
||||
|
@ -76,7 +212,9 @@ networks:
|
|||
external: true
|
||||
{{< /highlight >}}
|
||||
|
||||
### With redis
|
||||
## Example with traefik 1
|
||||
|
||||
This example assumes [traefik](https://traefik.io) in version 1 installed and configured to [use docker as a configuration provider](https://docs.traefik.io/v1.7/configuration/backends/docker/).
|
||||
|
||||
{{< highlight yaml >}}
|
||||
version: '3'
|
||||
|
@ -90,10 +228,8 @@ services:
|
|||
VIKUNJA_DATABASE_TYPE: mysql
|
||||
VIKUNJA_DATABASE_USER: vikunja
|
||||
VIKUNJA_DATABASE_DATABASE: vikunja
|
||||
VIKUNJA_REDIS_ENABLED: 1
|
||||
VIKUNJA_REDIS_HOST: 'redis:6379'
|
||||
VIKUNJA_CACHE_ENABLED: 1
|
||||
VIKUNJA_CACHE_TYPE: redis
|
||||
VIKUNJA_SERVICE_JWTSECRET: <a super secure random secret>
|
||||
VIKUNJA_SERVICE_FRONTENDURL: https://<your public frontend url with slash>/
|
||||
volumes:
|
||||
- ./files:/app/vikunja/files
|
||||
networks:
|
||||
|
@ -101,12 +237,11 @@ services:
|
|||
- default
|
||||
depends_on:
|
||||
- db
|
||||
- redis
|
||||
restart: unless-stopped
|
||||
labels:
|
||||
- "traefik.docker.network=web"
|
||||
- "traefik.enable=true"
|
||||
- "traefik.frontend.rule=Host:vikunja.example.com;PathPrefix:/api/v1"
|
||||
- "traefik.frontend.rule=Host:vikunja.example.com;PathPrefix:/api/v1,/dav/,/.well-known"
|
||||
- "traefik.port=3456"
|
||||
- "traefik.protocol=http"
|
||||
frontend:
|
||||
|
@ -123,8 +258,9 @@ services:
|
|||
restart: unless-stopped
|
||||
db:
|
||||
image: mariadb:10
|
||||
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: supersupersecret
|
||||
MYSQL_ROOT_PASSWORD: supersupersecret
|
||||
MYSQL_USER: vikunja
|
||||
MYSQL_PASSWORD: supersecret
|
||||
MYSQL_DATABASE: vikunja
|
||||
|
@ -132,8 +268,6 @@ services:
|
|||
- ./db:/var/lib/mysql
|
||||
restart: unless-stopped
|
||||
command: --max-connections=1000
|
||||
redis:
|
||||
image: redis
|
||||
|
||||
networks:
|
||||
web:
|
||||
|
@ -153,13 +287,18 @@ server {
|
|||
proxy_pass http://frontend:80;
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
location ~* ^/(api|dav|\.well-known)/ {
|
||||
proxy_pass http://api:3456;
|
||||
client_max_body_size 20M;
|
||||
}
|
||||
}
|
||||
{{< /highlight >}}
|
||||
|
||||
### Without redis
|
||||
<div class="notification is-warning">
|
||||
<b>NOTE:</b> If you change the max upload size in Vikunja's settings, you'll need to also change the <code>client_max_body_size</code> in the nginx proxy config.
|
||||
</div>
|
||||
|
||||
`docker-compose.yml` config:
|
||||
|
||||
{{< highlight yaml >}}
|
||||
version: '3'
|
||||
|
@ -167,25 +306,33 @@ version: '3'
|
|||
services:
|
||||
db:
|
||||
image: mariadb:10
|
||||
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: supersecret
|
||||
MYSQL_USER: vikunja
|
||||
MYSQL_PASSWORD: secret
|
||||
MYSQL_DATABASE: vikunja
|
||||
volumes:
|
||||
- ./db:/var/lib/mysql
|
||||
restart: unless-stopped
|
||||
api:
|
||||
image: vikunja/api
|
||||
environment:
|
||||
VIKUNJA_DATABASE_HOST: db
|
||||
VIKUNJA_DATABASE_PASSWORD: supersecret
|
||||
VIKUNJA_DATABASE_PASSWORD: secret
|
||||
VIKUNJA_DATABASE_TYPE: mysql
|
||||
VIKUNJA_DATABASE_USER: root
|
||||
VIKUNJA_DATABASE_USER: vikunja
|
||||
VIKUNJA_DATABASE_DATABASE: vikunja
|
||||
VIKUNJA_SERVICE_JWTSECRET: <a super secure random secret>
|
||||
VIKUNJA_SERVICE_FRONTENDURL: https://<your public frontend url with slash>/
|
||||
volumes:
|
||||
- ./files:/app/vikunja/files
|
||||
depends_on:
|
||||
- db
|
||||
restart: unless-stopped
|
||||
frontend:
|
||||
image: vikunja/frontend
|
||||
restart: unless-stopped
|
||||
proxy:
|
||||
image: nginx
|
||||
ports:
|
||||
|
@ -195,9 +342,23 @@ services:
|
|||
depends_on:
|
||||
- api
|
||||
- frontend
|
||||
restart: unless-stopped
|
||||
{{< /highlight >}}
|
||||
|
||||
### With redis
|
||||
## Example with Caddy v2 as proxy
|
||||
|
||||
You will need the following `Caddyfile` on your host (or elsewhere, but then you'd need to adjust the proxy mount at the bottom of the compose file):
|
||||
|
||||
{{< highlight conf >}}
|
||||
vikunja.example.com {
|
||||
reverse_proxy /api/* api:3456
|
||||
reverse_proxy /.well-known/* api:3456
|
||||
reverse_proxy /dav/* api:3456
|
||||
reverse_proxy frontend:80
|
||||
}
|
||||
{{< /highlight >}}
|
||||
|
||||
`docker-compose.yml` config:
|
||||
|
||||
{{< highlight yaml >}}
|
||||
version: '3'
|
||||
|
@ -205,39 +366,121 @@ version: '3'
|
|||
services:
|
||||
db:
|
||||
image: mariadb:10
|
||||
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: supersecret
|
||||
MYSQL_USER: vikunja
|
||||
MYSQL_PASSWORD: secret
|
||||
MYSQL_DATABASE: vikunja
|
||||
volumes:
|
||||
- ./db:/var/lib/mysql
|
||||
redis:
|
||||
image: redis
|
||||
restart: unless-stopped
|
||||
api:
|
||||
image: vikunja/api
|
||||
environment:
|
||||
VIKUNJA_DATABASE_HOST: db
|
||||
VIKUNJA_DATABASE_PASSWORD: supersecret
|
||||
VIKUNJA_DATABASE_PASSWORD: secret
|
||||
VIKUNJA_DATABASE_TYPE: mysql
|
||||
VIKUNJA_DATABASE_USER: root
|
||||
VIKUNJA_DATABASE_USER: vikunja
|
||||
VIKUNJA_DATABASE_DATABASE: vikunja
|
||||
VIKUNJA_REDIS_ENABLED: 1
|
||||
VIKUNJA_REDIS_HOST: 'redis:6379'
|
||||
VIKUNJA_CACHE_ENABLED: 1
|
||||
VIKUNJA_CACHE_TYPE: redis
|
||||
volumes:
|
||||
VIKUNJA_SERVICE_JWTSECRET: <a super secure random secret>
|
||||
VIKUNJA_SERVICE_FRONTENDURL: https://<your public frontend url with slash>/
|
||||
volumes:
|
||||
- ./files:/app/vikunja/files
|
||||
depends_on:
|
||||
- db
|
||||
- redis
|
||||
restart: unless-stopped
|
||||
frontend:
|
||||
image: vikunja/frontend
|
||||
proxy:
|
||||
image: nginx
|
||||
restart: unless-stopped
|
||||
caddy:
|
||||
image: caddy
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 80:80
|
||||
volumes:
|
||||
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
depends_on:
|
||||
- api
|
||||
- frontend
|
||||
{{< /highlight >}}
|
||||
volumes:
|
||||
- ./Caddyfile:/etc/caddy/Caddyfile:ro
|
||||
{{< /highlight >}}
|
||||
|
||||
## Setup on a Synology NAS
|
||||
|
||||
There is a proxy preinstalled in DSM, so if you want to access vikunja from outside,
|
||||
you can prepare 2 proxy rules:
|
||||
|
||||
* a redirection rule for vikunja's api (see example screenshot using port 3456)
|
||||
* a similar redirection rule for vikunja's frontend (using port 4321)
|
||||
|
||||
![Synology Proxy Settings](/docs/synology-proxy-1.png)
|
||||
|
||||
You should also add 2 empty folders for mariadb and vikunja inside Synology's
|
||||
docker main folders:
|
||||
|
||||
* Docker
|
||||
* vikunja
|
||||
* mariadb
|
||||
|
||||
Synology has its own GUI for managing Docker containers... But it's easier via docker compose.
|
||||
|
||||
To do that, you can
|
||||
|
||||
* either activate SSH and paste the adapted compose file in a terminal (using Putty or similar)
|
||||
* without activating SSH as a "custom script" (go to Control Panel / Task Scheduler / Create / Scheduled Task / User-defined script)
|
||||
* without activating SSH, by using Portainer (you have to install first, check out [this tutorial](https://www.portainer.io/blog/how-to-install-portainer-on-a-synology-nas) for exmple):
|
||||
1. Go to **Dashboard / Stacks** click the button **"Add Stack"**
|
||||
2. Give it the name Vikunja and paste the adapted docker compose file
|
||||
3. Deploy the Stack with the "Deploy Stack" button:
|
||||
|
||||
![Portainer Stack deploy](/docs/synology-proxy-2.png)
|
||||
|
||||
The docker-compose file we're going to use is very similar to the [example without any proxy](#example-without-any-proxy) above:
|
||||
|
||||
{{< highlight yaml >}}
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
db:
|
||||
image: mariadb:10
|
||||
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: supersecret
|
||||
MYSQL_USER: vikunja
|
||||
MYSQL_PASSWORD: secret
|
||||
MYSQL_DATABASE: vikunja
|
||||
volumes:
|
||||
- ./db:/var/lib/mysql
|
||||
restart: unless-stopped
|
||||
api:
|
||||
image: vikunja/api
|
||||
environment:
|
||||
VIKUNJA_DATABASE_HOST: db
|
||||
VIKUNJA_DATABASE_PASSWORD: secret
|
||||
VIKUNJA_DATABASE_TYPE: mysql
|
||||
VIKUNJA_DATABASE_USER: vikunja
|
||||
VIKUNJA_DATABASE_DATABASE: vikunja
|
||||
VIKUNJA_SERVICE_JWTSECRET: <a super secure random secret>
|
||||
VIKUNJA_SERVICE_FRONTENDURL: https://<your public frontend url with slash>/
|
||||
ports:
|
||||
- 3456:3456
|
||||
volumes:
|
||||
- ./files:/app/vikunja/files
|
||||
depends_on:
|
||||
- db
|
||||
restart: unless-stopped
|
||||
frontend:
|
||||
image: vikunja/frontend
|
||||
ports:
|
||||
- 4321:80
|
||||
environment:
|
||||
VIKUNJA_API_URL: http://vikunja-api-domain.tld/api/v1
|
||||
restart: unless-stopped
|
||||
{{< /highlight >}}
|
||||
|
||||
You may want to change the volumes to match the rest of your setup.
|
||||
|
||||
Once deployed, you might want to change the [`PUID` and `GUID` settings]({{< ref "install-backend.md">}}#setting-user-and-group-id-of-the-user-running-vikunja) or [set the time zone]({{< ref "config.md">}}#timezone).
|
||||
|
||||
After registering all your users, you might also want to [disable the user registration]({{<ref "config.md">}}#enableregistration).
|
||||
|
|
|
@ -10,6 +10,13 @@ menu:
|
|||
|
||||
# Backend
|
||||
|
||||
<div class="notification is-warning">
|
||||
<b>NOTE:</b> If you intend to run Vikunja with mysql and/or to use non-latin characters
|
||||
<a href="{{< ref "utf-8.md">}}">make sure your db is utf-8 compatible</a>.
|
||||
</div>
|
||||
|
||||
{{< table_of_contents >}}
|
||||
|
||||
## Install from binary
|
||||
|
||||
Download a copy of Vikunja from the [download page](https://vikunja.io/en/download/) for your architecture.
|
||||
|
@ -21,7 +28,7 @@ wget <download-url>
|
|||
### Verify the GPG signature
|
||||
|
||||
Starting with version `0.7`, all releases are signed using pgp.
|
||||
Releases from `master` will always be signed.
|
||||
Releases from `main` will always be signed.
|
||||
|
||||
To validate the downloaded zip file use the signiture file `.asc` and the key `FF054DACD908493A`:
|
||||
|
||||
|
@ -45,7 +52,7 @@ ln -s /opt/vikunja/vikunja /usr/bin/vikunja
|
|||
|
||||
### Systemd service
|
||||
|
||||
Take the following `service` file and adapt it to your needs:
|
||||
Save the following service file to `/etc/systemd/system/vikunja.service` and adapt it to your needs:
|
||||
|
||||
{{< highlight service >}}
|
||||
[Unit]
|
||||
|
@ -76,9 +83,7 @@ WantedBy=multi-user.target
|
|||
|
||||
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:
|
||||
After you made all necessary modifications, it's time to start the service:
|
||||
|
||||
{{< highlight bash >}}
|
||||
sudo systemctl enable vikunja
|
||||
|
@ -92,12 +97,12 @@ To build vikunja from source, see [building from source]({{< ref "build-from-sou
|
|||
### Updating
|
||||
|
||||
Simply replace the binary and templates with the new version, then restart Vikunja.
|
||||
It will automatically run all nessecary database migrations.
|
||||
It will automatically run all necessary database migrations.
|
||||
**Make sure to take a look at the changelog for the new version to not miss any manual steps the update may involve!**
|
||||
|
||||
## Docker
|
||||
|
||||
(Note: this assumes some familarity with docker)
|
||||
(Note: this assumes some familiarity with docker)
|
||||
|
||||
Usage with docker is pretty straightforward:
|
||||
|
||||
|
@ -106,7 +111,7 @@ docker run -p 3456:3456 vikunja/api
|
|||
{{< /highlight >}}
|
||||
|
||||
to run with a standard configuration.
|
||||
This will expose
|
||||
This will expose vikunja on port `3456` on the host running the container.
|
||||
|
||||
You can mount a local configuration like so:
|
||||
|
||||
|
@ -114,9 +119,21 @@ You can mount a local configuration like so:
|
|||
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.
|
||||
Though it is recommended to use environment variables or `.env` files to configure Vikunja in docker.
|
||||
See [config]({{< ref "config.md">}}) for a list of available configuration options.
|
||||
|
||||
### Files volume
|
||||
|
||||
By default the container stores all files uploaded and used through vikunja inside of `/app/vikunja/files` which is created as a docker volume.
|
||||
You should mount the volume somewhere to the host to permanently store the files and don't loose them if the container restarts.
|
||||
|
||||
### Setting user and group id of the user running vikunja
|
||||
|
||||
You can set the user and group id of the user running vikunja with the `PUID` and `PGID` environment variables.
|
||||
This follows the pattern used by [the linuxserver.io](https://docs.linuxserver.io/general/understanding-puid-and-pgid) docker images.
|
||||
|
||||
This is useful to solve general permission problems when host-mounting volumes such as the volume used for task attachments.
|
||||
|
||||
### Docker compose
|
||||
|
||||
To run the backend with a mariadb database you can use this example [docker-compose](https://docs.docker.com/compose/) file:
|
||||
|
@ -128,20 +145,26 @@ services:
|
|||
image: vikunja/api:latest
|
||||
environment:
|
||||
VIKUNJA_DATABASE_HOST: db
|
||||
VIKUNJA_DATABASE_PASSWORD: supersecret
|
||||
VIKUNJA_DATABASE_PASSWORD: secret
|
||||
VIKUNJA_DATABASE_TYPE: mysql
|
||||
VIKUNJA_DATABASE_USER: root
|
||||
VIKUNJA_DATABASE_USER: vikunja
|
||||
VIKUNJA_SERVICE_JWTSECRET: <generated secret>
|
||||
VIKUNJA_SERVICE_FRONTENDURL: https://<your public frontend url with slash>/
|
||||
volumes:
|
||||
- ./files:/app/vikunja/files
|
||||
db:
|
||||
image: mariadb:10
|
||||
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: supersecret
|
||||
MYSQL_USER: vikunja
|
||||
MYSQL_PASSWORD: secret
|
||||
MYSQL_DATABASE: vikunja
|
||||
volumes:
|
||||
- ./db:/var/lib/mysql
|
||||
- ./db:/var/lib/mysql
|
||||
{{< /highlight >}}
|
||||
|
||||
See [full docker example]({{< ref "full-docker-example.md">}}) for more varations of this config.
|
||||
See [full docker example]({{< ref "full-docker-example.md">}}) for more variations of this config.
|
||||
|
||||
## Debian packages
|
||||
|
||||
|
@ -156,6 +179,105 @@ dpkg -i vikunja.deb
|
|||
This will install the backend to `/opt/vikunja`.
|
||||
To configure it, use the config file in `/etc/vikunja/config.yml`.
|
||||
|
||||
## FreeBSD / FreeNAS
|
||||
|
||||
Unfortunately, we currently can't provide pre-built binaries for FreeBSD.
|
||||
As a workaround, it is possible to compile vikunja for FreeBSD directly on a FreeBSD machine, a guide is available below:
|
||||
|
||||
*Thanks to HungrySkeleton who originally created this guide [in the forum](https://community.vikunja.io/t/freebsd-support/69/11).*
|
||||
|
||||
### Jail Setup
|
||||
|
||||
1. Create jail named ```vikunja```
|
||||
2. Set jail properties to 'auto start'
|
||||
3. Mount storage (```/mnt``` to ```jailData/vikunja```)
|
||||
4. Start jail & SSH into it
|
||||
|
||||
### Installing packages
|
||||
|
||||
{{< highlight bash >}}
|
||||
pkg update && pkg upgrade -y
|
||||
pkg install nano git go gmake
|
||||
go install github.com/magefile/mage
|
||||
{{< /highlight >}}
|
||||
|
||||
### Clone vikunja repo
|
||||
|
||||
{{< highlight bash >}}
|
||||
mkdir /mnt/GO/code.vikunja.io
|
||||
cd /mnt/GO/code.vikunja.io
|
||||
git clone https://code.vikunja.io/api
|
||||
cd /mnt/GO/code.vikunja.io/api
|
||||
{{< /highlight >}}
|
||||
|
||||
### Compile binaries
|
||||
|
||||
{{< highlight bash >}}
|
||||
go install
|
||||
mage build
|
||||
{{< /highlight >}}
|
||||
|
||||
### Create folder to install backend server into
|
||||
|
||||
{{< highlight bash >}}
|
||||
mkdir /mnt/backend
|
||||
cp /mnt/GO/code.vikunja.io/api/vikunja /mnt/backend/vikunja
|
||||
cd /mnt/backend
|
||||
chmod +x /mnt/backend/vikunja
|
||||
{{< /highlight >}}
|
||||
|
||||
### Set vikunja to boot on startup
|
||||
|
||||
{{< highlight bash >}}
|
||||
nano /etc/rc.d/vikunja
|
||||
{{< /highlight >}}
|
||||
|
||||
Then paste into the file:
|
||||
|
||||
{{< highlight bash >}}
|
||||
#!/bin/sh
|
||||
|
||||
. /etc/rc.subr
|
||||
|
||||
name=vikunja
|
||||
rcvar=vikunja_enable
|
||||
|
||||
command="/mnt/backend/${name}"
|
||||
|
||||
load_rc_config $name
|
||||
run_rc_command "$1"
|
||||
{{< /highlight >}}
|
||||
|
||||
Save and exit. Then execute:
|
||||
|
||||
{{< highlight bash >}}
|
||||
chmod +x /etc/rc.d/vikunja
|
||||
nano /etc/rc.conf
|
||||
{{< /highlight >}}
|
||||
|
||||
Then add line to bottom of file:
|
||||
|
||||
{{< highlight bash >}}
|
||||
vikunja_enable="YES"
|
||||
{{< /highlight >}}
|
||||
|
||||
Test vikunja now works with
|
||||
|
||||
{{< highlight bash >}}
|
||||
service vikunja start
|
||||
{{< /highlight >}}
|
||||
|
||||
The API is now available through IP:
|
||||
|
||||
```
|
||||
192.168.1.XXX:3456
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
See [available configuration options]({{< ref "config.md">}}).
|
||||
|
||||
## Default Password
|
||||
|
||||
After successfully installing Vikunja, there is no default user or password.
|
||||
You only need to register a new account and set all the details when creating it.
|
||||
|
|
|
@ -17,9 +17,24 @@ Unzip them and store them somewhere your server can access them.
|
|||
|
||||
You also need to configure a rewrite condition to internally redirect all requests to `index.html` which handles all urls.
|
||||
|
||||
{{< table_of_contents >}}
|
||||
|
||||
## API URL configuration
|
||||
|
||||
By default, the frontend assumes it can reach the api at `/api/v1` relative to the frontend url.
|
||||
This means that if you make the frontend available at, say `https://vikunja.example.com`, it tries to reach the api
|
||||
at `https://vikunja.example.com/api/v1`.
|
||||
In this scenario it is not possible for the frontend and the api to live on separate servers or even just separate ports on the same server with [the use of a reverse proxy]({{< ref "reverse-proxies.md">}}).
|
||||
|
||||
To make configurations like this possible, the api url can be set in the `index.html` file of the frontend releases.
|
||||
Just open the file with a text editor - there are comments which will explain how to set the url.
|
||||
|
||||
**Note:** This needs to be done again after every update.
|
||||
(If you have a good idea for a better solution than this, we'd love to [hear it](https://vikunja.io/contact/))
|
||||
|
||||
## Docker
|
||||
|
||||
The docker image is based on nginx and just contains all nessecary files for the frontend.
|
||||
The docker image is based on nginx and just contains all necessary files for the frontend.
|
||||
|
||||
To run it, all you need is
|
||||
|
||||
|
@ -29,7 +44,14 @@ docker run -p 80:80 vikunja/frontend
|
|||
|
||||
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.
|
||||
See [full docker example]({{< ref "full-docker-example.md">}}) for more variations of this config.
|
||||
|
||||
The docker container runs as an unprivileged user and does not mount anything.
|
||||
|
||||
### API URL configuration in docker
|
||||
|
||||
When running the frontend with docker, it is possible to set the environment variable `$VIKUNJA_API_URL` to the api url.
|
||||
It is therefore not needed to change the url manually inside the docker container.
|
||||
|
||||
## NGINX
|
||||
|
||||
|
@ -96,7 +118,7 @@ Put the following config in `cat /etc/apache2/sites-available/vikunja.conf`:
|
|||
ServerName localhost
|
||||
DocumentRoot /path/to/vikunja/static/frontend/files
|
||||
RewriteEngine On
|
||||
RewriteRule ^\/?(config\.json|favicon\.ico|css|fonts|images|img|js) - [L]
|
||||
RewriteRule ^\/?(favicon\.ico|assets|audio|fonts|images|manifest\.webmanifest|robots\.txt|sw\.js|workbox-.*|api|dav|\.well-known) - [L]
|
||||
RewriteRule ^(.*)$ /index.html [QSA,L]
|
||||
</VirtualHost>
|
||||
{{< /highlight >}}
|
||||
|
|
|
@ -11,16 +11,20 @@ menu:
|
|||
|
||||
# Installing
|
||||
|
||||
Vikunja consists of two parts: [Backend](https://code.vikunja.io/api) and [frontend](https://code.vikunja.io/frontend).
|
||||
While the backend is required, the frontend is not.
|
||||
You don't neccesarily need to have a web-frontend, using Vikunja via the [mobile app](https://code.vikunja.io/app) is totally fine.
|
||||
Vikunja consists of two parts: [API](https://code.vikunja.io/api) and [frontend](https://code.vikunja.io/frontend).
|
||||
|
||||
However, using the web frontend is highly reccommended.
|
||||
You will always need to install at least the API.
|
||||
To actually use Vikunja you'll also need to somehow install a frontend to use it.
|
||||
You can either:
|
||||
|
||||
Vikunja can be installed in various forms.
|
||||
* [Install the web frontend]({{< ref "install-frontend.md">}})
|
||||
* Use the desktop app, which is essentially a web frontend packaged for easy installation on desktop devices
|
||||
* Use the mobile app only, but as of right now it only supports the very basic features of Vikunja
|
||||
|
||||
Vikunja can be installed in various ways.
|
||||
This document provides an overview and instructions for the different methods.
|
||||
|
||||
* [Backend]({{< ref "install-backend.md">}})
|
||||
* [API]({{< ref "install-backend.md">}})
|
||||
* [Installing from binary]({{< ref "install-backend.md#install-from-binary">}})
|
||||
* [Verify the GPG signature]({{< ref "install-backend.md#verify-the-gpg-signature">}})
|
||||
* [Set it up]({{< ref "install-backend.md#set-it-up">}})
|
||||
|
@ -30,6 +34,7 @@ This document provides an overview and instructions for the different methods.
|
|||
* [Docker]({{< ref "install-backend.md#docker">}})
|
||||
* [Debian packages]({{< ref "install-backend.md#debian-packages">}})
|
||||
* [Configuration]({{< ref "config.md">}})
|
||||
* [UTF-8 Settings]({{< ref "utf-8.md">}})
|
||||
* [Frontend]({{< ref "install-frontend.md">}})
|
||||
* [Docker]({{< ref "install-frontend.md#docker">}})
|
||||
* [NGINX]({{< ref "install-frontend.md#nginx">}})
|
||||
|
@ -38,3 +43,17 @@ This document provides an overview and instructions for the different methods.
|
|||
* [Reverse proxies]({{< ref "reverse-proxies.md">}})
|
||||
* [Full docker example]({{< ref "full-docker-example.md">}})
|
||||
* [Backups]({{< ref "backups.md">}})
|
||||
|
||||
## Installation on kubernetes
|
||||
|
||||
A third-party Helm Chart is available from the k8s-at-home project [here](https://github.com/k8s-at-home/charts/tree/master/charts/stable/vikunja).
|
||||
|
||||
## Other installation resources
|
||||
|
||||
* [Docker Compose is MUCH Easier Than you Think - Let's Install Vikunja](https://www.youtube.com/watch?v=fGlz2PkXjuo) (Youtube)
|
||||
* [Setup Vikunja using Docker Compose - Homelab Wiki](https://thehomelab.wiki/books/docker/page/setup-vikunja-using-docker-compose)
|
||||
* [A Closer look at Vikunja - Email Notifications - Enable or Disable Registrations - Allow Attachments](https://www.youtube.com/watch?v=47wj9pRT6Gw) (Youtube)
|
||||
* [Install Vikunja in Docker for self-hosted Task Tracking](https://smarthomepursuits.com/install-vikunja-in-docker-for-self-hosted-task-tracking/)
|
||||
* [Self-Hosted To-Do List with Vikunja in Docker](https://www.youtube.com/watch?v=DqyqDWpEvKI) (Youtube)
|
||||
* [Vikunja self-hosted (step by step)](https://nguyenminhhung.com/vikunja-self-hosted-step-by-step/)
|
||||
* [How to Install Vikunja on Your Synology NAS](https://mariushosting.com/how-to-install-vikunja-on-your-synology-nas/)
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
title: "Hosting Vikunja with k8s"
|
||||
date: 2022-08-12T13:41:48+02:00
|
||||
draft: false
|
||||
type: "doc"
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "setup"
|
||||
---
|
||||
|
||||
# Hosting Vikunja with k8s
|
||||
|
||||
We have an official Helm Chart for Vikunja.
|
||||
|
||||
Check out [the repo](https://kolaente.dev/vikunja/helm-chart/) for more information about how to use it.
|
||||
|
||||
## Third-party Helm Charts
|
||||
|
||||
There are two third-party Helm-Charts which can be used to host Vikunja with k8s:
|
||||
|
||||
* [Truecharts](https://truecharts.org/charts/stable/vikunja/)
|
||||
* [k8s at Home](https://github.com/k8s-at-home/charts)
|
|
@ -0,0 +1,68 @@
|
|||
---
|
||||
date: "2022-08-09:00:00+02:00"
|
||||
title: "OpenID example configurations"
|
||||
draft: false
|
||||
type: "doc"
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "setup"
|
||||
---
|
||||
|
||||
# OpenID example configurations
|
||||
|
||||
On this page you will find examples about how to set up Vikunja with a third-party OpenID provider.
|
||||
To add another example, please [edit this document](https://kolaente.dev/vikunja/api/src/branch/main/docs/content/doc/setup/openid-examples.md) and send a PR.
|
||||
|
||||
{{< table_of_contents >}}
|
||||
|
||||
## Authelia
|
||||
|
||||
Vikunja Config:
|
||||
|
||||
```yaml
|
||||
openid:
|
||||
enabled: true
|
||||
redirecturl: https://vikunja.mydomain.com/auth/openid/ <---- slash at the end is important
|
||||
providers:
|
||||
- name: Authelia
|
||||
authurl: https://login.mydomain.com
|
||||
clientid: <vikunja-id>
|
||||
clientsecret: <vikunja secret>
|
||||
```
|
||||
|
||||
Authelia config:
|
||||
|
||||
```yaml
|
||||
- id: <vikunja-id>
|
||||
description: Vikunja
|
||||
secret: <vikunja secret>
|
||||
redirect_uris:
|
||||
- https://vikunja.mydomain.com/auth/openid/authelia
|
||||
scopes:
|
||||
- openid
|
||||
- email
|
||||
- profile
|
||||
```
|
||||
|
||||
## Google / Google Workspace
|
||||
|
||||
Vikunja Config:
|
||||
|
||||
```yaml
|
||||
openid:
|
||||
enabled: true
|
||||
redirecturl: https://vikunja.mydomain.com/auth/openid/ <---- slash at the end is important
|
||||
providers:
|
||||
- name: Google
|
||||
authurl: https://accounts.google.com
|
||||
clientid: <google-oauth-client-id>
|
||||
clientsecret: <google-oauth-client-secret>
|
||||
```
|
||||
|
||||
Google config:
|
||||
|
||||
- Navigate to `https://console.cloud.google.com/apis/credentials` in the target project
|
||||
- Create a new OAuth client ID
|
||||
- Configure an authorized redirect URI of `https://vikunja.mydomain.com/auth/openid/google`
|
||||
|
||||
Note that there currently seems to be no way to stop creation of new users, even when `enableregistration` is `false` in the configuration. This means that this approach works well only with an "Internal Organization" app for Google Workspace, which limits the allowed users to organizational accounts only. External / public applications will potentially allow every Google user to register.
|
|
@ -13,6 +13,8 @@ menu:
|
|||
These examples assume you have an instance of the backend running on your server listening on port `3456`.
|
||||
If you've changed this setting, you need to update the server configurations accordingly.
|
||||
|
||||
{{< table_of_contents >}}
|
||||
|
||||
## NGINX
|
||||
|
||||
Below are two example configurations which you can put in your `nginx.conf`:
|
||||
|
@ -43,12 +45,17 @@ server {
|
|||
index index.html index.htm;
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
location ~* ^/(api|dav|\.well-known)/ {
|
||||
proxy_pass http://localhost:3456;
|
||||
client_max_body_size 20M;
|
||||
}
|
||||
}
|
||||
{{< /highlight >}}
|
||||
|
||||
<div class="notification is-warning">
|
||||
<b>NOTE:</b> If you change the max upload size in Vikunja's settings, you'll need to also change the <code>client_max_body_size</code> in the nginx proxy config.
|
||||
</div>
|
||||
|
||||
### without gzip
|
||||
|
||||
{{< highlight conf >}}
|
||||
|
@ -62,12 +69,33 @@ server {
|
|||
index index.html index.htm;
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
location ~* ^/(api|dav|\.well-known)/ {
|
||||
proxy_pass http://localhost:3456;
|
||||
client_max_body_size 20M;
|
||||
}
|
||||
}
|
||||
{{< /highlight >}}
|
||||
|
||||
<div class="notification is-warning">
|
||||
<b>NOTE:</b> If you change the max upload size in Vikunja's settings, you'll need to also change the <code>client_max_body_size</code> in the nginx proxy config.
|
||||
</div>
|
||||
|
||||
## NGINX Proxy Manager (NPM)
|
||||
|
||||
1. Create a standard Proxy Host for the Vikunja Frontend within NPM and point it to the URL you plan to use. The next several steps will enable the Proxy Host to successfully navigate to the API (on port 3456).
|
||||
2. Verify that the page will pull up in your browser. (Do not bother trying to log in. It won't work. Trust me.)
|
||||
3. Now, we'll work with the NPM container, so you need to identify the container name for your NPM installation. e.g. NGINX-PM
|
||||
4. From the command line, enter `sudo docker exec -it [NGINX-PM container name] /bin/bash` and navigate to the proxy hosts folder where the `.conf` files are stashed. Probably `/data/nginx/proxy_host`. (This folder is a persistent folder created in the NPM container and mounted by NPM.)
|
||||
5. Locate the `.conf` file where the server_name inside the file matches your Vikunja Proxy Host. Once found, add the following code, unchanged, just above the existing location block in that file. (They are listed by number, not name.)
|
||||
```nginx
|
||||
location ~* ^/(api|dav|\.well-known)/ {
|
||||
proxy_pass http://api:3456;
|
||||
client_max_body_size 20M;
|
||||
}
|
||||
```
|
||||
6. After saving the edited file, return to NPM's UI browser window and refresh the page to verify your Proxy Host for Vikunja is still online.
|
||||
7. Now, switch over to your Vikunja browser window and hit refresh. If you configured your URL correctly in original Vikunja container, you should be all set and the browser will correctly show Vikunja. If not, you'll need to adjust the address in the top of the login subscreen to match your proxy address.
|
||||
|
||||
## Apache
|
||||
|
||||
Put the following config in `cat /etc/apache2/sites-available/vikunja.conf`:
|
||||
|
@ -82,14 +110,18 @@ Put the following config in `cat /etc/apache2/sites-available/vikunja.conf`:
|
|||
</Proxy>
|
||||
ProxyPass /api http://localhost:3456/api
|
||||
ProxyPassReverse /api http://localhost:3456/api
|
||||
ProxyPass /dav http://localhost:3456/dav
|
||||
ProxyPassReverse /dav http://localhost:3456/dav
|
||||
ProxyPass /.well-known http://localhost:3456/.well-known
|
||||
ProxyPassReverse /.well-known http://localhost:3456/.well-known
|
||||
|
||||
DocumentRoot /var/www/html
|
||||
RewriteEngine On
|
||||
RewriteRule ^\/?(config\.json|favicon\.ico|css|fonts|images|img|js|api) - [L]
|
||||
RewriteRule ^\/?(favicon\.ico|assets|audio|fonts|images|manifest\.webmanifest|robots\.txt|sw\.js|workbox-.*|api|dav|\.well-known) - [L]
|
||||
RewriteRule ^(.*)$ /index.html [QSA,L]
|
||||
</VirtualHost>
|
||||
{{< /highlight >}}
|
||||
|
||||
**Note:** The apache modules `proxy` and `proxy_http` must be enabled for this.
|
||||
**Note:** The apache modules `proxy`, `proxy_http` and `rewrite` must be enabled for this.
|
||||
|
||||
For more details see the [frontend apache configuration]({{< ref "install-frontend.md#apache">}}).
|
||||
For more details see the [frontend apache configuration]({{< ref "install-frontend.md#apache">}}).
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
---
|
||||
title: "Running Vikunja in a subdirectory"
|
||||
date: 2022-09-23T12:15:04+02:00
|
||||
draft: false
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "setup"
|
||||
---
|
||||
|
||||
# Running Vikunja in a subdirectory
|
||||
|
||||
Running Vikunja in a subdirectory is not supported out of the box.
|
||||
However, you can still run it in a subdirectory but need to build the frontend yourself.
|
||||
|
||||
## Frontend
|
||||
|
||||
First, make sure you're able to build the frontend from source.
|
||||
Check [the guide about building from source]({{< ref "build-from-source.md">}}#frontend) about that.
|
||||
|
||||
### Dynamicly set with build command
|
||||
|
||||
Run the build with the `VIKUNJA_FRONTEND_BASE` variable specified.
|
||||
|
||||
```
|
||||
VIKUNJA_FRONTEND_BASE=/SUBPATH/ pnpm run build
|
||||
```
|
||||
|
||||
Where `SUBPATH` is the subdirectory you want to run Vikunja on.
|
||||
|
||||
### Set via .env.local
|
||||
|
||||
* Copy `.env.local.example` to `.env.local`
|
||||
* Uncomment `VIKUNJA_FRONTEND_BASE` and set `/subpath/` to the desired path.
|
||||
|
||||
After saving, build Vikunja as normal.
|
||||
|
||||
```
|
||||
pnpm run build
|
||||
```
|
||||
|
||||
Once you have the build files you can deploy them as usual.
|
||||
Note that when deploying in docker you'll need to put the files in a web container yourself, you
|
||||
can't use the `Dockerfile` in the repo without modifications.
|
||||
|
||||
## API
|
||||
|
||||
If you're not using a reverse proxy you're good to go.
|
||||
Simply configure the api url in the frontend as you normally would.
|
||||
|
||||
If you're using a reverse proxy you'll need to adjust the paths so that the api is available at `/SUBPATH/api/v1`.
|
||||
You can check if everything is working correctly by opening `/SUBPATH/api/v1/info` in a browser.
|
|
@ -0,0 +1,105 @@
|
|||
---
|
||||
date: "2020-07-06:00:00+02:00"
|
||||
title: "UTF-8 Settings"
|
||||
draft: false
|
||||
type: "doc"
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "setup"
|
||||
---
|
||||
|
||||
# UTF-8 Settings
|
||||
|
||||
Vikunja itself is always fully capable of handling utf-8 characters.
|
||||
However, your database might be not. Vikunja itself will work just fine until you want to use non-latin characters in your tasks/projects/etc.
|
||||
|
||||
On this page, you will find information about how to fully ensure non-latin characters like *aüäß* or emojis work with your installation.
|
||||
|
||||
{{< table_of_contents >}}
|
||||
|
||||
## Postgresql & SQLite
|
||||
|
||||
Postgresql and SQLite should handle utf-8 just fine - If you discover any issues nonetheless, please
|
||||
[drop us a message](https://vikunja.io/contact/).
|
||||
|
||||
## MySQL
|
||||
|
||||
MySQL is not able to handle utf-8 by default.
|
||||
To fix this, follow the steps below.
|
||||
|
||||
To find out if your db supports utf-8, run the following in a shell or similar, assuming the database
|
||||
you're using for vikunja is called `vikunja`:
|
||||
|
||||
{{< highlight sql >}}
|
||||
SELECT default_character_set_name FROM information_schema.SCHEMATA WHERE schema_name = 'vikunja';
|
||||
{{< /highlight >}}
|
||||
|
||||
This will get you a result like the following:
|
||||
|
||||
```
|
||||
+----------------------------+
|
||||
| default_character_set_name |
|
||||
+----------------------------+
|
||||
| latin1 |
|
||||
+----------------------------+
|
||||
1 row in set (0.001 sec)
|
||||
```
|
||||
|
||||
The charset `latin1` means the db is encoded in the `latin1` encoding which does not support utf-8 characters.
|
||||
|
||||
(The following guide is based on [this thread from stackoverflow](https://dba.stackexchange.com/a/104866))
|
||||
|
||||
### 0. Backup your database
|
||||
|
||||
Before attempting any conversion, please [back up your database]({{< ref "backups.md">}}).
|
||||
|
||||
### 1. Create a pre-conversion script
|
||||
|
||||
Copy the following sql statements in a file called `preAlterTables.sql` and replace all occurrences of `vikunja` with the name of your database:
|
||||
|
||||
{{< highlight sql >}}
|
||||
use information_schema;
|
||||
SELECT concat("ALTER DATABASE `",table_schema,"` CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;") as _sql
|
||||
FROM `TABLES` where table_schema like 'vikunja' and TABLE_TYPE='BASE TABLE' group by table_schema;
|
||||
SELECT concat("ALTER TABLE `",table_schema,"`.`",table_name,"` CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;") as _sql
|
||||
FROM `TABLES` where table_schema like 'vikunja' and TABLE_TYPE='BASE TABLE' group by table_schema, table_name;
|
||||
SELECT concat("ALTER TABLE `",table_schema,"`.`",table_name, "` CHANGE `",column_name,"` `",column_name,"` ",data_type,"(",character_maximum_length,") CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci",IF(is_nullable="YES"," NULL"," NOT NULL"),";") as _sql
|
||||
FROM `COLUMNS` where table_schema like 'vikunja' and data_type in ('varchar','char');
|
||||
SELECT concat("ALTER TABLE `",table_schema,"`.`",table_name, "` CHANGE `",column_name,"` `",column_name,"` ",data_type," CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci",IF(is_nullable="YES"," NULL"," NOT NULL"),";") as _sql
|
||||
FROM `COLUMNS` where table_schema like 'vikunja' and data_type in ('text','tinytext','mediumtext','longtext');
|
||||
{{< /highlight >}}
|
||||
|
||||
### 2. Run the pre-conversion script
|
||||
|
||||
Running this will create the actual migration script for your particular database structure and save it in a file called `alterTables.sql`:
|
||||
|
||||
{{< highlight bash >}}
|
||||
mysql -uroot < preAlterTables.sql | egrep '^ALTER' > alterTables.sql
|
||||
{{< /highlight >}}
|
||||
|
||||
### 3. Convert the database
|
||||
|
||||
At this point converting is just a matter of executing the previously generated sql script:
|
||||
|
||||
{{< highlight bash >}}
|
||||
mysql -uroot < alterTables.sql
|
||||
{{< /highlight >}}
|
||||
|
||||
### 4. Verify it was successfully converted
|
||||
|
||||
If everything worked as intended, your db collation should now look like this:
|
||||
|
||||
{{< highlight sql >}}
|
||||
SELECT default_character_set_name FROM information_schema.SCHEMATA WHERE schema_name = 'vikunja';
|
||||
{{< /highlight >}}
|
||||
|
||||
Should get you:
|
||||
|
||||
```
|
||||
+----------------------------+
|
||||
| default_character_set_name |
|
||||
+----------------------------+
|
||||
| utf8mb4 |
|
||||
+----------------------------+
|
||||
1 row in set (0.001 sec)
|
||||
```
|
|
@ -0,0 +1,45 @@
|
|||
---
|
||||
date: "2022-07-07:00:00+02:00"
|
||||
title: "Versions"
|
||||
draft: false
|
||||
type: "doc"
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "setup"
|
||||
---
|
||||
|
||||
# Vikunja Versions
|
||||
|
||||
The Vikunja api and frontend are available in two different release flavors.
|
||||
|
||||
{{< table_of_contents >}}
|
||||
|
||||
## Stable
|
||||
|
||||
Stable releases have a fixed version number like `0.18.2` and are published at irregular intervals whenever a new version is ready.
|
||||
They receive few bugfixes and security patches.
|
||||
|
||||
We use [Semantic Versioning](https://semver.org) for these releases.
|
||||
|
||||
## Unstable
|
||||
|
||||
Unstable versions are build every time a PR is merged or a commit to the main development branch is made.
|
||||
As such, they contain the current development code and are more likely to have bugs.
|
||||
There might be multiple new such builds a day.
|
||||
|
||||
Versions contain the last stable version, the number of commits since then and the commit the currently running binary was built from.
|
||||
They look like this: `v0.18.1+269-5cc4927b9e`
|
||||
|
||||
The demo instance at [try.vikunja.io](https://try.vikunja.io) automatically updates and always runs the last unstable build.
|
||||
|
||||
## Switching between versions
|
||||
|
||||
First you should create a backup of your current setup!
|
||||
|
||||
Switching between versions is the same process as [upgrading]({{< ref install-backend.md >}}#updating).
|
||||
Simply replace the stable binary with an unstable one or vice-versa.
|
||||
|
||||
For installations using docker, it is as simple as using the `unstable` or `latest` tag to switch between versions.
|
||||
|
||||
**Note:** While switching from stable to unstable should work without any problem, switching back might work but is not recommended and might break your instance.
|
||||
To switch from unstable back to stable the best way is to wait for the next stable release after the used unstable build and then upgrade to that.
|
|
@ -10,10 +10,10 @@ menu:
|
|||
|
||||
# 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).
|
||||
You can find the api docs under `http://vikunja.tld/api/v1/docs` of your instance.<br />
|
||||
A public instance is available on [try.vikunja.io](https://try.vikunja.io/api/v1/docs).
|
||||
|
||||
These docs are autgenerated from annotations in the code with swagger.
|
||||
These docs are autogenerated 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.
|
||||
You can use this to embed it into other OpenAPI compatible applications if you want.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
date: "2019-05-12:00:00+01:00"
|
||||
title: "Caldav"
|
||||
title: "CalDAV"
|
||||
draft: false
|
||||
type: "doc"
|
||||
menu:
|
||||
|
@ -8,24 +8,26 @@ menu:
|
|||
parent: "usage"
|
||||
---
|
||||
|
||||
# Caldav
|
||||
# CalDAV
|
||||
|
||||
> **Warning:** The caldav integration is in an early alpha stage and has bugs.
|
||||
> **Warning:** The CalDAV integration is in an early alpha stage and has bugs.
|
||||
> It works well with some clients while having issues with others.
|
||||
> If you encounter issues, please [report them](https://code.vikunja.io/api/issues/new?body=[caldav])
|
||||
> If you encounter issues, please [report them](https://code.vikunja.io/api/issues/new?body=[caldav])
|
||||
|
||||
Vikunja supports managing tasks via the [caldav VTODO](https://tools.ietf.org/html/rfc5545#section-3.6.2) extension.
|
||||
|
||||
{{< table_of_contents >}}
|
||||
|
||||
## URLs
|
||||
|
||||
All urls are located under the `/dav` subspace.
|
||||
|
||||
Urls are:
|
||||
|
||||
* `/principals/<username>/`: Returns urls for list discovery. *Use this url to initially make connections to new clients.*
|
||||
* `/lists/`: Used to manage lists
|
||||
* `/lists/<List ID>/`: Used to manage a single list
|
||||
* `/lists/<List ID>/<Task UID>`: Used to manage a task on a list
|
||||
* `/principals/<username>/`: Returns urls for project discovery. *Use this url to initially make connections to new clients.*
|
||||
* `/projects/`: Used to manage projects
|
||||
* `/projects/<List ID>/`: Used to manage a single project
|
||||
* `/projects/<List ID>/<Task UID>`: Used to manage a task on a project
|
||||
|
||||
## Supported properties
|
||||
|
||||
|
@ -35,46 +37,50 @@ Vikunja currently supports the following properties:
|
|||
* `SUMMARY`
|
||||
* `DESCRIPTION`
|
||||
* `PRIORITY`
|
||||
* `CATEGORIES`
|
||||
* `COMPLETED`
|
||||
* `CREATED` (only Vikunja → Client)
|
||||
* `DUE`
|
||||
* `DTSTART`
|
||||
* `DURATION`
|
||||
* `ORGANIZER`
|
||||
* `RELATED-TO`
|
||||
* `CREATED`
|
||||
* `DTSTAMP`
|
||||
* `LAST-MODIFIED`
|
||||
* `DTSTART`
|
||||
* `LAST-MODIFIED` (only Vikunja → Client)
|
||||
* `RRULE` (Recurrence) (only Vikunja → Client)
|
||||
* `VALARM` (Reminders)
|
||||
|
||||
Vikunja **currently does not** support these properties:
|
||||
|
||||
* `ATTACH`
|
||||
* `CATEGORIES`
|
||||
* `CLASS`
|
||||
* `COMMENT`
|
||||
* `CONTACT`
|
||||
* `GEO`
|
||||
* `LOCATION`
|
||||
* `ORGANIZER` (disabled)
|
||||
* `PERCENT-COMPLETE`
|
||||
* `RESOURCES`
|
||||
* `STATUS`
|
||||
* `CONTACT`
|
||||
* `RECURRENCE-ID`
|
||||
* `URL`
|
||||
* Recurrence
|
||||
* `RELATED-TO`
|
||||
* `RESOURCES`
|
||||
* `SEQUENCE`
|
||||
* `STATUS`
|
||||
* `URL`
|
||||
|
||||
## Tested Clients
|
||||
|
||||
#### Working
|
||||
### Working
|
||||
|
||||
* [Evolution](https://wiki.gnome.org/Apps/Evolution/)
|
||||
|
||||
#### Not working
|
||||
|
||||
* [OpenTasks](https://opentasks.app/) & [DAVx⁵](https://www.davx5.com/)
|
||||
* [Tasks (Android)](https://tasks.org/)
|
||||
|
||||
### Not working
|
||||
|
||||
* [Thunderbird (68)](https://www.thunderbird.net/)
|
||||
* iOS CalDAV Sync (See [#753](https://kolaente.dev/vikunja/api/issues/753))
|
||||
|
||||
## Dev logs
|
||||
|
||||
The whole thing is not optimized at all and probably pretty inefficent.
|
||||
The whole thing is not optimized at all and probably pretty inefficient.
|
||||
|
||||
Request body and headers are logged if the debug output is enabled.
|
||||
|
||||
|
@ -135,6 +141,4 @@ Requests from the app:::
|
|||
And then it just stops.
|
||||
... and complains about not being able to find the home set
|
||||
... without even requesting it...
|
||||
|
||||
|
||||
```
|
|
@ -10,11 +10,15 @@ menu:
|
|||
|
||||
# Command line interface
|
||||
|
||||
You can interact with Vikunja using its `cli` interface.
|
||||
You can interact with Vikunja using its `cli` interface.<br />
|
||||
The following commands are available:
|
||||
|
||||
* [dump](#dump)
|
||||
* [help](#help)
|
||||
* [migrate](#migrate)
|
||||
* [restore](#restore)
|
||||
* [testmail](#testmail)
|
||||
* [user](#user)
|
||||
* [version](#version)
|
||||
* [web](#web)
|
||||
|
||||
|
@ -22,6 +26,25 @@ If you don't specify a command, the [`web`](#web) command will be executed.
|
|||
|
||||
All commands use the same standard [config file]({{< ref "../setup/config.md">}}).
|
||||
|
||||
## Using the cli in docker
|
||||
|
||||
When running Vikunja in docker, you'll need to execute all commands in the `api` container.
|
||||
Instead of running the `vikunja` binary directly, run it like this:
|
||||
|
||||
```sh
|
||||
docker exec <name of the vikunja api container> /app/vikunja/vikunja <subcommand>
|
||||
```
|
||||
|
||||
### `dump`
|
||||
|
||||
Creates a zip file with all vikunja-related files.
|
||||
This includes config, version, all files and the full database.
|
||||
|
||||
Usage:
|
||||
{{< highlight bash >}}
|
||||
$ vikunja dump
|
||||
{{< /highlight >}}
|
||||
|
||||
### `help`
|
||||
|
||||
Shows more detailed help about any command.
|
||||
|
@ -57,12 +80,112 @@ Roll migrations back until a certain point.
|
|||
|
||||
Usage:
|
||||
{{< highlight bash >}}
|
||||
$ vikunja migrate rollback [flags]
|
||||
$ vikunja migrate rollback [flags]
|
||||
{{< /highlight >}}
|
||||
|
||||
Flags:
|
||||
* `-n`, `--name` string: The id of the migration you want to roll back until.
|
||||
|
||||
|
||||
### `restore`
|
||||
|
||||
Restores a previously created dump from a zip file, see `dump`.
|
||||
|
||||
Usage:
|
||||
{{< highlight bash >}}
|
||||
$ vikunja restore <path to dump zip file>
|
||||
{{< /highlight >}}
|
||||
|
||||
### `testmail`
|
||||
|
||||
Sends a test mail using the configured smtp connection.
|
||||
|
||||
Usage:
|
||||
{{< highlight bash >}}
|
||||
$ vikunja testmail <email to send the test mail to>
|
||||
{{< /highlight >}}
|
||||
|
||||
### `user`
|
||||
|
||||
Bundles a few commands to manage users.
|
||||
|
||||
#### `user change-status`
|
||||
|
||||
Enable or disable a user. Will toggle the current status if no flag (`--enable` or `--disable`) is provided.
|
||||
|
||||
Usage:
|
||||
{{< highlight bash >}}
|
||||
$ vikunja user change-status <user id> <flags>
|
||||
{{< /highlight >}}
|
||||
|
||||
Flags:
|
||||
* `-d`, `--disable`: Disable the user.
|
||||
* `-e`, `--enable`: Enable the user.
|
||||
|
||||
#### `user create`
|
||||
|
||||
Create a new user.
|
||||
|
||||
Usage:
|
||||
{{< highlight bash >}}
|
||||
$ vikunja user create <flags>
|
||||
{{< /highlight >}}
|
||||
|
||||
Flags:
|
||||
* `-a`, `--avatar-provider`: The avatar provider of the new user. Optional.
|
||||
* `-e`, `--email`: The email address of the new user.
|
||||
* `-p`, `--password`: The password of the new user. You will be asked to enter it if not provided through the flag.
|
||||
* `-u`, `--username`: The username of the new user.
|
||||
|
||||
#### `user delete`
|
||||
|
||||
Start the user deletion process.
|
||||
If called without the `--now` flag, this command will only trigger an email to the user in order for them to confirm and start the deletion process (this is the same behavoir as if the user requested their deletion via the web interface).
|
||||
With the flag the user is deleted **immediately**.
|
||||
|
||||
**USE WITH CAUTION.**
|
||||
|
||||
{{< highlight bash >}}
|
||||
$ vikunja user delete <id> <flags>
|
||||
{{< /highlight >}}
|
||||
|
||||
Flags:
|
||||
* `-n`, `--now` If provided, deletes the user immediately instead of emailing them first.
|
||||
|
||||
#### `user list`
|
||||
|
||||
Shows a list of all users.
|
||||
|
||||
Usage:
|
||||
{{< highlight bash >}}
|
||||
$ vikunja user list
|
||||
{{< /highlight >}}
|
||||
|
||||
#### `user reset-password`
|
||||
|
||||
Reset a users password, either through mailing them a reset link or directly.
|
||||
|
||||
Usage:
|
||||
{{< highlight bash >}}
|
||||
$ vikunja user reset-password <flags>
|
||||
{{< /highlight >}}
|
||||
|
||||
Flags:
|
||||
* `-d`, `--direct`: If provided, reset the password directly instead of sending the user a reset mail.
|
||||
* `-p`, `--password`: The new password of the user. Only used in combination with --direct. You will be asked to enter it if not provided through the flag.
|
||||
|
||||
#### `user update`
|
||||
|
||||
Update an existing user.
|
||||
|
||||
Usage:
|
||||
{{< highlight bash >}}
|
||||
$ vikunja user update <user id>
|
||||
{{< /highlight >}}
|
||||
|
||||
Flags:
|
||||
* `-a`, `--avatar-provider`: The new avatar provider of the new user.
|
||||
* `-e`, `--email`: The new email address of the user.
|
||||
* `-u`, `--username`: The new username of the user.
|
||||
|
||||
### `version`
|
||||
|
||||
|
@ -71,7 +194,7 @@ This is either the semantic version (something like `0.7`) or version + git comm
|
|||
|
||||
Usage:
|
||||
{{< highlight bash >}}
|
||||
$ vikunja version
|
||||
$ vikunja version
|
||||
{{< /highlight >}}
|
||||
|
||||
### `web`
|
||||
|
@ -80,5 +203,5 @@ Starts Vikunja's REST api server.
|
|||
|
||||
Usage:
|
||||
{{< highlight bash >}}
|
||||
$ vikunja web
|
||||
{{< /highlight >}}
|
||||
$ vikunja web
|
||||
{{< /highlight >}}
|
||||
|
|
|
@ -4,70 +4,166 @@ title: "Errors"
|
|||
draft: false
|
||||
type: "doc"
|
||||
menu:
|
||||
sidebar:
|
||||
parent: "usage"
|
||||
sidebar:
|
||||
parent: "usage"
|
||||
---
|
||||
|
||||
# Errors
|
||||
|
||||
This document describes the different errors Vikunja can return.
|
||||
|
||||
{{< table_of_contents >}}
|
||||
|
||||
## Generic
|
||||
|
||||
| ErrorCode | HTTP Status Code | Description |
|
||||
|-----------|------------------|-------------|
|
||||
| 0001 | 403 | Generic forbidden error. |
|
||||
| 1001 | 400 | A user with this username already exists. |
|
||||
| 1002 | 400 | A user with this email address already exists. |
|
||||
| 1004 | 400 | No username and password specified. |
|
||||
| 1005 | 404 | The user does not exist. |
|
||||
| 1006 | 400 | Could not get the user id. |
|
||||
| 1008 | 412 | No password reset token provided. |
|
||||
| 1009 | 412 | Invalid password reset token. |
|
||||
| 1010 | 412 | Invalid email confirm token. |
|
||||
| 1011 | 412 | Wrong username or password. |
|
||||
| 1012 | 412 | Email address of the user not confirmed. |
|
||||
| 1013 | 412 | New password is empty. |
|
||||
| 1014 | 412 | Old password is empty. |
|
||||
|
||||
## User
|
||||
|
||||
| ErrorCode | HTTP Status Code | Description |
|
||||
|-----------|------------------|-------------|
|
||||
| 1001 | 400 | A user with this username already exists. |
|
||||
| 1002 | 400 | A user with this email address already exists. |
|
||||
| 1004 | 400 | No username and password specified. |
|
||||
| 1005 | 404 | The user does not exist. |
|
||||
| 1006 | 400 | Could not get the user id. |
|
||||
| 1008 | 412 | No password reset token provided. |
|
||||
| 1009 | 412 | Invalid password reset token. |
|
||||
| 1010 | 412 | Invalid email confirm token. |
|
||||
| 1011 | 412 | Wrong username or password. |
|
||||
| 1012 | 412 | Email address of the user not confirmed. |
|
||||
| 1013 | 412 | New password is empty. |
|
||||
| 1014 | 412 | Old password is empty. |
|
||||
| 1015 | 412 | Totp is already enabled for this user. |
|
||||
| 1016 | 412 | Totp is not enabled for this user. |
|
||||
| 1017 | 412 | The provided Totp passcode is invalid. |
|
||||
| 1018 | 412 | The provided user avatar provider type setting is invalid. |
|
||||
| 1019 | 412 | No openid email address was provided. |
|
||||
| 1020 | 412 | This user account is disabled. |
|
||||
| 1021 | 412 | This account is managed by a third-party authentication provider. |
|
||||
| 1021 | 412 | The username must not contain spaces. |
|
||||
|
||||
## Validation
|
||||
|
||||
| ErrorCode | HTTP Status Code | Description |
|
||||
|-----------|------------------|-------------|
|
||||
| 2001 | 400 | ID cannot be empty or 0. |
|
||||
| 2002 | 400 | Some of the request data was invalid. The response contains an aditional array with all invalid fields. |
|
||||
| 3001 | 404 | The list does not exist. |
|
||||
| 3004 | 403 | The user needs to have read permissions on that list to perform that action. |
|
||||
| 3005 | 400 | The list title cannot be empty. |
|
||||
| 3006 | 404 | The list share does not exist. |
|
||||
| 3007 | 400 | A list with this identifier already exists. |
|
||||
| 3008 | 412 | The list is archived and can therefore only be accessed read only. This is also true for all tasks associated with this list. |
|
||||
| 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. |
|
||||
|
||||
## Project
|
||||
|
||||
| ErrorCode | HTTP Status Code | Description |
|
||||
|-----------|------------------|-------------------------------------------------------------------------------------------------------------------------------|
|
||||
| 3001 | 404 | The project does not exist. |
|
||||
| 3004 | 403 | The user needs to have read permissions on that project to perform that action. |
|
||||
| 3005 | 400 | The project title cannot be empty. |
|
||||
| 3006 | 404 | The project share does not exist. |
|
||||
| 3007 | 400 | A project with this identifier already exists. |
|
||||
| 3008 | 412 | The project is archived and can therefore only be accessed read only. This is also true for all tasks associated with this project. |
|
||||
| 3009 | 412 | The project cannot belong to a dynamically generated namespace like "Favorites". |
|
||||
| 3010 | 412 | The project must belong to a namespace. |
|
||||
|
||||
## Task
|
||||
|
||||
| ErrorCode | HTTP Status Code | Description |
|
||||
|-----------|------------------|-------------|
|
||||
| 4001 | 400 | The project task text cannot be empty. |
|
||||
| 4002 | 404 | The project task does not exist. |
|
||||
| 4003 | 403 | All bulk editing tasks must belong to the same project. |
|
||||
| 4004 | 403 | Need at least one task when bulk editing tasks. |
|
||||
| 4005 | 403 | The user does not have the right to see the task. |
|
||||
| 4006 | 403 | The user tried to set a parent task as the task itself. |
|
||||
| 4007 | 400 | The user tried to create a task relation with an invalid kind of relation. |
|
||||
| 4008 | 409 | The user tried to create a task relation which already exists. |
|
||||
| 4009 | 404 | The task relation does not exist. |
|
||||
| 4009 | 404 | The task relation does not exist. |
|
||||
| 4010 | 400 | Cannot relate a task with itself. |
|
||||
| 4011 | 404 | The task attachment does not exist. |
|
||||
| 4012 | 400 | The task attachment is too large. |
|
||||
| 4013 | 400 | The task sort param is invalid. |
|
||||
| 4014 | 400 | The task sort order is invalid. |
|
||||
| 4015 | 404 | The task comment does not exist. |
|
||||
| 4016 | 403 | Invalid task field. |
|
||||
| 4017 | 403 | Invalid task filter comparator. |
|
||||
| 5001 | 404 | The namspace does not exist. |
|
||||
| 4016 | 400 | Invalid task field. |
|
||||
| 4017 | 400 | Invalid task filter comparator. |
|
||||
| 4018 | 400 | Invalid task filter concatinator. |
|
||||
| 4019 | 400 | Invalid task filter value. |
|
||||
| 4020 | 400 | The provided attachment does not belong to that task. |
|
||||
| 4021 | 400 | This user is already assigned to that task. |
|
||||
| 4022 | 400 | The task has a relative reminder which does not specify relative to what. |
|
||||
|
||||
## Namespace
|
||||
|
||||
| ErrorCode | HTTP Status Code | Description |
|
||||
|-----------|------------------|-------------|
|
||||
| 5001 | 404 | The namespace does not exist. |
|
||||
| 5003 | 403 | The user does not have access to the specified namespace. |
|
||||
| 5006 | 400 | The namespace name cannot be empty. |
|
||||
| 5009 | 403 | The user needs to have namespace read access to perform that action. |
|
||||
| 5010 | 403 | This team does not have access to that namespace. |
|
||||
| 5011 | 409 | This user has already access to that namespace. |
|
||||
| 5012 | 412 | The namespace is archived and can therefore only be accessed read only. |
|
||||
| 6001 | 400 | The team name cannot be emtpy. |
|
||||
|
||||
## Team
|
||||
|
||||
| ErrorCode | HTTP Status Code | Description |
|
||||
|-----------|------------------|-------------|
|
||||
| 6001 | 400 | The team name cannot be empty. |
|
||||
| 6002 | 404 | The team does not exist. |
|
||||
| 6004 | 409 | The team already has access to that namespace or list. |
|
||||
| 6004 | 409 | The team already has access to that namespace or project. |
|
||||
| 6005 | 409 | The user is already a member of that team. |
|
||||
| 6006 | 400 | Cannot delete the last team member. |
|
||||
| 6007 | 403 | The team does not have access to the list to perform that action. |
|
||||
| 7002 | 409 | The user already has access to that list. |
|
||||
| 7003 | 403 | The user does not have access to that list. |
|
||||
| 6007 | 403 | The team does not have access to the project to perform that action. |
|
||||
|
||||
## User Project Access
|
||||
|
||||
| ErrorCode | HTTP Status Code | Description |
|
||||
|-----------|------------------|-------------|
|
||||
| 7002 | 409 | The user already has access to that project. |
|
||||
| 7003 | 403 | The user does not have access to that project. |
|
||||
|
||||
## Label
|
||||
|
||||
| ErrorCode | HTTP Status Code | Description |
|
||||
|-----------|------------------|-------------|
|
||||
| 8001 | 403 | This label already exists on that task. |
|
||||
| 8002 | 404 | The label does not exist. |
|
||||
| 8003 | 403 | The user does not have access to this label. |
|
||||
| 9001 | 403 | The right is invalid. |
|
||||
|
||||
## Right
|
||||
|
||||
| ErrorCode | HTTP Status Code | Description |
|
||||
|-----------|------------------|-------------|
|
||||
| 9001 | 403 | The right is invalid. |
|
||||
|
||||
## Kanban
|
||||
|
||||
| ErrorCode | HTTP Status Code | Description |
|
||||
|-----------|------------------|-------------|
|
||||
| 10001 | 404 | The bucket does not exist. |
|
||||
| 10002 | 400 | The bucket does not belong to that project. |
|
||||
| 10003 | 412 | You cannot remove the last bucket on a project. |
|
||||
| 10004 | 412 | You cannot add the task to this bucket as it already exceeded the limit of tasks it can hold. |
|
||||
| 10005 | 412 | There can be only one done bucket per project. |
|
||||
|
||||
## Saved Filters
|
||||
|
||||
| ErrorCode | HTTP Status Code | Description |
|
||||
|-----------|------------------|-------------|
|
||||
| 11001 | 404 | The saved filter does not exist. |
|
||||
| 11002 | 412 | Saved filters are not available for link shares. |
|
||||
|
||||
## Subscriptions
|
||||
|
||||
| ErrorCode | HTTP Status Code | Description |
|
||||
|-----------|------------------|-------------|
|
||||
| 12001 | 412 | The subscription entity type is invalid. |
|
||||
| 12002 | 412 | The user is already subscribed to the entity itself or a parent entity. |
|
||||
|
||||
## Link Shares
|
||||
|
||||
| ErrorCode | HTTP Status Code | Description |
|
||||
|-----------|------------------|--------------------------------------------------------------------------------|
|
||||
| 13001 | 412 | This link share requires a password for authentication, but none was provided. |
|
||||
| 13002 | 403 | The provided link share password is invalid. |
|
||||
| 13003 | 400 | The provided link share token is invalid. |
|
||||
|
|
|
@ -10,16 +10,16 @@ menu:
|
|||
|
||||
# Available task relation kinds
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| subtask | Task is a subtask of the other task. This is the opposite of `parenttask`. |
|
||||
| parenttask | Task is a parent task of the other task. This is the opposite of `subtask`. |
|
||||
| related | Both tasks are related to each other. How is not more specified. |
|
||||
| duplicateof | Task is a duplicate of the other task. This is the opposite of `duplicates`. |
|
||||
| duplicates | Task duplicates the other task. This is the opposite of `duplicateof`. |
|
||||
| blocking | Task is blocking the other task. This is the opposite of `blocked`. |
|
||||
| blocked | Task is blocked by the other task. This is the opposite of `blocking`. |
|
||||
| precedes | Task precedes the other task. This is the opposite of `follows`. |
|
||||
| follows | Task follows the other task. This is the opposite of `precedes`. |
|
||||
| copiedfrom | Task is copied from the other task. This is the opposite of `copiedto`. |
|
||||
| copiedto | Task is copied to the other task. This is the opposite of `copiedfrom`. |
|
||||
| Code | Description | Opposite of |
|
||||
|------|-------------|-------------|
|
||||
| `subtask` | Task is a subtask of the other task. | `parenttask` |
|
||||
| `parenttask` | Task is a parent task of the other task. | `subtask` |
|
||||
| `related` | Both tasks are related to each other.<br /> How is not more specified. | ⸺ |
|
||||
| `duplicateof` | Task is a duplicate of the other task. | `duplicates` |
|
||||
| `duplicates` | Task duplicates the other task. | `duplicateof` |
|
||||
| `blocking` | Task is blocking the other task. | `blocked` |
|
||||
| `blocked` | Task is blocked by the other task. | `blocking` |
|
||||
| `precedes` | Task precedes the other task. | `follows` |
|
||||
| `follows` | Task follows the other task. | `precedes` |
|
||||
| `copiedfrom` | Task is copied from the other task. | `copiedto` |
|
||||
| `copiedto` | Task is copied to the other task. | `copiedfrom` |
|
||||
|
|
|
@ -8,22 +8,22 @@ menu:
|
|||
parent: "usage"
|
||||
---
|
||||
|
||||
# List and namespace rights for teams and users
|
||||
# Project and namespace rights for teams and users
|
||||
|
||||
Whenever you share a list or namespace with a user or team, you can specify a `rights` parameter.
|
||||
Whenever you share a project or namespace with a user or team, you can specify a `rights` parameter.
|
||||
This parameter controls the rights that team or user is going to have (or has, if you request the current sharing status).
|
||||
|
||||
Rights are being specified using integers.
|
||||
|
||||
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. |
|
||||
| Right (int) | Meaning |
|
||||
|-------------|---------------------------------------------------------------------------------------------------------------|
|
||||
| 0 (Default) | Read only. Anything which is shared with this right cannot be edited. |
|
||||
| 1 | Read and write. Namespaces or projects shared with this right can be read and written to by the team or user. |
|
||||
| 2 | Admin. Can do anything like read and write, but can additionally manage sharing options. |
|
||||
|
||||
### Team admins
|
||||
## 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.
|
||||
A team admin can also add and remove team members and also change whether a user in the team is admin or not.
|
||||
|
|
|
@ -18,4 +18,8 @@ server {
|
|||
location /docs/contact {
|
||||
return 301 $scheme://vikunja.io/en/contact;
|
||||
}
|
||||
|
||||
location /docs/docs {
|
||||
return 301 $scheme://vikunja.io/docs;
|
||||
}
|
||||
}
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 121 KiB |
Binary file not shown.
After Width: | Height: | Size: 502 KiB |
|
@ -1 +0,0 @@
|
|||
Subproject commit c0d3eccb16e0858c0b174865fd01dc0bd96b7618
|
202
go.mod
202
go.mod
|
@ -17,83 +17,139 @@
|
|||
module code.vikunja.io/api
|
||||
|
||||
require (
|
||||
4d63.com/embedfiles v1.0.0 // indirect
|
||||
4d63.com/tz v1.1.0
|
||||
code.vikunja.io/web v0.0.0-20200208214421-c90649369427
|
||||
gitea.com/xorm/tests v0.5.6 // indirect
|
||||
gitea.com/xorm/xorm-redis-cache v0.0.0-20191113062523-5a6a9e2ab9f2
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a
|
||||
github.com/beevik/etree v1.1.0 // indirect
|
||||
github.com/c2h5oh/datasize v0.0.0-20200112174442-28bbd4740fee
|
||||
github.com/client9/misspell v0.3.4
|
||||
github.com/cweill/gotests v1.5.3
|
||||
github.com/d4l3k/messagediff v1.2.1 // indirect
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
||||
github.com/fzipp/gocyclo v0.0.0-20150627053110-6acd4345c835
|
||||
github.com/garyburd/redigo v1.6.0 // indirect
|
||||
github.com/go-openapi/jsonreference v0.19.3 // indirect
|
||||
github.com/go-openapi/spec v0.19.4 // indirect
|
||||
github.com/go-redis/redis/v7 v7.2.0
|
||||
github.com/go-sql-driver/mysql v1.5.0
|
||||
github.com/go-testfixtures/testfixtures/v3 v3.1.1
|
||||
github.com/go-xorm/core v0.6.2 // indirect
|
||||
github.com/go-xorm/xorm v0.7.9 // indirect
|
||||
github.com/golang/protobuf v1.3.5 // indirect
|
||||
github.com/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf
|
||||
github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334
|
||||
github.com/imdario/mergo v0.3.9
|
||||
github.com/jgautheron/goconst v0.0.0-20200227150835-cda7ea3bf591
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/labstack/echo/v4 v4.1.16
|
||||
github.com/labstack/gommon v0.3.0
|
||||
github.com/laurent22/ical-go v0.1.1-0.20181107184520-7e5d6ade8eef
|
||||
github.com/lib/pq v1.3.0
|
||||
github.com/mailru/easyjson v0.7.0 // indirect
|
||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.4
|
||||
github.com/onsi/ginkgo v1.12.0 // indirect
|
||||
github.com/onsi/gomega v1.9.0 // indirect
|
||||
code.vikunja.io/web v0.0.0-20210706160506-d85def955bd3
|
||||
gitea.com/xorm/xorm-redis-cache v0.2.0
|
||||
github.com/ThreeDotsLabs/watermill v1.2.0
|
||||
github.com/adlio/trello v1.10.0
|
||||
github.com/arran4/golang-ical v0.0.0-20230318005454-19abf92700cc
|
||||
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef
|
||||
github.com/bbrks/go-blurhash v1.1.1
|
||||
github.com/c2h5oh/datasize v0.0.0-20220606134207-859f65c6625b
|
||||
github.com/coreos/go-oidc/v3 v3.5.0
|
||||
github.com/cweill/gotests v1.6.0
|
||||
github.com/d4l3k/messagediff v1.2.1
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/dustinkirkland/golang-petname v0.0.0-20191129215211-8e5a1ed0cff0
|
||||
github.com/gabriel-vasile/mimetype v1.4.2
|
||||
github.com/getsentry/sentry-go v0.20.0
|
||||
github.com/go-sql-driver/mysql v1.7.0
|
||||
github.com/go-testfixtures/testfixtures/v3 v3.8.1
|
||||
github.com/gocarina/gocsv v0.0.0-20230406101422-6445c2b15027
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/iancoleman/strcase v0.2.0
|
||||
github.com/imdario/mergo v0.3.15
|
||||
github.com/jinzhu/copier v0.3.5
|
||||
github.com/labstack/echo-jwt/v4 v4.1.0
|
||||
github.com/labstack/echo/v4 v4.10.2
|
||||
github.com/labstack/gommon v0.4.0
|
||||
github.com/lib/pq v1.10.8
|
||||
github.com/magefile/mage v1.14.0
|
||||
github.com/mattn/go-sqlite3 v1.14.16
|
||||
github.com/olekukonko/tablewriter v0.0.5
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||
github.com/pelletier/go-toml v1.4.0 // indirect
|
||||
github.com/prometheus/client_golang v0.9.4
|
||||
github.com/pquerna/otp v1.4.0
|
||||
github.com/prometheus/client_golang v1.15.0
|
||||
github.com/redis/go-redis/v9 v9.0.3
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/samedi/caldav-go v3.0.0+incompatible
|
||||
github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749
|
||||
github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd
|
||||
github.com/spf13/afero v1.2.2
|
||||
github.com/spf13/cobra v0.0.7
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/viper v1.6.3
|
||||
github.com/stretchr/testify v1.5.1
|
||||
github.com/swaggo/swag v1.6.3
|
||||
github.com/ulule/limiter/v3 v3.5.0
|
||||
github.com/urfave/cli v1.22.2 // indirect
|
||||
golang.org/x/crypto v0.0.0-20200406173513-056763e48d71
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e // indirect
|
||||
golang.org/x/sys v0.0.0-20200409092240-59c9f1ba88fa // indirect
|
||||
golang.org/x/tools v0.0.0-20200410194907-79a7a3126eef // indirect
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
|
||||
github.com/spf13/afero v1.9.5
|
||||
github.com/spf13/cobra v1.7.0
|
||||
github.com/spf13/viper v1.15.0
|
||||
github.com/stretchr/testify v1.8.2
|
||||
github.com/swaggo/swag v1.16.1
|
||||
github.com/tkuchiki/go-timezone v0.2.2
|
||||
github.com/ulule/limiter/v3 v3.11.1
|
||||
github.com/vectordotdev/go-datemath v0.1.1-0.20220323213446-f3954d0b18ae
|
||||
github.com/wneessen/go-mail v0.3.9
|
||||
github.com/yuin/goldmark v1.5.4
|
||||
golang.org/x/crypto v0.8.0
|
||||
golang.org/x/image v0.7.0
|
||||
golang.org/x/oauth2 v0.7.0
|
||||
golang.org/x/sync v0.1.0
|
||||
golang.org/x/sys v0.7.0
|
||||
golang.org/x/term v0.7.0
|
||||
gopkg.in/d4l3k/messagediff.v1 v1.2.1
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a
|
||||
src.techknowlogick.com/xgo v0.0.0-20200408234745-bb0faa361273
|
||||
src.techknowlogick.com/xormigrate v1.2.0
|
||||
xorm.io/builder v0.3.7
|
||||
xorm.io/core v0.7.3
|
||||
xorm.io/xorm v1.0.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
src.techknowlogick.com/xgo v1.7.1-0.20230404174715-bff48e481f81
|
||||
src.techknowlogick.com/xormigrate v1.5.0
|
||||
xorm.io/builder v0.3.12
|
||||
xorm.io/xorm v1.3.2
|
||||
)
|
||||
|
||||
replace (
|
||||
github.com/coreos/bbolt => go.etcd.io/bbolt v1.3.4
|
||||
github.com/coreos/go-systemd => github.com/coreos/go-systemd/v22 v22.0.0
|
||||
github.com/hpcloud/tail => github.com/jeffbean/tail v0.0.0-20180825121900-988c412e1aaa923bd3f9dcb7c8a75b1b58818f4f // See https://github.com/hpcloud/tail/pull/159
|
||||
github.com/samedi/caldav-go => github.com/kolaente/caldav-go v3.0.1-0.20190524174923-9e5cd1688227+incompatible // Branch: feature/dynamic-supported-components, PR: https://github.com/samedi/caldav-go/pull/6 and https://github.com/samedi/caldav-go/pull/7
|
||||
gopkg.in/fsnotify.v1 => github.com/kolaente/fsnotify v1.4.10-0.20200411160148-1bc3c8ff4048 // See https://github.com/fsnotify/fsnotify/issues/328 and https://github.com/golang/go/issues/26904
|
||||
require (
|
||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||
github.com/PuerkitoBio/purell v1.1.1 // indirect
|
||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
|
||||
github.com/beevik/etree v1.1.0 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
||||
github.com/cenkalti/backoff/v3 v3.2.2 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/garyburd/redigo v1.6.0 // indirect
|
||||
github.com/ghodss/yaml v1.0.0 // indirect
|
||||
github.com/go-chi/chi/v5 v5.0.8 // indirect
|
||||
github.com/go-jose/go-jose/v3 v3.0.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||
github.com/go-openapi/jsonreference v0.19.6 // indirect
|
||||
github.com/go-openapi/spec v0.20.4 // indirect
|
||||
github.com/go-openapi/swag v0.19.15 // indirect
|
||||
github.com/goccy/go-json v0.9.11 // indirect
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/laurent22/ical-go v0.1.1-0.20181107184520-7e5d6ade8eef // indirect
|
||||
github.com/lithammer/shortuuid/v3 v3.0.7 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.9 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/oklog/ulid v1.3.1 // indirect
|
||||
github.com/onsi/ginkgo v1.16.4 // indirect
|
||||
github.com/onsi/gomega v1.16.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_model v0.3.0 // indirect
|
||||
github.com/prometheus/common v0.42.0 // indirect
|
||||
github.com/prometheus/procfs v0.9.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/spf13/cast v1.5.0 // indirect
|
||||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/subosito/gotenv v1.4.2 // indirect
|
||||
github.com/syndtr/goleveldb v1.0.0 // indirect
|
||||
github.com/urfave/cli/v2 v2.3.0 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||
golang.org/x/mod v0.9.0 // indirect
|
||||
golang.org/x/net v0.9.0 // indirect
|
||||
golang.org/x/text v0.9.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
golang.org/x/tools v0.7.0 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/protobuf v1.30.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
sigs.k8s.io/yaml v1.3.0 // indirect
|
||||
)
|
||||
|
||||
go 1.13
|
||||
replace github.com/samedi/caldav-go => github.com/kolaente/caldav-go v3.0.1-0.20190610114120-2a4eb8b5dcc9+incompatible // Branch: feature/dynamic-supported-components, PR: https://github.com/samedi/caldav-go/pull/6 and https://github.com/samedi/caldav-go/pull/7
|
||||
|
||||
go 1.20
|
||||
|
|
File diff suppressed because it is too large
Load Diff
8
main.go
8
main.go
|
@ -1,17 +1,17 @@
|
|||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
|
||||
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// it under the terms of the GNU Affero General Public Licensee as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
// GNU Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// You should have received a copy of the GNU Affero General Public Licensee
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
name: "vikunja"
|
||||
arch: "amd64"
|
||||
platform: "linux"
|
||||
version: "<version>"
|
||||
description: "Vikunja is an open-source todo application, written in Go. It lets you create lists,tasks and share them via teams or directly between users."
|
||||
maintainer: "Vikunja Maintainers <maintainers@vikunja.io>"
|
||||
homepage: "https://vikunja.io"
|
||||
section: "default"
|
||||
priority: "extra"
|
||||
license: "AGPLv3"
|
||||
depends:
|
||||
- systemd
|
||||
contents:
|
||||
- src: <binlocation>
|
||||
dst: /opt/vikunja/vikunja
|
||||
- src: ./config.yml.sample
|
||||
dst: /etc/vikunja/config.yml
|
||||
type: "config"
|
||||
- src: /opt/vikunja/vikunja
|
||||
dst: /usr/local/bin/vikunja
|
||||
type: "symlink"
|
||||
- src: vikunja.service
|
||||
dst: /usr/lib/systemd/system/vikunja.service
|
||||
scripts:
|
||||
postinstall: ./build/after-install.sh
|
|
@ -1,71 +1,68 @@
|
|||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
|
||||
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// it under the terms of the GNU Affero General Public Licensee as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
// GNU Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// You should have received a copy of the GNU Affero General Public Licensee
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package caldav
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/timeutil"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"code.vikunja.io/api/pkg/utils"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// DateFormat ist the caldav date format
|
||||
// DateFormat is the caldav date format
|
||||
const DateFormat = `20060102T150405`
|
||||
|
||||
// Event holds a single caldav event
|
||||
type Event struct {
|
||||
Summary string
|
||||
Description string
|
||||
UID string
|
||||
Alarms []Alarm
|
||||
|
||||
Timestamp timeutil.TimeStamp
|
||||
Start timeutil.TimeStamp
|
||||
End timeutil.TimeStamp
|
||||
}
|
||||
|
||||
// Todo holds a single VTODO
|
||||
type Todo struct {
|
||||
// Required
|
||||
Timestamp timeutil.TimeStamp
|
||||
Timestamp time.Time
|
||||
UID string
|
||||
|
||||
// Optional
|
||||
Summary string
|
||||
Description string
|
||||
Completed timeutil.TimeStamp
|
||||
Completed time.Time
|
||||
Organizer *user.User
|
||||
Priority int64 // 0-9, 1 is highest
|
||||
RelatedToUID string
|
||||
Color string
|
||||
Categories []string
|
||||
Start time.Time
|
||||
End time.Time
|
||||
DueDate time.Time
|
||||
Duration time.Duration
|
||||
RepeatAfter int64
|
||||
RepeatMode models.TaskRepeatMode
|
||||
Alarms []Alarm
|
||||
|
||||
Start timeutil.TimeStamp
|
||||
End timeutil.TimeStamp
|
||||
DueDate timeutil.TimeStamp
|
||||
Duration time.Duration
|
||||
|
||||
Created timeutil.TimeStamp
|
||||
Updated timeutil.TimeStamp // last-mod
|
||||
Created time.Time
|
||||
Updated time.Time // last-mod
|
||||
}
|
||||
|
||||
// Alarm holds infos about an alarm from a caldav event
|
||||
type Alarm struct {
|
||||
Time timeutil.TimeStamp
|
||||
Time time.Time
|
||||
Duration time.Duration
|
||||
RelativeTo models.ReminderRelation
|
||||
Description string
|
||||
}
|
||||
|
||||
|
@ -73,52 +70,33 @@ type Alarm struct {
|
|||
type Config struct {
|
||||
Name string
|
||||
ProdID string
|
||||
Color string
|
||||
}
|
||||
|
||||
// ParseEvents parses an array of caldav events and gives them back as string
|
||||
func ParseEvents(config *Config, events []*Event) (caldavevents string) {
|
||||
caldavevents += `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
X-PUBLISHED-TTL:PT4H
|
||||
X-WR-CALNAME:` + config.Name + `
|
||||
PRODID:-//` + config.ProdID + `//EN`
|
||||
|
||||
for _, e := range events {
|
||||
|
||||
if e.UID == "" {
|
||||
e.UID = makeCalDavTimeFromTimeStamp(e.Timestamp) + utils.Sha256(e.Summary)
|
||||
}
|
||||
|
||||
caldavevents += `
|
||||
BEGIN:VEVENT
|
||||
UID:` + e.UID + `
|
||||
SUMMARY:` + e.Summary + `
|
||||
DESCRIPTION:` + e.Description + `
|
||||
DTSTAMP:` + makeCalDavTimeFromTimeStamp(e.Timestamp) + `
|
||||
DTSTART:` + makeCalDavTimeFromTimeStamp(e.Start) + `
|
||||
DTEND:` + makeCalDavTimeFromTimeStamp(e.End)
|
||||
|
||||
for _, a := range e.Alarms {
|
||||
if a.Description == "" {
|
||||
a.Description = e.Summary
|
||||
}
|
||||
|
||||
caldavevents += `
|
||||
BEGIN:VALARM
|
||||
TRIGGER:` + calcAlarmDateFromReminder(e.Start, a.Time) + `
|
||||
ACTION:DISPLAY
|
||||
DESCRIPTION:` + a.Description + `
|
||||
END:VALARM`
|
||||
}
|
||||
caldavevents += `
|
||||
END:VEVENT`
|
||||
func getCaldavColor(color string) (caldavcolor string) {
|
||||
if color == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
caldavevents += `
|
||||
END:VCALENDAR` // Need a line break
|
||||
if !strings.HasPrefix(color, "#") {
|
||||
color = "#" + color
|
||||
}
|
||||
|
||||
return
|
||||
color += "FF"
|
||||
|
||||
return `
|
||||
X-APPLE-CALENDAR-COLOR:` + color + `
|
||||
X-OUTLOOK-COLOR:` + color + `
|
||||
X-FUNAMBOL-COLOR:` + color
|
||||
}
|
||||
|
||||
func formatDuration(duration time.Duration) string {
|
||||
seconds := duration.Seconds() - duration.Minutes()*60
|
||||
minutes := duration.Minutes() - duration.Hours()*60
|
||||
|
||||
return strconv.FormatFloat(duration.Hours(), 'f', 0, 64) + `H` +
|
||||
strconv.FormatFloat(minutes, 'f', 0, 64) + `M` +
|
||||
strconv.FormatFloat(seconds, 'f', 0, 64) + `S`
|
||||
}
|
||||
|
||||
// ParseTodos returns a caldav vcalendar string with todos
|
||||
|
@ -128,7 +106,7 @@ VERSION:2.0
|
|||
METHOD:PUBLISH
|
||||
X-PUBLISHED-TTL:PT4H
|
||||
X-WR-CALNAME:` + config.Name + `
|
||||
PRODID:-//` + config.ProdID + `//EN`
|
||||
PRODID:-//` + config.ProdID + `//EN` + getCaldavColor(config.Color)
|
||||
|
||||
for _, t := range todos {
|
||||
if t.UID == "" {
|
||||
|
@ -139,23 +117,30 @@ PRODID:-//` + config.ProdID + `//EN`
|
|||
BEGIN:VTODO
|
||||
UID:` + t.UID + `
|
||||
DTSTAMP:` + makeCalDavTimeFromTimeStamp(t.Timestamp) + `
|
||||
SUMMARY:` + t.Summary
|
||||
SUMMARY:` + t.Summary + getCaldavColor(t.Color)
|
||||
|
||||
if t.Start != 0 {
|
||||
if t.Start.Unix() > 0 {
|
||||
caldavtodos += `
|
||||
DTSTART: ` + makeCalDavTimeFromTimeStamp(t.Start)
|
||||
DTSTART:` + makeCalDavTimeFromTimeStamp(t.Start)
|
||||
if t.Duration != 0 && t.DueDate.Unix() == 0 {
|
||||
caldavtodos += `
|
||||
DURATION:PT` + formatDuration(t.Duration)
|
||||
}
|
||||
}
|
||||
if t.End != 0 {
|
||||
if t.End.Unix() > 0 {
|
||||
caldavtodos += `
|
||||
DTEND: ` + makeCalDavTimeFromTimeStamp(t.End)
|
||||
DTEND:` + makeCalDavTimeFromTimeStamp(t.End)
|
||||
}
|
||||
if t.Description != "" {
|
||||
re := regexp.MustCompile(`\r?\n`)
|
||||
formattedDescription := re.ReplaceAllString(t.Description, "\\n")
|
||||
caldavtodos += `
|
||||
DESCRIPTION:` + t.Description
|
||||
DESCRIPTION:` + formattedDescription
|
||||
}
|
||||
if t.Completed != 0 {
|
||||
if t.Completed.Unix() > 0 {
|
||||
caldavtodos += `
|
||||
COMPLETED: ` + makeCalDavTimeFromTimeStamp(t.Completed)
|
||||
COMPLETED:` + makeCalDavTimeFromTimeStamp(t.Completed) + `
|
||||
STATUS:COMPLETED`
|
||||
}
|
||||
if t.Organizer != nil {
|
||||
caldavtodos += `
|
||||
|
@ -167,29 +152,39 @@ ORGANIZER;CN=:` + t.Organizer.Username
|
|||
RELATED-TO:` + t.RelatedToUID
|
||||
}
|
||||
|
||||
if t.DueDate != 0 {
|
||||
if t.DueDate.Unix() > 0 {
|
||||
caldavtodos += `
|
||||
DUE:` + makeCalDavTimeFromTimeStamp(t.DueDate)
|
||||
}
|
||||
|
||||
if t.Created != 0 {
|
||||
if t.Created.Unix() > 0 {
|
||||
caldavtodos += `
|
||||
CREATED:` + makeCalDavTimeFromTimeStamp(t.Created)
|
||||
}
|
||||
|
||||
if t.Duration != 0 {
|
||||
caldavtodos += `
|
||||
DURATION:PT` + fmt.Sprintf("%.6f", t.Duration.Hours()) + `H` + fmt.Sprintf("%.6f", t.Duration.Minutes()) + `M` + fmt.Sprintf("%.6f", t.Duration.Seconds()) + `S`
|
||||
}
|
||||
|
||||
if t.Priority != 0 {
|
||||
caldavtodos += `
|
||||
PRIORITY:` + strconv.Itoa(int(t.Priority))
|
||||
PRIORITY:` + strconv.Itoa(mapPriorityToCaldav(t.Priority))
|
||||
}
|
||||
|
||||
if t.RepeatAfter > 0 || t.RepeatMode == models.TaskRepeatModeMonth {
|
||||
if t.RepeatMode == models.TaskRepeatModeMonth {
|
||||
caldavtodos += `
|
||||
RRULE:FREQ=MONTHLY;BYMONTHDAY=` + t.DueDate.Format("02") // Day of the month
|
||||
} else {
|
||||
caldavtodos += `
|
||||
RRULE:FREQ=SECONDLY;INTERVAL=` + strconv.FormatInt(t.RepeatAfter, 10)
|
||||
}
|
||||
}
|
||||
|
||||
if len(t.Categories) > 0 {
|
||||
caldavtodos += `
|
||||
CATEGORIES:` + strings.Join(t.Categories, ",")
|
||||
}
|
||||
|
||||
caldavtodos += `
|
||||
LAST-MODIFIED:` + makeCalDavTimeFromTimeStamp(t.Updated)
|
||||
|
||||
caldavtodos += ParseAlarms(t.Alarms, t.Summary)
|
||||
caldavtodos += `
|
||||
END:VTODO`
|
||||
}
|
||||
|
@ -200,20 +195,42 @@ END:VCALENDAR` // Need a line break
|
|||
return
|
||||
}
|
||||
|
||||
func makeCalDavTimeFromTimeStamp(ts timeutil.TimeStamp) (caldavtime string) {
|
||||
tz, _ := time.LoadLocation("UTC")
|
||||
return ts.ToTime().In(tz).Format(DateFormat)
|
||||
func ParseAlarms(alarms []Alarm, taskDescription string) (caldavalarms string) {
|
||||
for _, a := range alarms {
|
||||
if a.Description == "" {
|
||||
a.Description = taskDescription
|
||||
}
|
||||
|
||||
caldavalarms += `
|
||||
BEGIN:VALARM`
|
||||
switch a.RelativeTo {
|
||||
case models.ReminderRelationStartDate:
|
||||
caldavalarms += `
|
||||
TRIGGER;RELATED=START:` + makeCalDavDuration(a.Duration)
|
||||
case models.ReminderRelationEndDate, models.ReminderRelationDueDate:
|
||||
caldavalarms += `
|
||||
TRIGGER;RELATED=END:` + makeCalDavDuration(a.Duration)
|
||||
default:
|
||||
caldavalarms += `
|
||||
TRIGGER;VALUE=DATE-TIME:` + makeCalDavTimeFromTimeStamp(a.Time)
|
||||
}
|
||||
caldavalarms += `
|
||||
ACTION:DISPLAY
|
||||
DESCRIPTION:` + a.Description + `
|
||||
END:VALARM`
|
||||
}
|
||||
return caldavalarms
|
||||
}
|
||||
|
||||
func calcAlarmDateFromReminder(eventStartUnix, reminderUnix timeutil.TimeStamp) (alarmTime string) {
|
||||
if eventStartUnix > reminderUnix {
|
||||
alarmTime += `-`
|
||||
func makeCalDavTimeFromTimeStamp(ts time.Time) (caldavtime string) {
|
||||
return ts.In(time.UTC).Format(DateFormat) + "Z"
|
||||
}
|
||||
|
||||
func makeCalDavDuration(duration time.Duration) (caldavtime string) {
|
||||
if duration < 0 {
|
||||
duration = duration.Abs()
|
||||
caldavtime = "-"
|
||||
}
|
||||
alarmTime += `PT`
|
||||
diff := eventStartUnix - reminderUnix
|
||||
if diff < 0 { // Make it positive
|
||||
diff = diff * -1
|
||||
}
|
||||
alarmTime += strconv.Itoa(int(diff/60)) + "M"
|
||||
caldavtime += "PT" + strings.ToUpper(duration.Truncate(time.Millisecond).String())
|
||||
return
|
||||
}
|
||||
|
|
|
@ -1,246 +1,338 @@
|
|||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
|
||||
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// it under the terms of the GNU Affero General Public Licensee as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
// GNU Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// You should have received a copy of the GNU Affero General Public Licensee
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package caldav
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
func TestParseEvents(t *testing.T) {
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestParseTodos(t *testing.T) {
|
||||
type args struct {
|
||||
config *Config
|
||||
events []*Event
|
||||
todos []*Todo
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantCaldavevents string
|
||||
name string
|
||||
args args
|
||||
wantCaldavtasks string
|
||||
}{
|
||||
{
|
||||
name: "Test caldavparsing without reminders",
|
||||
name: "Test caldavparsing with multiline description",
|
||||
args: args{
|
||||
config: &Config{
|
||||
Name: "test",
|
||||
ProdID: "RandomProdID which is not random",
|
||||
Color: "ffffff",
|
||||
},
|
||||
events: []*Event{
|
||||
todos: []*Todo{
|
||||
{
|
||||
Summary: "Event #1",
|
||||
Description: "Lorem Ipsum",
|
||||
UID: "randommduid",
|
||||
Timestamp: 1543626724,
|
||||
Start: 1543626724,
|
||||
End: 1543627824,
|
||||
},
|
||||
{
|
||||
Summary: "Event #2",
|
||||
UID: "randommduidd",
|
||||
Timestamp: 1543726724,
|
||||
Start: 1543726724,
|
||||
End: 1543738724,
|
||||
},
|
||||
{
|
||||
Summary: "Event #3 with empty uid",
|
||||
UID: "20181202T0600242aaef4a81d770c1e775e26bc5abebc87f1d3d7bffaa83",
|
||||
Timestamp: 1543726824,
|
||||
Start: 1543726824,
|
||||
End: 1543727000,
|
||||
Summary: "Todo #1",
|
||||
Description: `Lorem Ipsum
|
||||
Dolor sit amet`,
|
||||
UID: "randommduid",
|
||||
Timestamp: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||
Color: "affffe",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantCaldavevents: `BEGIN:VCALENDAR
|
||||
wantCaldavtasks: `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
X-PUBLISHED-TTL:PT4H
|
||||
X-WR-CALNAME:test
|
||||
PRODID:-//RandomProdID which is not random//EN
|
||||
BEGIN:VEVENT
|
||||
X-APPLE-CALENDAR-COLOR:#ffffffFF
|
||||
X-OUTLOOK-COLOR:#ffffffFF
|
||||
X-FUNAMBOL-COLOR:#ffffffFF
|
||||
BEGIN:VTODO
|
||||
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
|
||||
DTSTAMP:20181201T011204Z
|
||||
SUMMARY:Todo #1
|
||||
X-APPLE-CALENDAR-COLOR:#affffeFF
|
||||
X-OUTLOOK-COLOR:#affffeFF
|
||||
X-FUNAMBOL-COLOR:#affffeFF
|
||||
DESCRIPTION:Lorem Ipsum\nDolor sit amet
|
||||
LAST-MODIFIED:00010101T000000Z
|
||||
END:VTODO
|
||||
END:VCALENDAR`,
|
||||
},
|
||||
{
|
||||
name: "Test caldavparsing with reminders",
|
||||
name: "Test caldavparsing with completed task",
|
||||
args: args{
|
||||
config: &Config{
|
||||
Name: "test2",
|
||||
Name: "test",
|
||||
ProdID: "RandomProdID which is not random",
|
||||
},
|
||||
events: []*Event{
|
||||
todos: []*Todo{
|
||||
{
|
||||
Summary: "Event #1",
|
||||
Summary: "Todo #1",
|
||||
Description: "Lorem Ipsum",
|
||||
UID: "randommduid",
|
||||
Timestamp: 1543626724,
|
||||
Start: 1543626724,
|
||||
End: 1543627824,
|
||||
Alarms: []Alarm{
|
||||
{Time: 1543626524},
|
||||
{Time: 1543626224},
|
||||
{Time: 1543626024},
|
||||
},
|
||||
},
|
||||
{
|
||||
Summary: "Event #2",
|
||||
UID: "randommduidd",
|
||||
Timestamp: 1543726724,
|
||||
Start: 1543726724,
|
||||
End: 1543738724,
|
||||
Alarms: []Alarm{
|
||||
{Time: 1543626524},
|
||||
{Time: 1543626224},
|
||||
{Time: 1543626024},
|
||||
},
|
||||
},
|
||||
{
|
||||
Summary: "Event #3 with empty uid",
|
||||
Timestamp: 1543726824,
|
||||
Start: 1543726824,
|
||||
End: 1543727000,
|
||||
Alarms: []Alarm{
|
||||
{Time: 1543626524},
|
||||
{Time: 1543626224},
|
||||
{Time: 1543626024},
|
||||
{Time: 1543826824},
|
||||
},
|
||||
},
|
||||
{
|
||||
Summary: "Event #4 without any",
|
||||
Timestamp: 1543726824,
|
||||
Start: 1543726824,
|
||||
End: 1543727000,
|
||||
Timestamp: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||
Completed: time.Unix(1543627824, 0).In(config.GetTimeZone()),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantCaldavevents: `BEGIN:VCALENDAR
|
||||
wantCaldavtasks: `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
X-PUBLISHED-TTL:PT4H
|
||||
X-WR-CALNAME:test2
|
||||
X-WR-CALNAME:test
|
||||
PRODID:-//RandomProdID which is not random//EN
|
||||
BEGIN:VEVENT
|
||||
BEGIN:VTODO
|
||||
UID:randommduid
|
||||
SUMMARY:Event #1
|
||||
DTSTAMP:20181201T011204Z
|
||||
SUMMARY:Todo #1
|
||||
DESCRIPTION:Lorem Ipsum
|
||||
DTSTAMP:20181201T011204
|
||||
DTSTART:20181201T011204
|
||||
DTEND:20181201T013024
|
||||
COMPLETED:20181201T013024Z
|
||||
STATUS:COMPLETED
|
||||
LAST-MODIFIED:00010101T000000Z
|
||||
END:VTODO
|
||||
END:VCALENDAR`,
|
||||
},
|
||||
{
|
||||
name: "with priority",
|
||||
args: args{
|
||||
config: &Config{
|
||||
Name: "test",
|
||||
ProdID: "RandomProdID which is not random",
|
||||
},
|
||||
todos: []*Todo{
|
||||
{
|
||||
Summary: "Todo #1",
|
||||
Description: "Lorem Ipsum",
|
||||
UID: "randommduid",
|
||||
Priority: 1,
|
||||
Timestamp: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantCaldavtasks: `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
X-PUBLISHED-TTL:PT4H
|
||||
X-WR-CALNAME:test
|
||||
PRODID:-//RandomProdID which is not random//EN
|
||||
BEGIN:VTODO
|
||||
UID:randommduid
|
||||
DTSTAMP:20181201T011204Z
|
||||
SUMMARY:Todo #1
|
||||
DESCRIPTION:Lorem Ipsum
|
||||
PRIORITY:9
|
||||
LAST-MODIFIED:00010101T000000Z
|
||||
END:VTODO
|
||||
END:VCALENDAR`,
|
||||
},
|
||||
{
|
||||
name: "with repeating monthly",
|
||||
args: args{
|
||||
config: &Config{
|
||||
Name: "test",
|
||||
ProdID: "RandomProdID which is not random",
|
||||
},
|
||||
todos: []*Todo{
|
||||
{
|
||||
Summary: "Todo #1",
|
||||
Description: "Lorem Ipsum",
|
||||
UID: "randommduid",
|
||||
Timestamp: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||
RepeatMode: models.TaskRepeatModeMonth,
|
||||
DueDate: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantCaldavtasks: `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
X-PUBLISHED-TTL:PT4H
|
||||
X-WR-CALNAME:test
|
||||
PRODID:-//RandomProdID which is not random//EN
|
||||
BEGIN:VTODO
|
||||
UID:randommduid
|
||||
DTSTAMP:20181201T011204Z
|
||||
SUMMARY:Todo #1
|
||||
DESCRIPTION:Lorem Ipsum
|
||||
DUE:20181201T011204Z
|
||||
RRULE:FREQ=MONTHLY;BYMONTHDAY=01
|
||||
LAST-MODIFIED:00010101T000000Z
|
||||
END:VTODO
|
||||
END:VCALENDAR`,
|
||||
},
|
||||
{
|
||||
name: "with repeat mode default",
|
||||
args: args{
|
||||
config: &Config{
|
||||
Name: "test",
|
||||
ProdID: "RandomProdID which is not random",
|
||||
},
|
||||
todos: []*Todo{
|
||||
{
|
||||
Summary: "Todo #1",
|
||||
Description: "Lorem Ipsum",
|
||||
UID: "randommduid",
|
||||
Timestamp: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||
RepeatMode: models.TaskRepeatModeDefault,
|
||||
DueDate: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||
RepeatAfter: 435,
|
||||
},
|
||||
},
|
||||
},
|
||||
wantCaldavtasks: `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
X-PUBLISHED-TTL:PT4H
|
||||
X-WR-CALNAME:test
|
||||
PRODID:-//RandomProdID which is not random//EN
|
||||
BEGIN:VTODO
|
||||
UID:randommduid
|
||||
DTSTAMP:20181201T011204Z
|
||||
SUMMARY:Todo #1
|
||||
DESCRIPTION:Lorem Ipsum
|
||||
DUE:20181201T011204Z
|
||||
RRULE:FREQ=SECONDLY;INTERVAL=435
|
||||
LAST-MODIFIED:00010101T000000Z
|
||||
END:VTODO
|
||||
END:VCALENDAR`,
|
||||
},
|
||||
{
|
||||
name: "with categories",
|
||||
args: args{
|
||||
config: &Config{
|
||||
Name: "test",
|
||||
ProdID: "RandomProdID which is not random",
|
||||
Color: "ffffff",
|
||||
},
|
||||
todos: []*Todo{
|
||||
{
|
||||
Summary: "Todo #1",
|
||||
UID: "randommduid",
|
||||
Timestamp: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||
Color: "affffe",
|
||||
Categories: []string{"label1", "label2"},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantCaldavtasks: `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
X-PUBLISHED-TTL:PT4H
|
||||
X-WR-CALNAME:test
|
||||
PRODID:-//RandomProdID which is not random//EN
|
||||
X-APPLE-CALENDAR-COLOR:#ffffffFF
|
||||
X-OUTLOOK-COLOR:#ffffffFF
|
||||
X-FUNAMBOL-COLOR:#ffffffFF
|
||||
BEGIN:VTODO
|
||||
UID:randommduid
|
||||
DTSTAMP:20181201T011204Z
|
||||
SUMMARY:Todo #1
|
||||
X-APPLE-CALENDAR-COLOR:#affffeFF
|
||||
X-OUTLOOK-COLOR:#affffeFF
|
||||
X-FUNAMBOL-COLOR:#affffeFF
|
||||
CATEGORIES:label1,label2
|
||||
LAST-MODIFIED:00010101T000000Z
|
||||
END:VTODO
|
||||
END:VCALENDAR`,
|
||||
},
|
||||
{
|
||||
name: "with alarm",
|
||||
args: args{
|
||||
config: &Config{
|
||||
Name: "test",
|
||||
ProdID: "RandomProdID which is not random",
|
||||
},
|
||||
todos: []*Todo{
|
||||
{
|
||||
Summary: "Todo #1",
|
||||
UID: "randommduid",
|
||||
Timestamp: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||
Alarms: []Alarm{
|
||||
{
|
||||
Time: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||
},
|
||||
{
|
||||
Time: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||
Description: "alarm description",
|
||||
},
|
||||
{
|
||||
Duration: -2 * time.Hour,
|
||||
RelativeTo: "due_date",
|
||||
},
|
||||
{
|
||||
Duration: 1 * time.Hour,
|
||||
RelativeTo: "start_date",
|
||||
},
|
||||
{
|
||||
Duration: time.Duration(0),
|
||||
RelativeTo: "end_date",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantCaldavtasks: `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
X-PUBLISHED-TTL:PT4H
|
||||
X-WR-CALNAME:test
|
||||
PRODID:-//RandomProdID which is not random//EN
|
||||
BEGIN:VTODO
|
||||
UID:randommduid
|
||||
DTSTAMP:20181201T011204Z
|
||||
SUMMARY:Todo #1
|
||||
LAST-MODIFIED:00010101T000000Z
|
||||
BEGIN:VALARM
|
||||
TRIGGER:-PT3M
|
||||
TRIGGER;VALUE=DATE-TIME:20181201T011204Z
|
||||
ACTION:DISPLAY
|
||||
DESCRIPTION:Event #1
|
||||
DESCRIPTION:Todo #1
|
||||
END:VALARM
|
||||
BEGIN:VALARM
|
||||
TRIGGER:-PT8M
|
||||
TRIGGER;VALUE=DATE-TIME:20181201T011204Z
|
||||
ACTION:DISPLAY
|
||||
DESCRIPTION:Event #1
|
||||
DESCRIPTION:alarm description
|
||||
END:VALARM
|
||||
BEGIN:VALARM
|
||||
TRIGGER:-PT11M
|
||||
TRIGGER;RELATED=END:-PT2H0M0S
|
||||
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
|
||||
DESCRIPTION:Todo #1
|
||||
END:VALARM
|
||||
BEGIN:VALARM
|
||||
TRIGGER:-PT1675M
|
||||
TRIGGER;RELATED=START:PT1H0M0S
|
||||
ACTION:DISPLAY
|
||||
DESCRIPTION:Event #2
|
||||
DESCRIPTION:Todo #1
|
||||
END:VALARM
|
||||
BEGIN:VALARM
|
||||
TRIGGER:-PT1678M
|
||||
TRIGGER;RELATED=END:PT0S
|
||||
ACTION:DISPLAY
|
||||
DESCRIPTION:Event #2
|
||||
DESCRIPTION:Todo #1
|
||||
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:VTODO
|
||||
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)
|
||||
}
|
||||
gotCaldavtasks := ParseTodos(tt.args.config, tt.args.todos)
|
||||
assert.Equal(t, tt.wantCaldavtasks, gotCaldavtasks)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,242 @@
|
|||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public Licensee as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public Licensee
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package caldav
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/api/pkg/utils"
|
||||
|
||||
ics "github.com/arran4/golang-ical"
|
||||
)
|
||||
|
||||
func GetCaldavTodosForTasks(project *models.ProjectWithTasksAndBuckets, projectTasks []*models.TaskWithComments) string {
|
||||
|
||||
// Make caldav todos from Vikunja todos
|
||||
var caldavtodos []*Todo
|
||||
for _, t := range projectTasks {
|
||||
|
||||
duration := t.EndDate.Sub(t.StartDate)
|
||||
var categories []string
|
||||
for _, label := range t.Labels {
|
||||
categories = append(categories, label.Title)
|
||||
}
|
||||
var alarms []Alarm
|
||||
for _, reminder := range t.Reminders {
|
||||
alarms = append(alarms, Alarm{
|
||||
Time: reminder.Reminder,
|
||||
Duration: time.Duration(reminder.RelativePeriod) * time.Second,
|
||||
RelativeTo: reminder.RelativeTo,
|
||||
})
|
||||
}
|
||||
|
||||
caldavtodos = append(caldavtodos, &Todo{
|
||||
Timestamp: t.Updated,
|
||||
UID: t.UID,
|
||||
Summary: t.Title,
|
||||
Description: t.Description,
|
||||
Completed: t.DoneAt,
|
||||
// Organizer: &t.CreatedBy, // Disabled until we figure out how this works
|
||||
Categories: categories,
|
||||
Priority: t.Priority,
|
||||
Start: t.StartDate,
|
||||
End: t.EndDate,
|
||||
Created: t.Created,
|
||||
Updated: t.Updated,
|
||||
DueDate: t.DueDate,
|
||||
Duration: duration,
|
||||
RepeatAfter: t.RepeatAfter,
|
||||
RepeatMode: t.RepeatMode,
|
||||
Alarms: alarms,
|
||||
})
|
||||
}
|
||||
|
||||
caldavConfig := &Config{
|
||||
Name: project.Title,
|
||||
ProdID: "Vikunja Todo App",
|
||||
}
|
||||
|
||||
return ParseTodos(caldavConfig, caldavtodos)
|
||||
}
|
||||
|
||||
func ParseTaskFromVTODO(content string) (vTask *models.Task, err error) {
|
||||
parsed, err := ics.ParseCalendar(strings.NewReader(content))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vTodo, ok := parsed.Components[0].(*ics.VTodo)
|
||||
if !ok {
|
||||
return nil, errors.New("VTODO element not found")
|
||||
}
|
||||
// We put the vTodo details in a map to be able to handle them more easily
|
||||
task := make(map[string]ics.IANAProperty)
|
||||
for _, c := range vTodo.UnknownPropertiesIANAProperties() {
|
||||
task[c.IANAToken] = c
|
||||
}
|
||||
|
||||
// Parse the priority
|
||||
var priority int64
|
||||
if _, ok := task["PRIORITY"]; ok {
|
||||
priorityParsed, err := strconv.ParseInt(task["PRIORITY"].Value, 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
priority = parseVTODOPriority(priorityParsed)
|
||||
}
|
||||
|
||||
// Parse the enddate
|
||||
duration, _ := time.ParseDuration(task["DURATION"].Value)
|
||||
|
||||
description := strings.ReplaceAll(task["DESCRIPTION"].Value, "\\,", ",")
|
||||
description = strings.ReplaceAll(description, "\\n", "\n")
|
||||
|
||||
var labels []*models.Label
|
||||
if val, ok := task["CATEGORIES"]; ok {
|
||||
categories := strings.Split(val.Value, ",")
|
||||
labels = make([]*models.Label, 0, len(categories))
|
||||
for _, category := range categories {
|
||||
labels = append(labels, &models.Label{
|
||||
Title: category,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
vTask = &models.Task{
|
||||
UID: task["UID"].Value,
|
||||
Title: task["SUMMARY"].Value,
|
||||
Description: description,
|
||||
Priority: priority,
|
||||
Labels: labels,
|
||||
DueDate: caldavTimeToTimestamp(task["DUE"]),
|
||||
Updated: caldavTimeToTimestamp(task["DTSTAMP"]),
|
||||
StartDate: caldavTimeToTimestamp(task["DTSTART"]),
|
||||
DoneAt: caldavTimeToTimestamp(task["COMPLETED"]),
|
||||
}
|
||||
|
||||
if task["STATUS"].Value == "COMPLETED" {
|
||||
vTask.Done = true
|
||||
}
|
||||
|
||||
if duration > 0 && !vTask.StartDate.IsZero() {
|
||||
vTask.EndDate = vTask.StartDate.Add(duration)
|
||||
}
|
||||
|
||||
for _, vAlarm := range vTodo.SubComponents() {
|
||||
if vAlarm, ok := vAlarm.(*ics.VAlarm); ok {
|
||||
vTask = parseVAlarm(vAlarm, vTask)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func parseVAlarm(vAlarm *ics.VAlarm, vTask *models.Task) *models.Task {
|
||||
for _, property := range vAlarm.UnknownPropertiesIANAProperties() {
|
||||
if property.IANAToken != "TRIGGER" {
|
||||
continue
|
||||
}
|
||||
|
||||
if contains(property.ICalParameters["VALUE"], "DATE-TIME") {
|
||||
// Example: TRIGGER;VALUE=DATE-TIME:20181201T011210Z
|
||||
vTask.Reminders = append(vTask.Reminders, &models.TaskReminder{
|
||||
Reminder: caldavTimeToTimestamp(property),
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
duration := utils.ParseISO8601Duration(property.Value)
|
||||
|
||||
if contains(property.ICalParameters["RELATED"], "END") {
|
||||
// Example: TRIGGER;RELATED=END:-P2D
|
||||
if vTask.EndDate.IsZero() {
|
||||
vTask.Reminders = append(vTask.Reminders, &models.TaskReminder{
|
||||
RelativePeriod: int64(duration.Seconds()),
|
||||
RelativeTo: models.ReminderRelationDueDate})
|
||||
} else {
|
||||
vTask.Reminders = append(vTask.Reminders, &models.TaskReminder{
|
||||
RelativePeriod: int64(duration.Seconds()),
|
||||
RelativeTo: models.ReminderRelationEndDate})
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Example: TRIGGER;RELATED=START:-P2D
|
||||
// Example: TRIGGER:-PT60M
|
||||
vTask.Reminders = append(vTask.Reminders, &models.TaskReminder{
|
||||
RelativePeriod: int64(duration.Seconds()),
|
||||
RelativeTo: models.ReminderRelationStartDate})
|
||||
}
|
||||
return vTask
|
||||
}
|
||||
|
||||
func contains(array []string, str string) bool {
|
||||
for _, value := range array {
|
||||
if value == str {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// https://tools.ietf.org/html/rfc5545#section-3.3.5
|
||||
func caldavTimeToTimestamp(ianaProperty ics.IANAProperty) time.Time {
|
||||
tstring := ianaProperty.Value
|
||||
if tstring == "" {
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
format := DateFormat
|
||||
|
||||
if strings.HasSuffix(tstring, "Z") {
|
||||
format = `20060102T150405Z`
|
||||
}
|
||||
|
||||
if len(tstring) == 8 {
|
||||
format = `20060102`
|
||||
}
|
||||
|
||||
var t time.Time
|
||||
var err error
|
||||
tzParameter := ianaProperty.ICalParameters["TZID"]
|
||||
if len(tzParameter) > 0 {
|
||||
loc, err := time.LoadLocation(tzParameter[0])
|
||||
if err != nil {
|
||||
log.Warningf("Error while parsing caldav timezone %s: %s", tzParameter[0], err)
|
||||
} else {
|
||||
t, err = time.ParseInLocation(format, tstring, loc)
|
||||
if err != nil {
|
||||
log.Warningf("Error while parsing caldav time %s to TimeStamp: %s at location %s", tstring, loc, err)
|
||||
} else {
|
||||
t = t.In(config.GetTimeZone())
|
||||
return t
|
||||
}
|
||||
}
|
||||
}
|
||||
t, err = time.Parse(format, tstring)
|
||||
if err != nil {
|
||||
log.Warningf("Error while parsing caldav time %s to TimeStamp: %s", tstring, err)
|
||||
return time.Time{}
|
||||
}
|
||||
return t
|
||||
}
|
|
@ -0,0 +1,406 @@
|
|||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public Licensee as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public Licensee
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package caldav
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"gopkg.in/d4l3k/messagediff.v1"
|
||||
)
|
||||
|
||||
func TestParseTaskFromVTODO(t *testing.T) {
|
||||
type args struct {
|
||||
content string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantVTask *models.Task
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "normal",
|
||||
args: args{content: `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
X-PUBLISHED-TTL:PT4H
|
||||
X-WR-CALNAME:test
|
||||
PRODID:-//RandomProdID which is not random//EN
|
||||
BEGIN:VTODO
|
||||
UID:randomuid
|
||||
DTSTAMP:20181201T011204
|
||||
SUMMARY:Todo #1
|
||||
DESCRIPTION:Lorem Ipsum
|
||||
LAST-MODIFIED:00010101T000000
|
||||
END:VTODO
|
||||
END:VCALENDAR`,
|
||||
},
|
||||
wantVTask: &models.Task{
|
||||
Title: "Todo #1",
|
||||
UID: "randomuid",
|
||||
Description: "Lorem Ipsum",
|
||||
Updated: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "With priority",
|
||||
args: args{content: `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
X-PUBLISHED-TTL:PT4H
|
||||
X-WR-CALNAME:test
|
||||
PRODID:-//RandomProdID which is not random//EN
|
||||
BEGIN:VTODO
|
||||
UID:randomuid
|
||||
DTSTAMP:20181201T011204
|
||||
SUMMARY:Todo #1
|
||||
DESCRIPTION:Lorem Ipsum
|
||||
PRIORITY:9
|
||||
LAST-MODIFIED:00010101T000000
|
||||
END:VTODO
|
||||
END:VCALENDAR`,
|
||||
},
|
||||
wantVTask: &models.Task{
|
||||
Title: "Todo #1",
|
||||
UID: "randomuid",
|
||||
Description: "Lorem Ipsum",
|
||||
Priority: 1,
|
||||
Updated: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "With categories",
|
||||
args: args{content: `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
X-PUBLISHED-TTL:PT4H
|
||||
X-WR-CALNAME:test
|
||||
PRODID:-//RandomProdID which is not random//EN
|
||||
BEGIN:VTODO
|
||||
UID:randomuid
|
||||
DTSTAMP:20181201T011204
|
||||
SUMMARY:Todo #1
|
||||
DESCRIPTION:Lorem Ipsum
|
||||
CATEGORIES:cat1,cat2
|
||||
LAST-MODIFIED:00010101T000000
|
||||
END:VTODO
|
||||
END:VCALENDAR`,
|
||||
},
|
||||
wantVTask: &models.Task{
|
||||
Title: "Todo #1",
|
||||
UID: "randomuid",
|
||||
Description: "Lorem Ipsum",
|
||||
Labels: []*models.Label{
|
||||
{
|
||||
Title: "cat1",
|
||||
},
|
||||
{
|
||||
Title: "cat2",
|
||||
},
|
||||
},
|
||||
Updated: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "With alarm (time trigger)",
|
||||
args: args{content: `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
X-PUBLISHED-TTL:PT4H
|
||||
X-WR-CALNAME:test
|
||||
PRODID:-//RandomProdID which is not random//EN
|
||||
BEGIN:VTODO
|
||||
UID:randomuid
|
||||
DTSTAMP:20181201T011204
|
||||
SUMMARY:Todo #1
|
||||
DESCRIPTION:Lorem Ipsum
|
||||
BEGIN:VALARM
|
||||
TRIGGER;VALUE=DATE-TIME:20181201T011210Z
|
||||
ACTION:DISPLAY
|
||||
END:VALARM
|
||||
END:VTODO
|
||||
END:VCALENDAR`,
|
||||
},
|
||||
wantVTask: &models.Task{
|
||||
Title: "Todo #1",
|
||||
UID: "randomuid",
|
||||
Description: "Lorem Ipsum",
|
||||
Reminders: []*models.TaskReminder{
|
||||
{
|
||||
Reminder: time.Date(2018, 12, 1, 1, 12, 10, 0, config.GetTimeZone()),
|
||||
},
|
||||
},
|
||||
Updated: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "With alarm (relative trigger)",
|
||||
args: args{content: `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
X-PUBLISHED-TTL:PT4H
|
||||
X-WR-CALNAME:test
|
||||
PRODID:-//RandomProdID which is not random//EN
|
||||
BEGIN:VTODO
|
||||
UID:randomuid
|
||||
DTSTAMP:20181201T011204
|
||||
SUMMARY:Todo #1
|
||||
DESCRIPTION:Lorem Ipsum
|
||||
DTSTART:20230228T170000Z
|
||||
DUE:20230304T150000Z
|
||||
BEGIN:VALARM
|
||||
TRIGGER:PT0S
|
||||
ACTION:DISPLAY
|
||||
END:VALARM
|
||||
BEGIN:VALARM
|
||||
TRIGGER;VALUE=DURATION:-PT60M
|
||||
ACTION:DISPLAY
|
||||
END:VALARM
|
||||
BEGIN:VALARM
|
||||
TRIGGER:-PT61M
|
||||
ACTION:DISPLAY
|
||||
END:VALARM
|
||||
BEGIN:VALARM
|
||||
TRIGGER;RELATED=START:-P1D
|
||||
ACTION:DISPLAY
|
||||
END:VALARM
|
||||
BEGIN:VALARM
|
||||
TRIGGER;RELATED=END:-PT30M
|
||||
ACTION:DISPLAY
|
||||
END:VALARM
|
||||
END:VTODO
|
||||
END:VCALENDAR`,
|
||||
},
|
||||
wantVTask: &models.Task{
|
||||
Title: "Todo #1",
|
||||
UID: "randomuid",
|
||||
Description: "Lorem Ipsum",
|
||||
StartDate: time.Date(2023, 2, 28, 17, 0, 0, 0, config.GetTimeZone()),
|
||||
DueDate: time.Date(2023, 3, 4, 15, 0, 0, 0, config.GetTimeZone()),
|
||||
Reminders: []*models.TaskReminder{
|
||||
{
|
||||
RelativeTo: models.ReminderRelationStartDate,
|
||||
RelativePeriod: 0,
|
||||
},
|
||||
{
|
||||
RelativeTo: models.ReminderRelationStartDate,
|
||||
RelativePeriod: -3600,
|
||||
},
|
||||
{
|
||||
RelativeTo: models.ReminderRelationStartDate,
|
||||
RelativePeriod: -3660,
|
||||
},
|
||||
{
|
||||
RelativeTo: models.ReminderRelationStartDate,
|
||||
RelativePeriod: -86400,
|
||||
},
|
||||
{
|
||||
RelativeTo: models.ReminderRelationDueDate,
|
||||
RelativePeriod: -1800,
|
||||
},
|
||||
},
|
||||
Updated: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "example task from tasks.org app",
|
||||
args: args{content: `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
PRODID:+//IDN tasks.org//android-130102//EN
|
||||
BEGIN:VTODO
|
||||
DTSTAMP:20230402T074158Z
|
||||
UID:4290517349243274514
|
||||
CREATED:20230402T060451Z
|
||||
LAST-MODIFIED:20230402T074154Z
|
||||
SUMMARY:Test with tasks.org
|
||||
PRIORITY:9
|
||||
CATEGORIES:Vikunja
|
||||
X-APPLE-SORT-ORDER:697384109
|
||||
DUE;TZID=Europe/Berlin:20230402T170001
|
||||
DTSTART;TZID=Europe/Berlin:20230401T090000
|
||||
BEGIN:VALARM
|
||||
TRIGGER;RELATED=END:PT0S
|
||||
ACTION:DISPLAY
|
||||
DESCRIPTION:Default Tasks.org description
|
||||
END:VALARM
|
||||
BEGIN:VALARM
|
||||
TRIGGER;VALUE=DATE-TIME:20230402T100000Z
|
||||
ACTION:DISPLAY
|
||||
DESCRIPTION:Default Tasks.org description
|
||||
END:VALARM
|
||||
END:VTODO
|
||||
BEGIN:VTIMEZONE
|
||||
TZID:Europe/Berlin
|
||||
LAST-MODIFIED:20220816T024022Z
|
||||
BEGIN:DAYLIGHT
|
||||
TZNAME:CEST
|
||||
TZOFFSETFROM:+0100
|
||||
TZOFFSETTO:+0200
|
||||
DTSTART:19810329T020000
|
||||
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU
|
||||
END:DAYLIGHT
|
||||
BEGIN:STANDARD
|
||||
TZNAME:CET
|
||||
TZOFFSETFROM:+0200
|
||||
TZOFFSETTO:+0100
|
||||
DTSTART:19961027T030000
|
||||
RRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU
|
||||
END:STANDARD
|
||||
END:VTIMEZONE
|
||||
END:VCALENDAR`,
|
||||
},
|
||||
wantVTask: &models.Task{
|
||||
Updated: time.Date(2023, 4, 2, 7, 41, 58, 0, config.GetTimeZone()),
|
||||
UID: "4290517349243274514",
|
||||
Title: "Test with tasks.org",
|
||||
Priority: 1,
|
||||
Labels: []*models.Label{
|
||||
{
|
||||
Title: "Vikunja",
|
||||
},
|
||||
},
|
||||
DueDate: time.Date(2023, 4, 2, 15, 0, 1, 0, config.GetTimeZone()),
|
||||
StartDate: time.Date(2023, 4, 1, 7, 0, 0, 0, config.GetTimeZone()),
|
||||
Reminders: []*models.TaskReminder{
|
||||
{
|
||||
RelativeTo: models.ReminderRelationDueDate,
|
||||
RelativePeriod: 0,
|
||||
},
|
||||
{
|
||||
Reminder: time.Date(2023, 4, 2, 10, 0, 0, 0, config.GetTimeZone()),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := ParseTaskFromVTODO(tt.args.content)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ParseTaskFromVTODO() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if diff, equal := messagediff.PrettyDiff(got, tt.wantVTask); !equal {
|
||||
t.Errorf("ParseTaskFromVTODO()\n gotVTask = %v\n want %v\n diff = %s", got, tt.wantVTask, diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCaldavTodosForTasks(t *testing.T) {
|
||||
type args struct {
|
||||
list *models.ProjectWithTasksAndBuckets
|
||||
tasks []*models.TaskWithComments
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantCaldav string
|
||||
}{
|
||||
{
|
||||
name: "Format single Task as CalDAV",
|
||||
args: args{
|
||||
list: &models.ProjectWithTasksAndBuckets{
|
||||
Project: models.Project{
|
||||
Title: "List title",
|
||||
},
|
||||
},
|
||||
tasks: []*models.TaskWithComments{
|
||||
{
|
||||
Task: models.Task{
|
||||
Title: "Task 1",
|
||||
UID: "randomuid",
|
||||
Description: "Description",
|
||||
Priority: 3,
|
||||
Created: time.Unix(1543626721, 0).In(config.GetTimeZone()),
|
||||
DueDate: time.Unix(1543626722, 0).In(config.GetTimeZone()),
|
||||
StartDate: time.Unix(1543626723, 0).In(config.GetTimeZone()),
|
||||
EndDate: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||
Updated: time.Unix(1543626725, 0).In(config.GetTimeZone()),
|
||||
DoneAt: time.Unix(1543626726, 0).In(config.GetTimeZone()),
|
||||
RepeatAfter: 86400,
|
||||
Labels: []*models.Label{
|
||||
{
|
||||
ID: 1,
|
||||
Title: "label1",
|
||||
},
|
||||
{
|
||||
ID: 2,
|
||||
Title: "label2",
|
||||
},
|
||||
},
|
||||
Reminders: []*models.TaskReminder{
|
||||
{
|
||||
Reminder: time.Unix(1543626730, 0).In(config.GetTimeZone()),
|
||||
},
|
||||
{
|
||||
Reminder: time.Unix(1543626731, 0).In(config.GetTimeZone()),
|
||||
RelativePeriod: -3600,
|
||||
RelativeTo: models.ReminderRelationDueDate,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantCaldav: `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
X-PUBLISHED-TTL:PT4H
|
||||
X-WR-CALNAME:List title
|
||||
PRODID:-//Vikunja Todo App//EN
|
||||
BEGIN:VTODO
|
||||
UID:randomuid
|
||||
DTSTAMP:20181201T011205Z
|
||||
SUMMARY:Task 1
|
||||
DTSTART:20181201T011203Z
|
||||
DTEND:20181201T011204Z
|
||||
DESCRIPTION:Description
|
||||
COMPLETED:20181201T011206Z
|
||||
STATUS:COMPLETED
|
||||
DUE:20181201T011202Z
|
||||
CREATED:20181201T011201Z
|
||||
PRIORITY:3
|
||||
RRULE:FREQ=SECONDLY;INTERVAL=86400
|
||||
CATEGORIES:label1,label2
|
||||
LAST-MODIFIED:20181201T011205Z
|
||||
BEGIN:VALARM
|
||||
TRIGGER;VALUE=DATE-TIME:20181201T011210Z
|
||||
ACTION:DISPLAY
|
||||
DESCRIPTION:Task 1
|
||||
END:VALARM
|
||||
BEGIN:VALARM
|
||||
TRIGGER;RELATED=END:-PT1H0M0S
|
||||
ACTION:DISPLAY
|
||||
DESCRIPTION:Task 1
|
||||
END:VALARM
|
||||
END:VTODO
|
||||
END:VCALENDAR`,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := GetCaldavTodosForTasks(tt.args.list, tt.args.tasks)
|
||||
if diff, equal := messagediff.PrettyDiff(got, tt.wantCaldav); !equal {
|
||||
t.Errorf("GetCaldavTodosForTasks() gotVTask = %v, want %v, diff = %s", got, tt.wantCaldav, diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public Licensee as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public Licensee
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package caldav
|
||||
|
||||
// In caldav, priority values are an int from 0 to 9 where 1 is the highest priority and 9 the lowest. 0 is "unset".
|
||||
// Vikunja only has priorites from 0 to 5 where 0 is unset and 5 is the highest
|
||||
// See https://icalendar.org/iCalendar-RFC-5545/3-8-1-9-priority.html
|
||||
func mapPriorityToCaldav(priority int64) (caldavPriority int) {
|
||||
switch priority {
|
||||
case 0:
|
||||
return 0
|
||||
case 1: // Low
|
||||
return 9
|
||||
case 2: // Medium
|
||||
return 5
|
||||
case 3: // High
|
||||
return 3
|
||||
case 4: // Urgent
|
||||
return 2
|
||||
case 5: // DO NOW
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// See mapPriorityToCaldav
|
||||
func parseVTODOPriority(priority int64) (vikunjaPriority int64) {
|
||||
switch priority {
|
||||
case 0:
|
||||
return 0
|
||||
case 1:
|
||||
return 5
|
||||
case 2:
|
||||
return 4
|
||||
case 3:
|
||||
return 3
|
||||
case 4:
|
||||
return 3
|
||||
case 5:
|
||||
return 2
|
||||
case 6:
|
||||
return 1
|
||||
case 7:
|
||||
return 1
|
||||
case 8:
|
||||
return 1
|
||||
case 9:
|
||||
return 1
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
|
@ -0,0 +1,131 @@
|
|||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public Licensee as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public Licensee
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package caldav
|
||||
|
||||
import "testing"
|
||||
|
||||
func Test_parseVTODOPriority(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
priority int64
|
||||
want int64
|
||||
}{
|
||||
{
|
||||
name: "unset",
|
||||
priority: 0,
|
||||
want: 0,
|
||||
},
|
||||
{
|
||||
name: "DO NOW",
|
||||
priority: 1,
|
||||
want: 5,
|
||||
},
|
||||
{
|
||||
name: "urgent",
|
||||
priority: 2,
|
||||
want: 4,
|
||||
},
|
||||
{
|
||||
name: "high 1",
|
||||
priority: 3,
|
||||
want: 3,
|
||||
},
|
||||
{
|
||||
name: "high 2",
|
||||
priority: 4,
|
||||
want: 3,
|
||||
},
|
||||
{
|
||||
name: "medium",
|
||||
priority: 5,
|
||||
want: 2,
|
||||
},
|
||||
{
|
||||
name: "low 1",
|
||||
priority: 6,
|
||||
want: 1,
|
||||
},
|
||||
{
|
||||
name: "low 2",
|
||||
priority: 7,
|
||||
want: 1,
|
||||
},
|
||||
{
|
||||
name: "low 3",
|
||||
priority: 8,
|
||||
want: 1,
|
||||
},
|
||||
{
|
||||
name: "low 4",
|
||||
priority: 9,
|
||||
want: 1,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if gotVikunjaPriority := parseVTODOPriority(tt.priority); gotVikunjaPriority != tt.want {
|
||||
t.Errorf("parseVTODOPriority() = %v, want %v", gotVikunjaPriority, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_mapPriorityToCaldav(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
priority int64
|
||||
wantCaldavPriority int
|
||||
}{
|
||||
{
|
||||
name: "unset",
|
||||
priority: 0,
|
||||
wantCaldavPriority: 0,
|
||||
},
|
||||
{
|
||||
name: "low",
|
||||
priority: 1,
|
||||
wantCaldavPriority: 9,
|
||||
},
|
||||
{
|
||||
name: "medium",
|
||||
priority: 2,
|
||||
wantCaldavPriority: 5,
|
||||
},
|
||||
{
|
||||
name: "high",
|
||||
priority: 3,
|
||||
wantCaldavPriority: 3,
|
||||
},
|
||||
{
|
||||
name: "urgent",
|
||||
priority: 4,
|
||||
wantCaldavPriority: 2,
|
||||
},
|
||||
{
|
||||
name: "DO NOW",
|
||||
priority: 5,
|
||||
wantCaldavPriority: 1,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if gotCaldavPriority := mapPriorityToCaldav(tt.priority); gotCaldavPriority != tt.wantCaldavPriority {
|
||||
t.Errorf("mapPriorityToCaldav() = %v, want %v", gotCaldavPriority, tt.wantCaldavPriority)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,39 +1,27 @@
|
|||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
|
||||
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// it under the terms of the GNU Affero General Public Licensee as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
// GNU Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// You should have received a copy of the GNU Affero General Public Licensee
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/files"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/mail"
|
||||
"code.vikunja.io/api/pkg/migration"
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
migrator "code.vikunja.io/api/pkg/modules/migration"
|
||||
"code.vikunja.io/api/pkg/red"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"fmt"
|
||||
"github.com/spf13/cobra"
|
||||
"os"
|
||||
)
|
||||
|
||||
func init() {
|
||||
cobra.OnInitialize(initialize)
|
||||
}
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "vikunja",
|
||||
|
@ -47,7 +35,8 @@ alpine areas of the Andes and a relative of the llama.
|
|||
Vikunja is a self-hosted To-Do list application with a web app and mobile apps for all platforms. It is licensed under the GPLv3.
|
||||
|
||||
Find more info at vikunja.io.`,
|
||||
Run: webCmd.Run,
|
||||
PreRun: webCmd.PreRun,
|
||||
Run: webCmd.Run,
|
||||
}
|
||||
|
||||
// Execute starts the application
|
||||
|
@ -57,42 +46,3 @@ func Execute() {
|
|||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// Initializes all kinds of things in the right order
|
||||
func initialize() {
|
||||
// Init the config
|
||||
config.InitConfig()
|
||||
|
||||
// Init redis
|
||||
red.InitRedis()
|
||||
|
||||
// Set logger
|
||||
log.InitLogger()
|
||||
|
||||
// Run the migrations
|
||||
migration.Migrate(nil)
|
||||
|
||||
// Set Engine
|
||||
err := models.SetEngine()
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
err = user.InitDB()
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
err = files.SetEngine()
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
err = migrator.InitDB()
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
|
||||
// Initialize the files handler
|
||||
files.InitFileHandler()
|
||||
|
||||
// Start the mail daemon
|
||||
mail.StartMailDaemon()
|
||||
}
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public Licensee as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public Licensee
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"code.vikunja.io/api/pkg/initialize"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/modules/dump"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(dumpCmd)
|
||||
}
|
||||
|
||||
var dumpCmd = &cobra.Command{
|
||||
Use: "dump",
|
||||
Short: "Dump all vikunja data into a zip file. Includes config, files and db.",
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
initialize.FullInit()
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
filename := "vikunja-dump_" + time.Now().Format("2006-01-02_15-03-05") + ".zip"
|
||||
if err := dump.Dump(filename); err != nil {
|
||||
log.Critical(err.Error())
|
||||
}
|
||||
},
|
||||
}
|
|
@ -1,22 +1,23 @@
|
|||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
|
||||
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// it under the terms of the GNU Affero General Public Licensee as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
// GNU Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// You should have received a copy of the GNU Affero General Public Licensee
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/initialize"
|
||||
"code.vikunja.io/api/pkg/migration"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
@ -24,7 +25,7 @@ import (
|
|||
func init() {
|
||||
migrateCmd.AddCommand(migrateListCmd)
|
||||
migrationRollbackCmd.Flags().StringVarP(&rollbackUntilFlag, "name", "n", "", "The id of the migration you want to roll back until.")
|
||||
migrationRollbackCmd.MarkFlagRequired("name")
|
||||
_ = migrationRollbackCmd.MarkFlagRequired("name")
|
||||
migrateCmd.AddCommand(migrationRollbackCmd)
|
||||
rootCmd.AddCommand(migrateCmd)
|
||||
}
|
||||
|
@ -35,6 +36,9 @@ func init() {
|
|||
var migrateCmd = &cobra.Command{
|
||||
Use: "migrate",
|
||||
Short: "Run all database migrations which didn't already run.",
|
||||
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
||||
initialize.LightInit()
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
migration.Migrate(nil)
|
||||
},
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public Licensee as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public Licensee
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/initialize"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/modules/dump"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(restoreCmd)
|
||||
}
|
||||
|
||||
var restoreCmd = &cobra.Command{
|
||||
Use: "restore [filename]",
|
||||
Short: "Restores all vikunja data from a vikunja dump.",
|
||||
Args: cobra.ExactArgs(1),
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
initialize.FullInit()
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if err := dump.Restore(args[0]); err != nil {
|
||||
log.Critical(err.Error())
|
||||
}
|
||||
},
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public Licensee as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public Licensee
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/initialize"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/mail"
|
||||
"code.vikunja.io/api/pkg/notifications"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(testmailCmd)
|
||||
}
|
||||
|
||||
var testmailCmd = &cobra.Command{
|
||||
Use: "testmail [email]",
|
||||
Short: "Send a test mail using the configured smtp connection",
|
||||
Args: cobra.ExactArgs(1),
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
initialize.LightInit()
|
||||
|
||||
// Start the mail daemon
|
||||
mail.StartMailDaemon()
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
log.Info("Sending testmail...")
|
||||
message := notifications.NewMail().
|
||||
From("Vikunja <"+config.MailerFromEmail.GetString()+">").
|
||||
To(args[0]).
|
||||
Subject("Test from Vikunja").
|
||||
Line("This is a test mail!").
|
||||
Line("If you received this, Vikunja is correctly set up to send emails.").
|
||||
Action("Go to your instance", config.ServiceFrontendurl.GetString())
|
||||
|
||||
opts, err := notifications.RenderMail(message)
|
||||
if err != nil {
|
||||
log.Errorf("Error sending test mail: %s", err.Error())
|
||||
return
|
||||
}
|
||||
if err := mail.SendTestMail(opts); err != nil {
|
||||
log.Errorf("Error sending test mail: %s", err.Error())
|
||||
return
|
||||
}
|
||||
log.Info("Testmail successfully sent.")
|
||||
},
|
||||
}
|
|
@ -0,0 +1,372 @@
|
|||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public Licensee as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public Licensee
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/asaskevich/govalidator"
|
||||
|
||||
"code.vikunja.io/api/pkg/db"
|
||||
"code.vikunja.io/api/pkg/initialize"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"github.com/olekukonko/tablewriter"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/term"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
var (
|
||||
userFlagUsername string
|
||||
userFlagEmail string
|
||||
userFlagPassword string
|
||||
userFlagAvatar = "default"
|
||||
userFlagResetPasswordDirectly bool
|
||||
userFlagEnableUser bool
|
||||
userFlagDisableUser bool
|
||||
userFlagDeleteNow bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
// User create flags
|
||||
userCreateCmd.Flags().StringVarP(&userFlagUsername, "username", "u", "", "The username of the new user.")
|
||||
_ = userCreateCmd.MarkFlagRequired("username")
|
||||
userCreateCmd.Flags().StringVarP(&userFlagEmail, "email", "e", "", "The email address of the new user.")
|
||||
_ = userCreateCmd.MarkFlagRequired("email")
|
||||
userCreateCmd.Flags().StringVarP(&userFlagPassword, "password", "p", "", "The password of the new user. You will be asked to enter it if not provided through the flag.")
|
||||
userCreateCmd.Flags().StringVarP(&userFlagAvatar, "avatar-provider", "a", "", "The avatar provider of the new user. Optional.")
|
||||
|
||||
// User update flags
|
||||
userUpdateCmd.Flags().StringVarP(&userFlagUsername, "username", "u", "", "The new username of the user.")
|
||||
userUpdateCmd.Flags().StringVarP(&userFlagEmail, "email", "e", "", "The new email address of the user.")
|
||||
userUpdateCmd.Flags().StringVarP(&userFlagAvatar, "avatar-provider", "a", "", "The new avatar provider of the new user.")
|
||||
|
||||
// Reset PW flags
|
||||
userResetPasswordCmd.Flags().BoolVarP(&userFlagResetPasswordDirectly, "direct", "d", false, "If provided, reset the password directly instead of sending the user a reset mail.")
|
||||
userResetPasswordCmd.Flags().StringVarP(&userFlagPassword, "password", "p", "", "The new password of the user. Only used in combination with --direct. You will be asked to enter it if not provided through the flag.")
|
||||
|
||||
// Change status flags
|
||||
userChangeEnabledCmd.Flags().BoolVarP(&userFlagDisableUser, "disable", "d", false, "Disable the user.")
|
||||
userChangeEnabledCmd.Flags().BoolVarP(&userFlagEnableUser, "enable", "e", false, "Enable the user.")
|
||||
|
||||
// User deletion flags
|
||||
userDeleteCmd.Flags().BoolVarP(&userFlagDeleteNow, "now", "n", false, "If provided, deletes the user immediately instead of sending them an email first.")
|
||||
|
||||
userCmd.AddCommand(userProjectCmd, userCreateCmd, userUpdateCmd, userResetPasswordCmd, userChangeEnabledCmd, userDeleteCmd)
|
||||
rootCmd.AddCommand(userCmd)
|
||||
}
|
||||
|
||||
func getPasswordFromFlagOrInput() (pw string) {
|
||||
pw = userFlagPassword
|
||||
if userFlagPassword == "" {
|
||||
fmt.Print("Enter Password: ")
|
||||
bytePW, err := term.ReadPassword(int(os.Stdin.Fd()))
|
||||
if err != nil {
|
||||
log.Fatalf("Error reading password: %s", err)
|
||||
}
|
||||
fmt.Printf("\nConfirm Password: ")
|
||||
byteConfirmPW, err := term.ReadPassword(int(os.Stdin.Fd()))
|
||||
if err != nil {
|
||||
log.Fatalf("Error reading password: %s", err)
|
||||
}
|
||||
if string(bytePW) != string(byteConfirmPW) {
|
||||
log.Critical("Passwords don't match!")
|
||||
}
|
||||
fmt.Printf("\n")
|
||||
pw = strings.TrimSpace(string(bytePW))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func getUserFromArg(s *xorm.Session, arg string) *user.User {
|
||||
id, err := strconv.ParseInt(arg, 10, 64)
|
||||
if err != nil {
|
||||
log.Fatalf("Invalid user id: %s", err)
|
||||
}
|
||||
|
||||
u, err := user.GetUserWithEmail(s, &user.User{ID: id})
|
||||
if err != nil {
|
||||
log.Fatalf("Could not get user: %s", err)
|
||||
}
|
||||
return u
|
||||
}
|
||||
|
||||
var userCmd = &cobra.Command{
|
||||
Use: "user",
|
||||
Short: "Manage users locally through the cli.",
|
||||
}
|
||||
|
||||
var userProjectCmd = &cobra.Command{
|
||||
Use: "project",
|
||||
Short: "Shows a project of all users.",
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
initialize.FullInit()
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
users, err := user.ProjectAllUsers(s)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
log.Fatalf("Error getting users: %s", err)
|
||||
}
|
||||
|
||||
if err := s.Commit(); err != nil {
|
||||
log.Fatalf("Error getting users: %s", err)
|
||||
}
|
||||
|
||||
table := tablewriter.NewWriter(os.Stdout)
|
||||
table.SetHeader([]string{
|
||||
"ID",
|
||||
"Username",
|
||||
"Email",
|
||||
"Status",
|
||||
"Created",
|
||||
"Updated",
|
||||
})
|
||||
|
||||
for _, u := range users {
|
||||
table.Append([]string{
|
||||
strconv.FormatInt(u.ID, 10),
|
||||
u.Username,
|
||||
u.Email,
|
||||
u.Status.String(),
|
||||
u.Created.Format(time.RFC3339),
|
||||
u.Updated.Format(time.RFC3339),
|
||||
})
|
||||
}
|
||||
|
||||
table.Render()
|
||||
},
|
||||
}
|
||||
|
||||
var userCreateCmd = &cobra.Command{
|
||||
Use: "create",
|
||||
Short: "Create a new user.",
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
initialize.FullInit()
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
u := &user.User{
|
||||
Username: userFlagUsername,
|
||||
Email: userFlagEmail,
|
||||
Password: getPasswordFromFlagOrInput(),
|
||||
}
|
||||
|
||||
if !govalidator.IsEmail(userFlagEmail) {
|
||||
log.Fatalf("Provided email is invalid.")
|
||||
}
|
||||
|
||||
newUser, err := user.CreateUser(s, u)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
log.Fatalf("Error creating new user: %s", err)
|
||||
}
|
||||
|
||||
err = models.CreateNewNamespaceForUser(s, newUser)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
log.Fatalf("Error creating new namespace for user: %s", err)
|
||||
}
|
||||
|
||||
if err := s.Commit(); err != nil {
|
||||
log.Fatalf("Error saving everything: %s", err)
|
||||
}
|
||||
|
||||
fmt.Printf("\nUser was created successfully.\n")
|
||||
},
|
||||
}
|
||||
|
||||
var userUpdateCmd = &cobra.Command{
|
||||
Use: "update [user id]",
|
||||
Short: "Update an existing user.",
|
||||
Args: cobra.ExactArgs(1),
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
initialize.FullInit()
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
u := getUserFromArg(s, args[0])
|
||||
|
||||
if userFlagUsername != "" {
|
||||
u.Username = userFlagUsername
|
||||
}
|
||||
if userFlagEmail != "" {
|
||||
u.Email = userFlagEmail
|
||||
}
|
||||
if userFlagAvatar != "default" {
|
||||
u.AvatarProvider = userFlagAvatar
|
||||
}
|
||||
|
||||
_, err := user.UpdateUser(s, u, false)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
log.Fatalf("Error updating the user: %s", err)
|
||||
}
|
||||
|
||||
if err := s.Commit(); err != nil {
|
||||
log.Fatalf("Error saving everything: %s", err)
|
||||
}
|
||||
|
||||
fmt.Println("User updated successfully.")
|
||||
},
|
||||
}
|
||||
|
||||
var userResetPasswordCmd = &cobra.Command{
|
||||
Use: "reset-password [user id]",
|
||||
Short: "Reset a users password, either through mailing them a reset link or directly.",
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
initialize.FullInit()
|
||||
},
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
u := getUserFromArg(s, args[0])
|
||||
|
||||
// By default we reset as usual, only with specific flag directly.
|
||||
if userFlagResetPasswordDirectly {
|
||||
err := user.UpdateUserPassword(s, u, getPasswordFromFlagOrInput())
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
log.Fatalf("Could not update user password: %s", err)
|
||||
}
|
||||
fmt.Println("Password updated successfully.")
|
||||
} else {
|
||||
err := user.RequestUserPasswordResetToken(s, u)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
log.Fatalf("Could not send password reset email: %s", err)
|
||||
}
|
||||
fmt.Println("Password reset email sent successfully.")
|
||||
}
|
||||
|
||||
if err := s.Commit(); err != nil {
|
||||
log.Fatalf("Could not send password reset email: %s", err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
var userChangeEnabledCmd = &cobra.Command{
|
||||
Use: "change-status [user id]",
|
||||
Short: "Enable or disable a user. Will toggle the current status if no flag (--enable or --disable) is provided.",
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
initialize.FullInit()
|
||||
},
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
u := getUserFromArg(s, args[0])
|
||||
|
||||
if userFlagEnableUser {
|
||||
u.Status = user.StatusActive
|
||||
} else if userFlagDisableUser {
|
||||
u.Status = user.StatusDisabled
|
||||
} else {
|
||||
if u.Status == user.StatusActive {
|
||||
u.Status = user.StatusDisabled
|
||||
} else {
|
||||
u.Status = user.StatusActive
|
||||
}
|
||||
}
|
||||
_, err := user.UpdateUser(s, u, false)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
log.Fatalf("Could not enable the user")
|
||||
}
|
||||
|
||||
if err := s.Commit(); err != nil {
|
||||
log.Fatalf("Error saving everything: %s", err)
|
||||
}
|
||||
|
||||
fmt.Printf("User status successfully changed, status is now \"%s\"\n", u.Status)
|
||||
},
|
||||
}
|
||||
|
||||
var userDeleteCmd = &cobra.Command{
|
||||
Use: "delete [user id]",
|
||||
Short: "Delete an existing user.",
|
||||
Long: "Kick off the user deletion process. If call without the --now flag, this command will only trigger an email to the user in order for them to confirm and start the deletion process. With the flag the user is deleted immediately. USE WITH CAUTION.",
|
||||
Args: cobra.ExactArgs(1),
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
initialize.FullInit()
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if userFlagDeleteNow {
|
||||
fmt.Println("You requested to delete the user immediately. Are you sure?")
|
||||
fmt.Println(`To confirm, please type "yes, I confirm" in all uppercase:`)
|
||||
|
||||
cr := bufio.NewReader(os.Stdin)
|
||||
text, err := cr.ReadString('\n')
|
||||
if err != nil {
|
||||
log.Fatalf("could not read confirmation message: %s", err)
|
||||
}
|
||||
if text != "YES, I CONFIRM\n" {
|
||||
log.Fatalf("invalid confirmation message")
|
||||
}
|
||||
}
|
||||
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
if err := s.Begin(); err != nil {
|
||||
log.Fatalf("Count not start transaction: %s", err)
|
||||
}
|
||||
|
||||
u := getUserFromArg(s, args[0])
|
||||
|
||||
if userFlagDeleteNow {
|
||||
err := models.DeleteUser(s, u)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
log.Fatalf("Error removing the user: %s", err)
|
||||
}
|
||||
} else {
|
||||
err := user.RequestDeletion(s, u)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
log.Fatalf("Could not request user deletion: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := s.Commit(); err != nil {
|
||||
log.Fatalf("Error saving everything: %s", err)
|
||||
}
|
||||
|
||||
if userFlagDeleteNow {
|
||||
fmt.Println("User deleted successfully.")
|
||||
} else {
|
||||
fmt.Println("User scheduled for deletion successfully.")
|
||||
}
|
||||
},
|
||||
}
|
|
@ -1,24 +1,26 @@
|
|||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
|
||||
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// it under the terms of the GNU Affero General Public Licensee as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
// GNU Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// You should have received a copy of the GNU Affero General Public Licensee
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/version"
|
||||
"fmt"
|
||||
"runtime"
|
||||
|
||||
"code.vikunja.io/api/pkg/version"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
|
@ -31,6 +33,6 @@ var versionCmd = &cobra.Command{
|
|||
Short: "Print the version number of Vikunja",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Printf("Vikunja api version %s\n", version.Version)
|
||||
fmt.Printf("Built at %s\n", version.BuildTime)
|
||||
fmt.Printf("Built with %s\n", runtime.Version())
|
||||
},
|
||||
}
|
||||
|
|
|
@ -1,54 +1,91 @@
|
|||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
|
||||
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// it under the terms of the GNU Affero General Public Licensee as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
// GNU Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// You should have received a copy of the GNU Affero General Public Licensee
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/routes"
|
||||
"code.vikunja.io/api/pkg/swagger"
|
||||
"code.vikunja.io/api/pkg/version"
|
||||
"context"
|
||||
"github.com/spf13/cobra"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"time"
|
||||
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/cron"
|
||||
"code.vikunja.io/api/pkg/initialize"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/routes"
|
||||
"code.vikunja.io/api/pkg/utils"
|
||||
"code.vikunja.io/api/pkg/version"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(webCmd)
|
||||
}
|
||||
|
||||
func setupUnixSocket(e *echo.Echo) error {
|
||||
path := config.ServiceUnixSocket.GetString()
|
||||
|
||||
// Remove old unix socket that may have remained after a crash
|
||||
if err := os.Remove(path); err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
if config.ServiceUnixSocketMode.Get() != nil {
|
||||
// Use Umask instead of Chmod to prevent insecure race condition
|
||||
// (no-op on Windows)
|
||||
mode := config.ServiceUnixSocketMode.GetInt()
|
||||
oldmask := utils.Umask(0o777 &^ mode)
|
||||
defer utils.Umask(oldmask)
|
||||
}
|
||||
|
||||
l, err := net.Listen("unix", path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
e.Listener = l
|
||||
return nil
|
||||
}
|
||||
|
||||
var webCmd = &cobra.Command{
|
||||
Use: "web",
|
||||
Short: "Starts the rest api web server",
|
||||
PreRun: func(cmd *cobra.Command, args []string) {
|
||||
initialize.FullInit()
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
|
||||
// Version notification
|
||||
log.Infof("Vikunja version %s, built at %s", version.Version, version.BuildTime)
|
||||
|
||||
// Additional swagger information
|
||||
swagger.SwaggerInfo.Version = version.Version
|
||||
log.Infof("Vikunja version %s", version.Version)
|
||||
|
||||
// Start the webserver
|
||||
e := routes.NewEcho()
|
||||
routes.RegisterRoutes(e)
|
||||
// Start server
|
||||
go func() {
|
||||
// Listen unix socket if needed (ServiceInterface will be ignored)
|
||||
if config.ServiceUnixSocket.GetString() != "" {
|
||||
if err := setupUnixSocket(e); err != nil {
|
||||
e.Logger.Fatal(err)
|
||||
}
|
||||
}
|
||||
if err := e.Start(config.ServiceInterface.GetString()); err != nil {
|
||||
e.Logger.Info("shutting down...")
|
||||
}
|
||||
|
@ -65,5 +102,6 @@ var webCmd = &cobra.Command{
|
|||
if err := e.Shutdown(ctx); err != nil {
|
||||
e.Logger.Fatal(err)
|
||||
}
|
||||
cron.Stop()
|
||||
},
|
||||
}
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
|
||||
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// it under the terms of the GNU Affero General Public Licensee as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
// GNU Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// You should have received a copy of the GNU Affero General Public Licensee
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package config
|
||||
|
@ -21,10 +21,13 @@ import (
|
|||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
_ "time/tzdata" // Imports time zone data instead of relying on the os
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
@ -34,12 +37,19 @@ type Key string
|
|||
|
||||
// These constants hold all config value keys
|
||||
const (
|
||||
ServiceJWTSecret Key = `service.JWTSecret`
|
||||
ServiceInterface Key = `service.interface`
|
||||
ServiceFrontendurl Key = `service.frontendurl`
|
||||
ServiceEnableCaldav Key = `service.enablecaldav`
|
||||
ServiceRootpath Key = `service.rootpath`
|
||||
ServiceMaxItemsPerPage Key = `service.maxitemsperpage`
|
||||
// #nosec
|
||||
ServiceJWTSecret Key = `service.JWTSecret`
|
||||
ServiceJWTTTL Key = `service.jwtttl`
|
||||
ServiceJWTTTLLong Key = `service.jwtttllong`
|
||||
ServiceInterface Key = `service.interface`
|
||||
ServiceUnixSocket Key = `service.unixsocket`
|
||||
ServiceUnixSocketMode Key = `service.unixsocketmode`
|
||||
ServiceFrontendurl Key = `service.frontendurl`
|
||||
ServiceEnableCaldav Key = `service.enablecaldav`
|
||||
ServiceRootpath Key = `service.rootpath`
|
||||
ServiceStaticpath Key = `service.staticpath`
|
||||
ServiceMaxItemsPerPage Key = `service.maxitemsperpage`
|
||||
// Deprecated: Use metrics.enabled
|
||||
ServiceEnableMetrics Key = `service.enablemetrics`
|
||||
ServiceMotd Key = `service.motd`
|
||||
ServiceEnableLinkSharing Key = `service.enablelinksharing`
|
||||
|
@ -47,6 +57,20 @@ const (
|
|||
ServiceEnableTaskAttachments Key = `service.enabletaskattachments`
|
||||
ServiceTimeZone Key = `service.timezone`
|
||||
ServiceEnableTaskComments Key = `service.enabletaskcomments`
|
||||
ServiceEnableTotp Key = `service.enabletotp`
|
||||
ServiceSentryDsn Key = `service.sentrydsn`
|
||||
ServiceTestingtoken Key = `service.testingtoken`
|
||||
ServiceEnableEmailReminders Key = `service.enableemailreminders`
|
||||
ServiceEnableUserDeletion Key = `service.enableuserdeletion`
|
||||
ServiceMaxAvatarSize Key = `service.maxavatarsize`
|
||||
|
||||
AuthLocalEnabled Key = `auth.local.enabled`
|
||||
AuthOpenIDEnabled Key = `auth.openid.enabled`
|
||||
AuthOpenIDRedirectURL Key = `auth.openid.redirecturl`
|
||||
AuthOpenIDProviders Key = `auth.openid.providers`
|
||||
|
||||
LegalImprintURL Key = `legal.imprinturl`
|
||||
LegalPrivacyURL Key = `legal.privacyurl`
|
||||
|
||||
DatabaseType Key = `database.type`
|
||||
DatabaseHost Key = `database.host`
|
||||
|
@ -58,6 +82,10 @@ const (
|
|||
DatabaseMaxIdleConnections Key = `database.maxidleconnections`
|
||||
DatabaseMaxConnectionLifetime Key = `database.maxconnectionlifetime`
|
||||
DatabaseSslMode Key = `database.sslmode`
|
||||
DatabaseSslCert Key = `database.sslcert`
|
||||
DatabaseSslKey Key = `database.sslkey`
|
||||
DatabaseSslRootCert Key = `database.sslrootcert`
|
||||
DatabaseTLS Key = `database.tls`
|
||||
|
||||
CacheEnabled Key = `cache.enabled`
|
||||
CacheType Key = `cache.type`
|
||||
|
@ -68,10 +96,12 @@ const (
|
|||
MailerPort Key = `mailer.port`
|
||||
MailerUsername Key = `mailer.username`
|
||||
MailerPassword Key = `mailer.password`
|
||||
MailerAuthType Key = `mailer.authtype`
|
||||
MailerSkipTLSVerify Key = `mailer.skiptlsverify`
|
||||
MailerFromEmail Key = `mailer.fromemail`
|
||||
MailerQueuelength Key = `mailer.queuelength`
|
||||
MailerQueueTimeout Key = `mailer.queuetimeout`
|
||||
MailerForceSSL Key = `mailer.forcessl`
|
||||
|
||||
RedisEnabled Key = `redis.enabled`
|
||||
RedisHost Key = `redis.host`
|
||||
|
@ -79,13 +109,17 @@ const (
|
|||
RedisDB Key = `redis.db`
|
||||
|
||||
LogEnabled Key = `log.enabled`
|
||||
LogErrors Key = `log.errors`
|
||||
LogStandard Key = `log.standard`
|
||||
LogLevel Key = `log.level`
|
||||
LogDatabase Key = `log.database`
|
||||
LogDatabaseLevel Key = `log.databaselevel`
|
||||
LogHTTP Key = `log.http`
|
||||
LogEcho Key = `log.echo`
|
||||
LogPath Key = `log.path`
|
||||
LogEvents Key = `log.events`
|
||||
LogEventsLevel Key = `log.eventslevel`
|
||||
LogMail Key = `log.mail`
|
||||
LogMailLevel Key = `log.maillevel`
|
||||
|
||||
RateLimitEnabled Key = `ratelimit.enabled`
|
||||
RateLimitKind Key = `ratelimit.kind`
|
||||
|
@ -96,17 +130,47 @@ const (
|
|||
FilesBasePath Key = `files.basepath`
|
||||
FilesMaxSize Key = `files.maxsize`
|
||||
|
||||
MigrationWunderlistEnable Key = `migration.wunderlist.enable`
|
||||
MigrationWunderlistClientID Key = `migration.wunderlist.clientid`
|
||||
MigrationWunderlistClientSecret Key = `migration.wunderlist.clientsecret`
|
||||
MigrationWunderlistRedirectURL Key = `migration.wunderlist.redirecturl`
|
||||
MigrationTodoistEnable Key = `migration.todoist.enable`
|
||||
MigrationTodoistClientID Key = `migration.todoist.clientid`
|
||||
MigrationTodoistClientSecret Key = `migration.todoist.clientsecret`
|
||||
MigrationTodoistRedirectURL Key = `migration.todoist.redirecturl`
|
||||
MigrationTrelloEnable Key = `migration.trello.enable`
|
||||
MigrationTrelloKey Key = `migration.trello.key`
|
||||
MigrationTrelloRedirectURL Key = `migration.trello.redirecturl`
|
||||
MigrationMicrosoftTodoEnable Key = `migration.microsofttodo.enable`
|
||||
MigrationMicrosoftTodoClientID Key = `migration.microsofttodo.clientid`
|
||||
MigrationMicrosoftTodoClientSecret Key = `migration.microsofttodo.clientsecret`
|
||||
MigrationMicrosoftTodoRedirectURL Key = `migration.microsofttodo.redirecturl`
|
||||
|
||||
CorsEnable Key = `cors.enable`
|
||||
CorsOrigins Key = `cors.origins`
|
||||
CorsMaxAge Key = `cors.maxage`
|
||||
|
||||
AvatarProvider Key = `avatar.provider`
|
||||
AvatarGravaterExpiration Key = `avatar.gravatarexpiration`
|
||||
|
||||
BackgroundsEnabled Key = `backgrounds.enabled`
|
||||
BackgroundsUploadEnabled Key = `backgrounds.providers.upload.enabled`
|
||||
BackgroundsUnsplashEnabled Key = `backgrounds.providers.unsplash.enabled`
|
||||
BackgroundsUnsplashAccessToken Key = `backgrounds.providers.unsplash.accesstoken`
|
||||
BackgroundsUnsplashApplicationID Key = `backgrounds.providers.unsplash.applicationid`
|
||||
|
||||
KeyvalueType Key = `keyvalue.type`
|
||||
|
||||
MetricsEnabled Key = `metrics.enabled`
|
||||
MetricsUsername Key = `metrics.username`
|
||||
MetricsPassword Key = `metrics.password`
|
||||
|
||||
DefaultSettingsAvatarProvider Key = `defaultsettings.avatar_provider`
|
||||
DefaultSettingsAvatarFileID Key = `defaultsettings.avatar_file_id`
|
||||
DefaultSettingsEmailRemindersEnabled Key = `defaultsettings.email_reminders_enabled`
|
||||
DefaultSettingsDiscoverableByName Key = `defaultsettings.discoverable_by_name`
|
||||
DefaultSettingsDiscoverableByEmail Key = `defaultsettings.discoverable_by_email`
|
||||
DefaultSettingsOverdueTaskRemindersEnabled Key = `defaultsettings.overdue_tasks_reminders_enabled`
|
||||
DefaultSettingsDefaultProjectID Key = `defaultsettings.default_project_id`
|
||||
DefaultSettingsWeekStart Key = `defaultsettings.week_start`
|
||||
DefaultSettingsLanguage Key = `defaultsettings.language`
|
||||
DefaultSettingsTimezone Key = `defaultsettings.timezone`
|
||||
DefaultSettingsOverdueTaskRemindersTime Key = `defaultsettings.overdue_tasks_reminders_time`
|
||||
)
|
||||
|
||||
// GetString returns a string config value
|
||||
|
@ -139,6 +203,28 @@ func (k Key) GetStringSlice() []string {
|
|||
return viper.GetStringSlice(string(k))
|
||||
}
|
||||
|
||||
// Get returns the raw value from a config option
|
||||
func (k Key) Get() interface{} {
|
||||
return viper.Get(string(k))
|
||||
}
|
||||
|
||||
var timezone *time.Location
|
||||
|
||||
// GetTimeZone returns the time zone configured for vikunja
|
||||
// It is a separate function and not done through viper because that makes handling
|
||||
// it way easier, especially when testing.
|
||||
func GetTimeZone() *time.Location {
|
||||
if timezone == nil {
|
||||
loc, err := time.LoadLocation(ServiceTimeZone.GetString())
|
||||
if err != nil {
|
||||
fmt.Printf("Error parsing time zone: %s", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
timezone = loc
|
||||
}
|
||||
return timezone
|
||||
}
|
||||
|
||||
// Set sets a value
|
||||
func (k Key) Set(i interface{}) {
|
||||
viper.Set(string(k), i)
|
||||
|
@ -149,6 +235,39 @@ func (k Key) setDefault(i interface{}) {
|
|||
viper.SetDefault(string(k), i)
|
||||
}
|
||||
|
||||
// Tries different methods to figure out the binary folder.
|
||||
// Copied and adopted from https://github.com/speedata/publisher/commit/3b668668d57edef04ea854d5bbd58f83eb1b799f
|
||||
func getBinaryDirLocation() string {
|
||||
// First, check if the standard library gives us the path. This will work 99% of the time.
|
||||
ex, err := os.Executable()
|
||||
if err == nil {
|
||||
return filepath.Dir(ex)
|
||||
}
|
||||
|
||||
// Then check if the binary was run with a full path and use that if that's the case.
|
||||
if strings.Contains(os.Args[0], "/") {
|
||||
binDir, err := filepath.Abs(filepath.Dir(os.Args[0]))
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return binDir
|
||||
}
|
||||
|
||||
exeSuffix := ""
|
||||
if runtime.GOOS == "windows" {
|
||||
exeSuffix = ".exe"
|
||||
}
|
||||
|
||||
// All else failing, search for a vikunja binary in the current $PATH.
|
||||
// This can give wrong results.
|
||||
exeLocation, err := exec.LookPath("vikunja" + exeSuffix)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return filepath.Dir(exeLocation)
|
||||
}
|
||||
|
||||
// InitDefaultConfig sets default config values
|
||||
// This is an extra function so we can call it when initializing tests without initializing the full config
|
||||
func InitDefaultConfig() {
|
||||
|
@ -160,16 +279,15 @@ func InitDefaultConfig() {
|
|||
|
||||
// Service
|
||||
ServiceJWTSecret.setDefault(random)
|
||||
ServiceJWTTTL.setDefault(259200) // 72 hours
|
||||
ServiceJWTTTLLong.setDefault(2592000) // 30 days
|
||||
ServiceInterface.setDefault(":3456")
|
||||
ServiceUnixSocket.setDefault("")
|
||||
ServiceFrontendurl.setDefault("")
|
||||
ServiceEnableCaldav.setDefault(true)
|
||||
|
||||
ex, err := os.Executable()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
exPath := filepath.Dir(ex)
|
||||
ServiceRootpath.setDefault(exPath)
|
||||
ServiceRootpath.setDefault(getBinaryDirLocation())
|
||||
ServiceStaticpath.setDefault("")
|
||||
ServiceMaxItemsPerPage.setDefault(50)
|
||||
ServiceEnableMetrics.setDefault(false)
|
||||
ServiceMotd.setDefault("")
|
||||
|
@ -178,6 +296,14 @@ func InitDefaultConfig() {
|
|||
ServiceEnableTaskAttachments.setDefault(true)
|
||||
ServiceTimeZone.setDefault("GMT")
|
||||
ServiceEnableTaskComments.setDefault(true)
|
||||
ServiceEnableTotp.setDefault(true)
|
||||
ServiceEnableEmailReminders.setDefault(true)
|
||||
ServiceEnableUserDeletion.setDefault(true)
|
||||
ServiceMaxAvatarSize.setDefault(1024)
|
||||
|
||||
// Auth
|
||||
AuthLocalEnabled.setDefault(true)
|
||||
AuthOpenIDEnabled.setDefault(false)
|
||||
|
||||
// Database
|
||||
DatabaseType.setDefault("sqlite")
|
||||
|
@ -190,6 +316,10 @@ func InitDefaultConfig() {
|
|||
DatabaseMaxIdleConnections.setDefault(50)
|
||||
DatabaseMaxConnectionLifetime.setDefault(10000)
|
||||
DatabaseSslMode.setDefault("disable")
|
||||
DatabaseSslCert.setDefault("")
|
||||
DatabaseSslKey.setDefault("")
|
||||
DatabaseSslRootCert.setDefault("")
|
||||
DatabaseTLS.setDefault("false")
|
||||
|
||||
// Cacher
|
||||
CacheEnabled.setDefault(false)
|
||||
|
@ -199,12 +329,14 @@ func InitDefaultConfig() {
|
|||
MailerEnabled.setDefault(false)
|
||||
MailerHost.setDefault("")
|
||||
MailerPort.setDefault("587")
|
||||
MailerUsername.setDefault("user")
|
||||
MailerUsername.setDefault("")
|
||||
MailerPassword.setDefault("")
|
||||
MailerSkipTLSVerify.setDefault(false)
|
||||
MailerFromEmail.setDefault("mail@vikunja")
|
||||
MailerQueuelength.setDefault(100)
|
||||
MailerQueueTimeout.setDefault(30)
|
||||
MailerForceSSL.setDefault(false)
|
||||
MailerAuthType.setDefault("plain")
|
||||
// Redis
|
||||
RedisEnabled.setDefault(false)
|
||||
RedisHost.setDefault("localhost:6379")
|
||||
|
@ -212,13 +344,17 @@ func InitDefaultConfig() {
|
|||
RedisDB.setDefault(0)
|
||||
// Logger
|
||||
LogEnabled.setDefault(true)
|
||||
LogErrors.setDefault("stdout")
|
||||
LogStandard.setDefault("stdout")
|
||||
LogLevel.setDefault("INFO")
|
||||
LogDatabase.setDefault("off")
|
||||
LogDatabaseLevel.setDefault("DEBUG")
|
||||
LogDatabaseLevel.setDefault("WARNING")
|
||||
LogHTTP.setDefault("stdout")
|
||||
LogEcho.setDefault("off")
|
||||
LogPath.setDefault(ServiceRootpath.GetString() + "/logs")
|
||||
LogEvents.setDefault("off")
|
||||
LogEventsLevel.setDefault("INFO")
|
||||
LogMail.setDefault("off")
|
||||
LogMailLevel.setDefault("INFO")
|
||||
// Rate Limit
|
||||
RateLimitEnabled.setDefault(false)
|
||||
RateLimitKind.setDefault("user")
|
||||
|
@ -233,10 +369,23 @@ func InitDefaultConfig() {
|
|||
CorsOrigins.setDefault([]string{"*"})
|
||||
CorsMaxAge.setDefault(0)
|
||||
// Migration
|
||||
MigrationWunderlistEnable.setDefault(false)
|
||||
MigrationTodoistEnable.setDefault(false)
|
||||
MigrationTrelloEnable.setDefault(false)
|
||||
MigrationMicrosoftTodoEnable.setDefault(false)
|
||||
// Avatar
|
||||
AvatarProvider.setDefault("gravatar")
|
||||
AvatarGravaterExpiration.setDefault(3600)
|
||||
// Project Backgrounds
|
||||
BackgroundsEnabled.setDefault(true)
|
||||
BackgroundsUploadEnabled.setDefault(true)
|
||||
BackgroundsUnsplashEnabled.setDefault(false)
|
||||
// Key Value
|
||||
KeyvalueType.setDefault("memory")
|
||||
// Metrics
|
||||
MetricsEnabled.setDefault(false)
|
||||
// Settings
|
||||
DefaultSettingsAvatarProvider.setDefault("initials")
|
||||
DefaultSettingsOverdueTaskRemindersEnabled.setDefault(true)
|
||||
DefaultSettingsOverdueTaskRemindersTime.setDefault("9:00")
|
||||
}
|
||||
|
||||
// InitConfig initializes the config, sets defaults etc.
|
||||
|
@ -263,14 +412,55 @@ func InitConfig() {
|
|||
|
||||
viper.AddConfigPath(".")
|
||||
viper.SetConfigName("config")
|
||||
|
||||
err = viper.ReadInConfig()
|
||||
if err != nil {
|
||||
log.Println(err.Error())
|
||||
log.Println("Using default config.")
|
||||
return
|
||||
if viper.ConfigFileUsed() != "" {
|
||||
log.Printf("Using config file: %s", viper.ConfigFileUsed())
|
||||
|
||||
if err != nil {
|
||||
log.Println(err.Error())
|
||||
log.Println("Using default config.")
|
||||
}
|
||||
} else {
|
||||
log.Println("No config file found, using default or config from environment variables.")
|
||||
}
|
||||
|
||||
log.Printf("Using config file: %s", viper.ConfigFileUsed())
|
||||
if CacheType.GetString() == "keyvalue" {
|
||||
CacheType.Set(KeyvalueType.GetString())
|
||||
}
|
||||
|
||||
if RateLimitStore.GetString() == "keyvalue" {
|
||||
RateLimitStore.Set(KeyvalueType.GetString())
|
||||
}
|
||||
|
||||
if ServiceFrontendurl.GetString() != "" && !strings.HasSuffix(ServiceFrontendurl.GetString(), "/") {
|
||||
ServiceFrontendurl.Set(ServiceFrontendurl.GetString() + "/")
|
||||
}
|
||||
|
||||
if AuthOpenIDRedirectURL.GetString() == "" {
|
||||
AuthOpenIDRedirectURL.Set(ServiceFrontendurl.GetString() + "auth/openid/")
|
||||
}
|
||||
|
||||
if MigrationTodoistRedirectURL.GetString() == "" {
|
||||
MigrationTodoistRedirectURL.Set(ServiceFrontendurl.GetString() + "migrate/todoist")
|
||||
}
|
||||
|
||||
if MigrationTrelloRedirectURL.GetString() == "" {
|
||||
MigrationTrelloRedirectURL.Set(ServiceFrontendurl.GetString() + "migrate/trello")
|
||||
}
|
||||
|
||||
if MigrationMicrosoftTodoRedirectURL.GetString() == "" {
|
||||
MigrationMicrosoftTodoRedirectURL.Set(ServiceFrontendurl.GetString() + "migrate/microsoft-todo")
|
||||
}
|
||||
|
||||
if DefaultSettingsTimezone.GetString() == "" {
|
||||
DefaultSettingsTimezone.Set(ServiceTimeZone.GetString())
|
||||
}
|
||||
|
||||
if ServiceEnableMetrics.GetBool() {
|
||||
log.Println("WARNING: service.enablemetrics is deprecated and will be removed in a future release. Please use metrics.enable.")
|
||||
MetricsEnabled.Set(true)
|
||||
}
|
||||
}
|
||||
|
||||
func random(length int) (string, error) {
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public Licensee as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public Licensee
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package cron
|
||||
|
||||
import (
|
||||
"github.com/robfig/cron/v3"
|
||||
)
|
||||
|
||||
var c *cron.Cron
|
||||
|
||||
// Init starts the cron
|
||||
func Init() {
|
||||
c = cron.New()
|
||||
c.Start()
|
||||
}
|
||||
|
||||
// Schedule schedules a job as a cron job
|
||||
func Schedule(schedule string, f func()) (err error) {
|
||||
_, err = c.AddFunc(schedule, f)
|
||||
return
|
||||
}
|
||||
|
||||
// Stop stops the cron scheduler
|
||||
func Stop() {
|
||||
c.Stop()
|
||||
}
|
124
pkg/db/db.go
124
pkg/db/db.go
|
@ -1,34 +1,37 @@
|
|||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2020 Vikunja and contributors. All rights reserved.
|
||||
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// it under the terms of the GNU Affero General Public Licensee as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
// GNU Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// You should have received a copy of the GNU Affero General Public Licensee
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
xrc "gitea.com/xorm/xorm-redis-cache"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"xorm.io/core"
|
||||
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
xrc "gitea.com/xorm/xorm-redis-cache"
|
||||
"xorm.io/xorm"
|
||||
"xorm.io/xorm/caches"
|
||||
"xorm.io/xorm/names"
|
||||
"xorm.io/xorm/schemas"
|
||||
|
||||
_ "github.com/go-sql-driver/mysql" // Because.
|
||||
_ "github.com/lib/pq" // Because.
|
||||
|
@ -51,26 +54,35 @@ func CreateDBEngine() (engine *xorm.Engine, err error) {
|
|||
}
|
||||
|
||||
// Use Mysql if set
|
||||
if config.DatabaseType.GetString() == "mysql" {
|
||||
switch config.DatabaseType.GetString() {
|
||||
case "mysql":
|
||||
engine, err = initMysqlEngine()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else if config.DatabaseType.GetString() == "postgres" {
|
||||
case "postgres":
|
||||
engine, err = initPostgresEngine()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
case "sqlite":
|
||||
// Otherwise use sqlite
|
||||
engine, err = initSqliteEngine()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
default:
|
||||
log.Fatalf("Unknown database type %s", config.DatabaseType.GetString())
|
||||
}
|
||||
|
||||
engine.SetMapper(core.GonicMapper{})
|
||||
logger := log.NewXormLogger()
|
||||
engine.SetTZLocation(config.GetTimeZone()) // Vikunja's timezone
|
||||
loc, err := time.LoadLocation("GMT") // The db data timezone
|
||||
if err != nil {
|
||||
log.Fatalf("Error parsing time zone: %s", err)
|
||||
}
|
||||
engine.SetTZDatabase(loc)
|
||||
engine.SetMapper(names.GonicMapper{})
|
||||
logger := log.NewXormLogger("")
|
||||
engine.SetLogger(logger)
|
||||
|
||||
// Cache
|
||||
|
@ -81,7 +93,7 @@ func CreateDBEngine() (engine *xorm.Engine, err error) {
|
|||
cacher := caches.NewLRUCacher(caches.NewMemoryStore(), config.CacheMaxElementSize.GetInt())
|
||||
engine.SetDefaultCacher(cacher)
|
||||
case "redis":
|
||||
cacher := xrc.NewRedisCacher(config.RedisEnabled.GetString(), config.RedisPassword.GetString(), xrc.DEFAULT_EXPIRATION, log.GetXormLoggerForRedis(logger))
|
||||
cacher := xrc.NewRedisCacher(config.RedisHost.GetString(), config.RedisPassword.GetString(), xrc.DEFAULT_EXPIRATION, engine.Logger())
|
||||
engine.SetDefaultCacher(cacher)
|
||||
default:
|
||||
log.Info("Did not find a valid cache type. Caching disabled. Please refer to the docs for poosible cache types.")
|
||||
|
@ -100,12 +112,18 @@ func RegisterTableStructsForCache(val interface{}) {
|
|||
func initMysqlEngine() (engine *xorm.Engine, err error) {
|
||||
// We're using utf8mb here instead of just utf8 because we want to use non-BMP characters.
|
||||
// See https://stackoverflow.com/a/30074553/10924593 for more info.
|
||||
host := fmt.Sprintf("tcp(%s)", config.DatabaseHost.GetString())
|
||||
if config.DatabaseHost.GetString()[0] == '/' { // looks like a unix socket
|
||||
host = fmt.Sprintf("unix(%s)", config.DatabaseHost.GetString())
|
||||
}
|
||||
|
||||
connStr := fmt.Sprintf(
|
||||
"%s:%s@tcp(%s)/%s?charset=utf8mb4&parseTime=true",
|
||||
"%s:%s@%s/%s?charset=utf8mb4&parseTime=true&tls=%s",
|
||||
config.DatabaseUser.GetString(),
|
||||
config.DatabasePassword.GetString(),
|
||||
config.DatabaseHost.GetString(),
|
||||
config.DatabaseDatabase.GetString())
|
||||
host,
|
||||
config.DatabaseDatabase.GetString(),
|
||||
config.DatabaseTLS.GetString())
|
||||
engine, err = xorm.NewEngine("mysql", connStr)
|
||||
if err != nil {
|
||||
return
|
||||
|
@ -135,15 +153,33 @@ func parsePostgreSQLHostPort(info string) (string, string) {
|
|||
return host, port
|
||||
}
|
||||
|
||||
// Copied and adopted from https://github.com/go-gitea/gitea/blob/f337c32e868381c6d2d948221aca0c59f8420c13/modules/setting/database.go#L176-L186
|
||||
func getPostgreSQLConnectionString(dbHost, dbUser, dbPasswd, dbName, dbSslMode, dbSslCert, dbSslKey, dbSslRootCert string) (connStr string) {
|
||||
dbParam := "?"
|
||||
if strings.Contains(dbName, dbParam) {
|
||||
dbParam = "&"
|
||||
}
|
||||
host, port := parsePostgreSQLHostPort(dbHost)
|
||||
if host[0] == '/' { // looks like a unix socket
|
||||
connStr = fmt.Sprintf("postgres://%s:%s@:%s/%s%ssslmode=%s&sslcert=%s&sslkey=%s&sslrootcert=%s&host=%s",
|
||||
url.PathEscape(dbUser), url.PathEscape(dbPasswd), port, dbName, dbParam, dbSslMode, dbSslCert, dbSslKey, dbSslRootCert, host)
|
||||
} else {
|
||||
connStr = fmt.Sprintf("postgres://%s:%s@%s:%s/%s%ssslmode=%s&sslcert=%s&sslkey=%s&sslrootcert=%s",
|
||||
url.PathEscape(dbUser), url.PathEscape(dbPasswd), host, port, dbName, dbParam, dbSslMode, dbSslCert, dbSslKey, dbSslRootCert)
|
||||
}
|
||||
return connStr
|
||||
}
|
||||
|
||||
func initPostgresEngine() (engine *xorm.Engine, err error) {
|
||||
host, port := parsePostgreSQLHostPort(config.DatabaseHost.GetString())
|
||||
connStr := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=%s",
|
||||
host,
|
||||
port,
|
||||
url.PathEscape(config.DatabaseUser.GetString()),
|
||||
url.PathEscape(config.DatabasePassword.GetString()),
|
||||
connStr := getPostgreSQLConnectionString(
|
||||
config.DatabaseHost.GetString(),
|
||||
config.DatabaseUser.GetString(),
|
||||
config.DatabasePassword.GetString(),
|
||||
config.DatabaseDatabase.GetString(),
|
||||
config.DatabaseSslMode.GetString(),
|
||||
config.DatabaseSslCert.GetString(),
|
||||
config.DatabaseSslKey.GetString(),
|
||||
config.DatabaseSslRootCert.GetString(),
|
||||
)
|
||||
|
||||
engine, err = xorm.NewEngine("postgres", connStr)
|
||||
|
@ -166,5 +202,47 @@ func initSqliteEngine() (engine *xorm.Engine, err error) {
|
|||
path = "./db.db"
|
||||
}
|
||||
|
||||
// Try opening the db file to return a better error message if that does not work
|
||||
var exists = true
|
||||
if _, err := os.Stat(path); err != nil {
|
||||
exists = !os.IsNotExist(err)
|
||||
}
|
||||
file, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not open database file [uid=%d, gid=%d]: %w", os.Getuid(), os.Getgid(), err)
|
||||
}
|
||||
_ = file.Close() // We directly close the file because we only want to check if it is writable. It will be reopened lazily later by xorm.
|
||||
|
||||
if !exists {
|
||||
_ = os.Remove(path) // Remove the file to not prevent the db from creating another one
|
||||
}
|
||||
|
||||
return xorm.NewEngine("sqlite3", path)
|
||||
}
|
||||
|
||||
// WipeEverything wipes all tables and their data. Use with caution...
|
||||
func WipeEverything() error {
|
||||
|
||||
tables, err := x.DBMetas()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, t := range tables {
|
||||
if err := x.DropTables(t.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewSession creates a new xorm session
|
||||
func NewSession() *xorm.Session {
|
||||
return x.NewSession()
|
||||
}
|
||||
|
||||
// Type returns the db type of the currently configured db
|
||||
func Type() schemas.DBType {
|
||||
return x.Dialect().URI().DBType
|
||||
}
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public Licensee as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public Licensee
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
|
||||
"xorm.io/xorm/schemas"
|
||||
)
|
||||
|
||||
// Dump dumps all database tables
|
||||
func Dump() (data map[string][]byte, err error) {
|
||||
tables, err := x.DBMetas()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
data = make(map[string][]byte, len(tables))
|
||||
for _, table := range tables {
|
||||
entries := []map[string]interface{}{}
|
||||
err := x.Table(table.Name).Find(&entries)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data[table.Name], err = json.Marshal(entries)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Restore restores a table with all its entries
|
||||
func Restore(table string, contents []map[string]interface{}) (err error) {
|
||||
if _, err := x.IsTableExist(table); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
meta, err := x.DBMetas()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var metaForCurrentTable *schemas.Table
|
||||
for _, m := range meta {
|
||||
if m.Name == table {
|
||||
metaForCurrentTable = m
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if metaForCurrentTable == nil {
|
||||
log.Fatalf("Could not find table definition for table %s", table)
|
||||
}
|
||||
|
||||
for _, content := range contents {
|
||||
for colName, value := range content {
|
||||
// Date fields might get restored as 0001-01-01 from null dates. This can have unintended side-effects like
|
||||
// users being scheduled for deletion after a restore.
|
||||
// To avoid this, we set these dates to nil so that they'll end up as null in the db.
|
||||
col := metaForCurrentTable.GetColumn(colName)
|
||||
strVal, is := value.(string)
|
||||
if is && col.SQLType.IsTime() && (strVal == "" || strings.HasPrefix(strVal, "0001-")) {
|
||||
content[colName] = nil
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := x.Table(table).Insert(content); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if Type() == schemas.POSTGRES {
|
||||
idSequence := table + "_id_seq"
|
||||
_, err = x.Query("SELECT setval('" + idSequence + "', COALESCE(MAX(id), 1) )")
|
||||
if err != nil {
|
||||
log.Warningf("Could not reset id sequence for %s: %s", idSequence, err)
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// RestoreAndTruncate removes all content from the table before restoring it from the contents map
|
||||
func RestoreAndTruncate(table string, contents []map[string]interface{}) (err error) {
|
||||
if _, err := x.IsTableExist(table); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if x.Dialect().URI().DBType == schemas.SQLITE {
|
||||
if _, err := x.Query("DELETE FROM " + table); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if _, err := x.Query("TRUNCATE TABLE ?", table); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return Restore(table, contents)
|
||||
}
|
|
@ -0,0 +1,226 @@
|
|||
- id: 1
|
||||
title: testbucket1
|
||||
project_id: 1
|
||||
created_by_id: 1
|
||||
limit: 9999999 # This bucket has a limit we will never exceed in the tests to make sure the logic allows for buckets with limits
|
||||
position: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 2
|
||||
title: testbucket2
|
||||
project_id: 1
|
||||
created_by_id: 1
|
||||
limit: 3
|
||||
position: 2
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 3
|
||||
title: testbucket3
|
||||
project_id: 1
|
||||
created_by_id: 1
|
||||
is_done_bucket: 1
|
||||
position: 3
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 4
|
||||
title: testbucket4 - other project
|
||||
project_id: 2
|
||||
created_by_id: 1
|
||||
is_done_bucket: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
# The following are not or only partly owned by user 1
|
||||
- id: 5
|
||||
title: testbucket5
|
||||
project_id: 20
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 6
|
||||
title: testbucket6
|
||||
project_id: 6
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 7
|
||||
title: testbucket7
|
||||
project_id: 7
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 8
|
||||
title: testbucket8
|
||||
project_id: 8
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 9
|
||||
title: testbucket9
|
||||
project_id: 9
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 10
|
||||
title: testbucket10
|
||||
project_id: 10
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 11
|
||||
title: testbucket11
|
||||
project_id: 11
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 12
|
||||
title: testbucket13
|
||||
project_id: 12
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 13
|
||||
title: testbucket13
|
||||
project_id: 13
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 14
|
||||
title: testbucket14
|
||||
project_id: 14
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 15
|
||||
title: testbucket15
|
||||
project_id: 15
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 16
|
||||
title: testbucket16
|
||||
project_id: 16
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 17
|
||||
title: testbucket17
|
||||
project_id: 17
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 18
|
||||
title: testbucket18
|
||||
project_id: 5
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 19
|
||||
title: testbucket19
|
||||
project_id: 21
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 20
|
||||
title: testbucket20
|
||||
project_id: 22
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 21
|
||||
title: testbucket21
|
||||
project_id: 3
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
# Duplicate buckets to make deletion of one of them possible
|
||||
- id: 22
|
||||
title: testbucket22
|
||||
project_id: 6
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 23
|
||||
title: testbucket23
|
||||
project_id: 7
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 24
|
||||
title: testbucket24
|
||||
project_id: 8
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 25
|
||||
title: testbucket25
|
||||
project_id: 9
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 26
|
||||
title: testbucket26
|
||||
project_id: 10
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 27
|
||||
title: testbucket27
|
||||
project_id: 11
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 28
|
||||
title: testbucket28
|
||||
project_id: 12
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 29
|
||||
title: testbucket29
|
||||
project_id: 13
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 30
|
||||
title: testbucket30
|
||||
project_id: 14
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 31
|
||||
title: testbucket31
|
||||
project_id: 15
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 32
|
||||
title: testbucket32
|
||||
project_id: 16
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 33
|
||||
title: testbucket33
|
||||
project_id: 17
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
# This bucket is the last one in its project
|
||||
- id: 34
|
||||
title: testbucket34
|
||||
project_id: 18
|
||||
created_by_id: 1
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 35
|
||||
title: testbucket35
|
||||
project_id: 23
|
||||
created_by_id: -2
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
||||
- id: 36
|
||||
title: testbucket36
|
||||
project_id: 26
|
||||
created_by_id: 15
|
||||
created: 2020-04-18 21:13:52
|
||||
updated: 2020-04-18 21:13:52
|
|
@ -0,0 +1,21 @@
|
|||
- entity_id: 1
|
||||
user_id: 1
|
||||
kind: 1
|
||||
- entity_id: 15
|
||||
user_id: 6 # owner
|
||||
kind: 1
|
||||
- entity_id: 15
|
||||
user_id: 1
|
||||
kind: 1
|
||||
- entity_id: 34
|
||||
user_id: 13 # owner
|
||||
kind: 1
|
||||
- entity_id: 34
|
||||
user_id: 1
|
||||
kind: 1
|
||||
- entity_id: 23
|
||||
user_id: 12 # owner
|
||||
kind: 2
|
||||
- entity_id: 23
|
||||
user_id: 1
|
||||
kind: 2
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue