Compare commits
70 Commits
main
...
release/0.
Author | SHA1 | Date | |
---|---|---|---|
6259dd8d42 | |||
1d96d220eb | |||
65f8a816ff | |||
766f676bea | |||
41d6df833c | |||
135b5fb24e | |||
55c9c791ed | |||
cedc2c3634 | |||
adb6670e9e | |||
f3d5993d82 | |||
f383c4e27b | |||
8d23a78647 | |||
75e9056d7b | |||
8ab083582e | |||
65d26a162d | |||
5095f5603d | |||
|
05e2286dfc | ||
91fc21679a | |||
2d5a41bdcf | |||
|
c0b12a60da | ||
|
7224032ed8 | ||
f05ce69db8 | |||
c629fa0676 | |||
3b03ab719e | |||
66e147b992 | |||
c6613c4530 | |||
a0a1f4f9cb | |||
02f7f56ba2 | |||
7b1e1c79e3 | |||
b5d322e4b6 | |||
|
60dfce54ec | ||
7fbaf3afff | |||
1950692e85 | |||
446525837c | |||
bffbc9492f | |||
75a57dafa6 | |||
b59322dec4 | |||
4f39aa21e9 | |||
629d6a278f | |||
c58584bd63 | |||
b4f7e286f9 | |||
06d822b547 | |||
7e272a9abe | |||
e8626ae543 | |||
3875f9ff9d | |||
962c96fbb7 | |||
61a1e6d057 | |||
1cf4a3fdff | |||
1e0dcba7f9 | |||
42c45155fa | |||
98b8744e03 | |||
b6073db9f2 | |||
e7f1f6cde7 | |||
8ed7aec7a0 | |||
454ba32fe3 | |||
94b08c0307 | |||
15811fd4d4 | |||
642b2067bf | |||
dab108ae86 | |||
652b9f0776 | |||
5e374e695e | |||
93162715e3 | |||
6f841d2ee8 | |||
6ac8dd7145 | |||
dec7f75c65 | |||
03c79da74e | |||
9bd6795266 | |||
961f3e244f | |||
6ee0a605fe | |||
41c9e3f9a4 |
|
@ -1,4 +1,7 @@
|
|||
files/
|
||||
dist/
|
||||
logs/
|
||||
|
||||
Dockerfile
|
||||
docker-manifest.tmpl
|
||||
docker-manifest-unstable.tmpl
|
||||
|
|
288
.drone.yml
288
.drone.yml
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: testing
|
||||
|
||||
workspace:
|
||||
|
@ -111,7 +112,7 @@ steps:
|
|||
# compiling the same magefile at the same time. It's also faster if each step does not need to compile it first.
|
||||
- name: mage
|
||||
image: vikunja/golang-build:latest
|
||||
pull: true
|
||||
pull: always
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
commands:
|
||||
|
@ -122,7 +123,7 @@ steps:
|
|||
|
||||
- name: build
|
||||
image: vikunja/golang-build:latest
|
||||
pull: true
|
||||
pull: always
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
depends_on: [ mage ]
|
||||
|
@ -133,7 +134,7 @@ steps:
|
|||
|
||||
- name: lint
|
||||
image: golang:1.19-alpine
|
||||
pull: true
|
||||
pull: always
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
depends_on: [ build ]
|
||||
|
@ -147,7 +148,7 @@ steps:
|
|||
|
||||
- name: test-migration-prepare
|
||||
image: kolaente/toolbox:latest
|
||||
pull: true
|
||||
pull: always
|
||||
commands:
|
||||
# Get the latest version
|
||||
- wget https://dl.vikunja.io/api/unstable/vikunja-unstable-linux-amd64-full.zip -q -O vikunja-latest.zip
|
||||
|
@ -155,7 +156,7 @@ steps:
|
|||
|
||||
- name: test-migration-sqlite
|
||||
image: vikunja/golang-build:latest
|
||||
pull: true
|
||||
pull: always
|
||||
depends_on: [ test-migration-prepare, build ]
|
||||
environment:
|
||||
VIKUNJA_DATABASE_TYPE: sqlite
|
||||
|
@ -174,7 +175,7 @@ steps:
|
|||
|
||||
- name: test-migration-mysql
|
||||
image: vikunja/golang-build:latest
|
||||
pull: true
|
||||
pull: always
|
||||
depends_on: [ test-migration-prepare, build ]
|
||||
environment:
|
||||
VIKUNJA_DATABASE_TYPE: mysql
|
||||
|
@ -193,7 +194,7 @@ steps:
|
|||
|
||||
- name: test-migration-psql
|
||||
image: vikunja/golang-build:latest
|
||||
pull: true
|
||||
pull: always
|
||||
depends_on: [ test-migration-prepare, build ]
|
||||
environment:
|
||||
VIKUNJA_DATABASE_TYPE: postgres
|
||||
|
@ -213,7 +214,7 @@ steps:
|
|||
|
||||
- name: test
|
||||
image: vikunja/golang-build:latest
|
||||
pull: true
|
||||
pull: always
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
commands:
|
||||
|
@ -224,7 +225,7 @@ steps:
|
|||
|
||||
- name: test-sqlite
|
||||
image: vikunja/golang-build:latest
|
||||
pull: true
|
||||
pull: always
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
VIKUNJA_TESTS_USE_CONFIG: 1
|
||||
|
@ -241,7 +242,7 @@ steps:
|
|||
|
||||
- name: test-mysql
|
||||
image: vikunja/golang-build:latest
|
||||
pull: true
|
||||
pull: always
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
VIKUNJA_TESTS_USE_CONFIG: 1
|
||||
|
@ -258,7 +259,7 @@ steps:
|
|||
|
||||
- name: test-postgres
|
||||
image: vikunja/golang-build:latest
|
||||
pull: true
|
||||
pull: always
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
VIKUNJA_TESTS_USE_CONFIG: 1
|
||||
|
@ -276,7 +277,7 @@ steps:
|
|||
|
||||
- name: integration-test
|
||||
image: vikunja/golang-build:latest
|
||||
pull: true
|
||||
pull: always
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
commands:
|
||||
|
@ -287,7 +288,7 @@ steps:
|
|||
|
||||
- name: integration-test-sqlite
|
||||
image: vikunja/golang-build:latest
|
||||
pull: true
|
||||
pull: always
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
VIKUNJA_TESTS_USE_CONFIG: 1
|
||||
|
@ -304,7 +305,7 @@ steps:
|
|||
|
||||
- name: integration-test-mysql
|
||||
image: vikunja/golang-build:latest
|
||||
pull: true
|
||||
pull: always
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
VIKUNJA_TESTS_USE_CONFIG: 1
|
||||
|
@ -321,7 +322,7 @@ steps:
|
|||
|
||||
- name: integration-test-postgres
|
||||
image: vikunja/golang-build:latest
|
||||
pull: true
|
||||
pull: always
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
VIKUNJA_TESTS_USE_CONFIG: 1
|
||||
|
@ -343,14 +344,15 @@ steps:
|
|||
########
|
||||
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: release
|
||||
|
||||
depends_on:
|
||||
- testing
|
||||
|
||||
workspace:
|
||||
base: /go
|
||||
path: src/code.vikunja.io/api
|
||||
base: /source
|
||||
path: /
|
||||
|
||||
trigger:
|
||||
ref:
|
||||
|
@ -368,7 +370,7 @@ steps:
|
|||
# compiling the same magefile at the same time. It's also faster if each step does not need to compile it first.
|
||||
- name: mage
|
||||
image: vikunja/golang-build:latest
|
||||
pull: true
|
||||
pull: always
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
commands:
|
||||
|
@ -378,7 +380,7 @@ steps:
|
|||
|
||||
- name: before-static-build
|
||||
image: techknowlogick/xgo:latest
|
||||
pull: true
|
||||
pull: always
|
||||
commands:
|
||||
- export PATH=$PATH:$GOPATH/bin
|
||||
- go install github.com/magefile/mage
|
||||
|
@ -387,7 +389,7 @@ steps:
|
|||
|
||||
- name: static-build-windows
|
||||
image: techknowlogick/xgo:latest
|
||||
pull: true
|
||||
pull: always
|
||||
environment:
|
||||
# This path does not exist. However, when we set the gopath to /go, the build fails. Not sure why.
|
||||
# Leaving this here until we know how to resolve this properly.
|
||||
|
@ -400,7 +402,7 @@ steps:
|
|||
|
||||
- name: static-build-linux
|
||||
image: techknowlogick/xgo:latest
|
||||
pull: true
|
||||
pull: always
|
||||
environment:
|
||||
# This path does not exist. However, when we set the gopath to /go, the build fails. Not sure why.
|
||||
# Leaving this here until we know how to resolve this properly.
|
||||
|
@ -413,7 +415,7 @@ steps:
|
|||
|
||||
- name: static-build-darwin
|
||||
image: techknowlogick/xgo:latest
|
||||
pull: true
|
||||
pull: always
|
||||
environment:
|
||||
# This path does not exist. However, when we set the gopath to /go, the build fails. Not sure why.
|
||||
# Leaving this here until we know how to resolve this properly.
|
||||
|
@ -426,7 +428,7 @@ steps:
|
|||
|
||||
- name: after-build-compress
|
||||
image: kolaente/upx
|
||||
pull: true
|
||||
pull: always
|
||||
depends_on:
|
||||
- static-build-windows
|
||||
- static-build-linux
|
||||
|
@ -436,7 +438,7 @@ steps:
|
|||
|
||||
- name: after-build-static
|
||||
image: techknowlogick/xgo:latest
|
||||
pull: true
|
||||
pull: always
|
||||
depends_on:
|
||||
- after-build-compress
|
||||
commands:
|
||||
|
@ -448,7 +450,7 @@ steps:
|
|||
|
||||
- name: sign-release
|
||||
image: plugins/gpgsign:1
|
||||
pull: true
|
||||
pull: always
|
||||
depends_on: [ after-build-static ]
|
||||
settings:
|
||||
key:
|
||||
|
@ -462,7 +464,7 @@ steps:
|
|||
# Push the releases to our pseudo-s3-bucket
|
||||
- name: release-latest
|
||||
image: plugins/s3
|
||||
pull: true
|
||||
pull: always
|
||||
settings:
|
||||
bucket: vikunja-releases
|
||||
access_key:
|
||||
|
@ -484,7 +486,7 @@ steps:
|
|||
|
||||
- name: release-version
|
||||
image: plugins/s3
|
||||
pull: true
|
||||
pull: always
|
||||
settings:
|
||||
bucket: vikunja-releases
|
||||
access_key:
|
||||
|
@ -504,8 +506,8 @@ steps:
|
|||
|
||||
# Build os packages and push it to our bucket
|
||||
- name: build-os-packages-unstable
|
||||
image: goreleaser/nfpm
|
||||
pull: true
|
||||
image: goreleaser/nfpm:v2.23.0
|
||||
pull: always
|
||||
commands:
|
||||
- apk add git go
|
||||
- ./mage-static release:packages
|
||||
|
@ -517,11 +519,11 @@ steps:
|
|||
- main
|
||||
event:
|
||||
- push
|
||||
depends_on: [ static-build-linux ]
|
||||
depends_on: [ after-build-compress ]
|
||||
|
||||
- name: build-os-packages-version
|
||||
image: goreleaser/nfpm
|
||||
pull: true
|
||||
image: goreleaser/nfpm:v2.23.0
|
||||
pull: always
|
||||
commands:
|
||||
- apk add git go
|
||||
- ./mage-static release:packages
|
||||
|
@ -531,12 +533,12 @@ steps:
|
|||
when:
|
||||
event:
|
||||
- tag
|
||||
depends_on: [ static-build-linux ]
|
||||
depends_on: [ after-build-compress ]
|
||||
|
||||
# Push the os releases to our pseudo-s3-bucket
|
||||
- name: release-os-latest
|
||||
image: plugins/s3
|
||||
pull: true
|
||||
pull: always
|
||||
settings:
|
||||
bucket: vikunja-releases
|
||||
access_key:
|
||||
|
@ -558,7 +560,7 @@ steps:
|
|||
|
||||
- name: release-os-version
|
||||
image: plugins/s3
|
||||
pull: true
|
||||
pull: always
|
||||
settings:
|
||||
bucket: vikunja-releases
|
||||
access_key:
|
||||
|
@ -578,6 +580,7 @@ steps:
|
|||
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: deploy-docs
|
||||
|
||||
workspace:
|
||||
|
@ -596,8 +599,7 @@ trigger:
|
|||
steps:
|
||||
- name: theme
|
||||
image: kolaente/toolbox
|
||||
pull: true
|
||||
group: build-static
|
||||
pull: always
|
||||
commands:
|
||||
- mkdir docs/themes/vikunja -p
|
||||
- cd docs/themes/vikunja
|
||||
|
@ -605,8 +607,8 @@ steps:
|
|||
- tar -xzf vikunja-theme.tar.gz
|
||||
|
||||
- name: build
|
||||
image: klakegg/hugo:0.104.2
|
||||
pull: true
|
||||
image: klakegg/hugo:0.107.0
|
||||
pull: always
|
||||
commands:
|
||||
- cd docs
|
||||
- hugo
|
||||
|
@ -614,7 +616,7 @@ steps:
|
|||
|
||||
- name: docker
|
||||
image: plugins/docker
|
||||
pull: true
|
||||
pull: always
|
||||
settings:
|
||||
username:
|
||||
from_secret: docker_username
|
||||
|
@ -627,102 +629,11 @@ steps:
|
|||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: docker-arm-release
|
||||
name: docker-release
|
||||
|
||||
depends_on:
|
||||
- testing
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: arm64
|
||||
|
||||
trigger:
|
||||
ref:
|
||||
- refs/heads/main
|
||||
- "refs/tags/**"
|
||||
|
||||
steps:
|
||||
- name: fetch-tags
|
||||
image: docker:git
|
||||
commands:
|
||||
- git fetch --tags
|
||||
|
||||
- name: docker-arm-unstable
|
||||
image: plugins/docker:linux-arm
|
||||
pull: true
|
||||
settings:
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
repo: vikunja/api
|
||||
tags: unstable-linux-arm
|
||||
dockerfile: Dockerfile.arm32
|
||||
depends_on: [ fetch-tags ]
|
||||
when:
|
||||
ref:
|
||||
- refs/heads/main
|
||||
|
||||
- name: docker-arm
|
||||
image: plugins/docker:linux-arm
|
||||
pull: true
|
||||
settings:
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
repo: vikunja/api
|
||||
auto_tag: true
|
||||
auto_tag_suffix: linux-arm
|
||||
dockerfile: Dockerfile.arm32
|
||||
depends_on: [ fetch-tags ]
|
||||
when:
|
||||
ref:
|
||||
- "refs/tags/**"
|
||||
|
||||
- name: docker-arm64-unstable
|
||||
image: plugins/docker:linux-arm64
|
||||
pull: true
|
||||
settings:
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
repo: vikunja/api
|
||||
tags: unstable-linux-arm64
|
||||
depends_on: [ fetch-tags ]
|
||||
when:
|
||||
ref:
|
||||
- refs/heads/main
|
||||
|
||||
- name: docker-arm64
|
||||
image: plugins/docker:linux-arm64
|
||||
pull: true
|
||||
settings:
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
repo: vikunja/api
|
||||
auto_tag: true
|
||||
auto_tag_suffix: linux-arm64
|
||||
depends_on: [ fetch-tags ]
|
||||
when:
|
||||
ref:
|
||||
- "refs/tags/**"
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: docker-amd64-release
|
||||
|
||||
depends_on:
|
||||
- testing
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: amd64
|
||||
|
||||
trigger:
|
||||
ref:
|
||||
- refs/heads/main
|
||||
|
@ -735,94 +646,55 @@ steps:
|
|||
- git fetch --tags
|
||||
|
||||
- name: docker-unstable
|
||||
image: plugins/docker:linux-amd64
|
||||
pull: true
|
||||
settings:
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
repo: vikunja/api
|
||||
tags: unstable-linux-amd64
|
||||
depends_on: [ fetch-tags ]
|
||||
when:
|
||||
ref:
|
||||
- refs/heads/main
|
||||
|
||||
- name: docker
|
||||
image: plugins/docker:linux-amd64
|
||||
pull: true
|
||||
settings:
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
repo: vikunja/api
|
||||
auto_tag: true
|
||||
auto_tag_suffix: linux-amd64
|
||||
depends_on: [ fetch-tags ]
|
||||
when:
|
||||
ref:
|
||||
- "refs/tags/**"
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: docker-manifest
|
||||
|
||||
trigger:
|
||||
ref:
|
||||
- refs/heads/main
|
||||
- "refs/tags/**"
|
||||
|
||||
depends_on:
|
||||
- docker-amd64-release
|
||||
- docker-arm-release
|
||||
|
||||
steps:
|
||||
- name: manifest-unstable
|
||||
image: thegeeklab/drone-docker-buildx
|
||||
privileged: true
|
||||
pull: always
|
||||
image: plugins/manifest
|
||||
settings:
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
repo: vikunja/api
|
||||
tags: unstable
|
||||
ignore_missing: true
|
||||
spec: docker-manifest-unstable.tmpl
|
||||
password:
|
||||
from_secret: docker_password
|
||||
username:
|
||||
from_secret: docker_username
|
||||
platforms:
|
||||
- linux/386
|
||||
- linux/amd64
|
||||
- linux/arm/v6
|
||||
- linux/arm/v7
|
||||
- linux/arm64/v8
|
||||
depends_on: [ fetch-tags ]
|
||||
when:
|
||||
ref:
|
||||
- refs/heads/main
|
||||
|
||||
- name: manifest-release
|
||||
pull: always
|
||||
image: plugins/manifest
|
||||
settings:
|
||||
auto_tag: true
|
||||
ignore_missing: true
|
||||
spec: docker-manifest.tmpl
|
||||
password:
|
||||
from_secret: docker_password
|
||||
username:
|
||||
from_secret: docker_username
|
||||
- name: generate-tags
|
||||
image: thegeeklab/docker-autotag
|
||||
environment:
|
||||
DOCKER_AUTOTAG_VERSION: ${DRONE_TAG}
|
||||
DOCKER_AUTOTAG_EXTRA_TAGS: latest
|
||||
DOCKER_AUTOTAG_OUTPUT_FILE: .tags
|
||||
depends_on: [ fetch-tags ]
|
||||
when:
|
||||
ref:
|
||||
- "refs/tags/**"
|
||||
|
||||
- name: manifest-release-latest
|
||||
- name: docker-release
|
||||
image: thegeeklab/drone-docker-buildx
|
||||
privileged: true
|
||||
pull: always
|
||||
image: plugins/manifest
|
||||
depends_on:
|
||||
- clone
|
||||
settings:
|
||||
tags: latest
|
||||
ignore_missing: true
|
||||
spec: docker-manifest.tmpl
|
||||
password:
|
||||
from_secret: docker_password
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
repo: vikunja/api
|
||||
platforms:
|
||||
- linux/386
|
||||
- linux/amd64
|
||||
- linux/arm/v6
|
||||
- linux/arm/v7
|
||||
- linux/arm64/v8
|
||||
depends_on: [ generate-tags ]
|
||||
when:
|
||||
ref:
|
||||
- "refs/tags/**"
|
||||
|
@ -841,9 +713,7 @@ depends_on:
|
|||
- testing
|
||||
- release
|
||||
- deploy-docs
|
||||
- docker-arm-release
|
||||
- docker-amd64-release
|
||||
- docker-manifest
|
||||
- docker-release
|
||||
|
||||
steps:
|
||||
- name: notify
|
||||
|
@ -861,6 +731,6 @@ steps:
|
|||
- failure
|
||||
---
|
||||
kind: signature
|
||||
hmac: 5500acb776acae4975592637767035c45af277f1f56c8d10ffd99f38761cd5c8
|
||||
hmac: 148171b051fff279f249608111b56ef0fb5a09a8944ce7aee3e3cf7cf5b6fe4a
|
||||
|
||||
...
|
||||
|
|
|
@ -1,58 +0,0 @@
|
|||
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
|
84
CHANGELOG.md
84
CHANGELOG.md
|
@ -7,6 +7,90 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|||
|
||||
All releases can be found on https://code.vikunja.io/api/releases.
|
||||
|
||||
## [0.20.2] - 2023-01-24
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* *(build)* Downgrade xgo to 1.19.2 so that builds work again
|
||||
* *(caldav)* Add Z suffix to dates make it clear dates are in UTC
|
||||
* *(caldav)* Use const for repeat modes
|
||||
* *(ci)* Pipeline dependency
|
||||
* *(ci)* Pin nfpm container version and binary location
|
||||
* *(ci)* Set release path to /source
|
||||
* *(ci)* Tagging logic for release docker images
|
||||
* *(docs)* Add docs about cli user delete
|
||||
* *(docs)* Old helm charts url (#1344)
|
||||
* *(docs)* Fix a few minor typos (#59)
|
||||
* *(drone)* Add type, fix pull, remove group (#1355)
|
||||
* *(dump)* Make sure null dates are properly set when restoring from a dump
|
||||
* *(export)* Ignore file size for export files
|
||||
* *(list)* Return lists for a namespace id even if that namespace is deleted
|
||||
* *(mailer)* Forcessl config (#60)
|
||||
* *(migration)* Use Todoist v9 api to migrate tasks from them
|
||||
* *(migration)* Import TickTick data by column name instead of index (#1356)
|
||||
* *(migration)* Use the proper authorization method for Todoist's api, fix issues with importing deleted items
|
||||
* *(reminders)* Overdue tasks join condition
|
||||
* *(reminders)* Make sure an overdue reminder is sent when there is only one overdue task
|
||||
* *(reminders)* Prevent duplicate reminders when updating task details
|
||||
* *(restore)* Check if we're really dealing with a string
|
||||
* *(tasks)* Don't include undone overdue tasks from archived lists or namespaces in notification mails
|
||||
* *(tasks)* Don't reset the kanban bucket when updating a task and not providing one
|
||||
* *(tasks)* Don't set a repeating task done when moving it do the done bucket
|
||||
* *(user)* Make reset the user's name to empty actually work* Swagger docs ([41c9e3f](41c9e3f9a47280887b56941280904aea6ef31f85))
|
||||
* Restore notifications table from dump when it already had the correct format ([15811fd](15811fd4d4485cd25cf8d2f8fdd04ebfea8e6663))
|
||||
|
||||
|
||||
### Dependencies
|
||||
|
||||
* *(deps)* Update module github.com/yuin/goldmark to v1.5.3 (#1317)
|
||||
* *(deps)* Update module golang.org/x/crypto to v0.2.0 (#1315)
|
||||
* *(deps)* Update module github.com/spf13/afero to v1.9.3 (#1320)
|
||||
* *(deps)* Update module golang.org/x/crypto to v0.3.0 (#1321)
|
||||
* *(deps)* Update github.com/arran4/golang-ical digest to a677353 (#1323)
|
||||
* *(deps)* Update module github.com/wneessen/go-mail to v0.3.5 (#1325)
|
||||
* *(deps)* Update github.com/arran4/golang-ical digest to 1093469 (#1326)
|
||||
* *(deps)* Update module github.com/golang-jwt/jwt/v4 to v4.4.3 (#1328)
|
||||
* *(deps)* Update module github.com/go-sql-driver/mysql to v1.7.0 (#1332)
|
||||
* *(deps)* Update module golang.org/x/sys to v0.3.0 (#1333)
|
||||
* *(deps)* Update module golang.org/x/term to v0.3.0 (#1336)
|
||||
* *(deps)* Update module golang.org/x/image to v0.2.0 (#1335)
|
||||
* *(deps)* Update module golang.org/x/oauth2 to v0.2.0 (#1316)
|
||||
* *(deps)* Update module golang.org/x/oauth2 to v0.3.0 (#1337)
|
||||
* *(deps)* Update module github.com/getsentry/sentry-go to v0.16.0 (#1338)
|
||||
* *(deps)* Update module golang.org/x/crypto to v0.4.0 (#1339)
|
||||
* *(deps)* Update module github.com/pquerna/otp to v1.4.0 (#1341)
|
||||
* *(deps)* Update module github.com/swaggo/swag to v1.8.9 (#1327)
|
||||
* *(deps)* Update module github.com/wneessen/go-mail to v0.3.6 (#1342)
|
||||
* *(deps)* Update module github.com/labstack/echo/v4 to v4.10.0 (#1343)
|
||||
* *(deps)* Update module github.com/wneessen/go-mail to v0.3.7 (#1348)
|
||||
* *(deps)* Update module github.com/coreos/go-oidc/v3 to v3.5.0 (#1349)
|
||||
* *(deps)* Update module golang.org/x/sys to v0.4.0 (#1351)
|
||||
* *(deps)* Update module golang.org/x/image to v0.3.0 (#1350)
|
||||
* *(deps)* Update module golang.org/x/term to v0.4.0 (#1352)
|
||||
* *(deps)* Update module golang.org/x/crypto to v0.5.0 (#1353)
|
||||
* *(deps)* Update goreleaser/nfpm docker tag to v2.23.0 (#1347)
|
||||
* *(deps)* Update module github.com/wneessen/go-mail to v0.3.8 (#1357)
|
||||
* *(deps)* Update module src.techknowlogick.com/xgo to v1.6.0+1.19.5 (#1358)
|
||||
* *(deps)* Update klakegg/hugo docker tag to v0.107.0 (#1272)
|
||||
* *(deps)* Update module github.com/getsentry/sentry-go to v0.17.0 (#1361)
|
||||
* *(deps)* Update module src.techknowlogick.com/xgo to v1.7.0+1.19.5 (#1364)
|
||||
* *(deps)* Update module github.com/spf13/viper to v1.15.0 (#1365)
|
||||
* *(deps)* Update module github.com/labstack/echo-jwt/v4 to v4.0.1 (#1369)
|
||||
|
||||
### Features
|
||||
|
||||
* *(migrators)* Remove wunderlist (#1346)
|
||||
* *(release)* Use compressed binaries for package releases
|
||||
* Use docker buildx to build multiarch images ([9bd6795](9bd6795266fd54ae42664c20ed7633ac7daf6199))
|
||||
|
||||
### Miscellaneous Tasks
|
||||
|
||||
* Remove custom gitea bug template in favor of githubs ([7b1e1c7](7b1e1c79e358f3fcecb217259491f016402cdcc7))
|
||||
|
||||
### Other
|
||||
|
||||
* *(other)* Added Google & Google Workspace to OpenId examples (#1319)
|
||||
|
||||
## [0.20.1] - 2022-11-11
|
||||
|
||||
### Bug Fixes
|
||||
|
|
12
Dockerfile
12
Dockerfile
|
@ -1,9 +1,9 @@
|
|||
|
||||
##############
|
||||
# Build stage
|
||||
FROM golang:1.19-alpine AS build-env
|
||||
FROM --platform=$BUILDPLATFORM techknowlogick/xgo:go-1.19.2 AS build-env
|
||||
|
||||
RUN apk --no-cache add build-base git && \
|
||||
RUN \
|
||||
go install github.com/magefile/mage@latest && \
|
||||
mv /go/bin/mage /usr/local/go/bin
|
||||
|
||||
|
@ -13,9 +13,11 @@ ARG VIKUNJA_VERSION
|
|||
COPY . /go/src/code.vikunja.io/api
|
||||
WORKDIR /go/src/code.vikunja.io/api
|
||||
|
||||
ARG TARGETOS TARGETARCH TARGETVARIANT
|
||||
# Checkout version if set
|
||||
RUN if [ -n "${VIKUNJA_VERSION}" ]; then git checkout "${VIKUNJA_VERSION}"; fi \
|
||||
&& mage build:clean build
|
||||
RUN if [ -n "${VIKUNJA_VERSION}" ]; then git checkout "${VIKUNJA_VERSION}"; fi && \
|
||||
mage build:clean && \
|
||||
mage release:xgo $TARGETOS/$TARGETARCH/$TARGETVARIANT
|
||||
|
||||
###################
|
||||
# The actual image
|
||||
|
@ -25,7 +27,7 @@ FROM alpine:3.16
|
|||
LABEL maintainer="maintainers@vikunja.io"
|
||||
|
||||
WORKDIR /app/vikunja/
|
||||
COPY --from=build-env /go/src/code.vikunja.io/api/vikunja .
|
||||
COPY --from=build-env /build/vikunja-* vikunja
|
||||
ENV VIKUNJA_SERVICE_ROOTPATH=/app/vikunja/
|
||||
|
||||
# Dynamic permission changing stuff
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
|
||||
##############
|
||||
# Build stage
|
||||
FROM golang:1.19-buster AS build-env
|
||||
|
||||
RUN go install github.com/magefile/mage@latest && \
|
||||
mv /go/bin/mage /usr/local/go/bin
|
||||
|
||||
ARG VIKUNJA_VERSION
|
||||
|
||||
# Setup repo
|
||||
COPY . /go/src/code.vikunja.io/api
|
||||
WORKDIR /go/src/code.vikunja.io/api
|
||||
|
||||
# Checkout version if set
|
||||
RUN if [ -n "${VIKUNJA_VERSION}" ]; then git checkout "${VIKUNJA_VERSION}"; fi \
|
||||
&& mage build:clean build
|
||||
|
||||
###################
|
||||
# The actual image
|
||||
# Note: I wanted to use the scratch image here, but unfortunatly the go-sqlite bindings require cgo and
|
||||
# because of this, the container would not start when I compiled the image without cgo.
|
||||
# We're using debian as a base image here because the latest alpine image does not work with arm.
|
||||
FROM debian:buster-slim
|
||||
LABEL maintainer="maintainers@vikunja.io"
|
||||
|
||||
WORKDIR /app/vikunja/
|
||||
COPY --from=build-env /go/src/code.vikunja.io/api/vikunja .
|
||||
ENV VIKUNJA_SERVICE_ROOTPATH=/app/vikunja/
|
||||
|
||||
# Dynamic permission changing stuff
|
||||
ENV PUID 1000
|
||||
ENV PGID 1000
|
||||
RUN addgroup --gid ${PGID} vikunja && \
|
||||
chown ${PUID} -R /app/vikunja && \
|
||||
useradd --shell /bin/sh --gid vikunja --uid ${PUID} --home-dir /app/vikunja vikunja
|
||||
COPY run.sh /run.sh
|
||||
|
||||
# Fix time zone settings not working
|
||||
RUN apt-get update && apt-get install -y tzdata && apt-get clean
|
||||
|
||||
# Files permissions
|
||||
RUN mkdir /app/vikunja/files && \
|
||||
chown -R vikunja /app/vikunja/files
|
||||
VOLUME /app/vikunja/files
|
||||
|
||||
CMD ["/run.sh"]
|
||||
EXPOSE 3456
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
[![Build Status](https://drone.kolaente.de/api/badges/vikunja/api/status.svg)](https://drone.kolaente.de/vikunja/api)
|
||||
[![License: AGPL v3](https://img.shields.io/badge/License-AGPL%20v3-blue.svg)](LICENSE)
|
||||
[![Download](https://img.shields.io/badge/download-v0.20.1-brightgreen.svg)](https://dl.vikunja.io)
|
||||
[![Download](https://img.shields.io/badge/download-v0.20.2-brightgreen.svg)](https://dl.vikunja.io)
|
||||
[![Docker Pulls](https://img.shields.io/docker/pulls/vikunja/api.svg)](https://hub.docker.com/r/vikunja/api/)
|
||||
[![Swagger Docs](https://img.shields.io/badge/swagger-docs-brightgreen.svg)](https://try.vikunja.io/api/v1/docs)
|
||||
[![Go Report Card](https://goreportcard.com/badge/kolaente.dev/vikunja/api)](https://goreportcard.com/report/kolaente.dev/vikunja/api)
|
||||
|
|
|
@ -191,21 +191,6 @@ files:
|
|||
maxsize: 20MB
|
||||
|
||||
migration:
|
||||
# These are the settings for the wunderlist migrator
|
||||
wunderlist:
|
||||
# Wheter to enable the wunderlist migrator or not
|
||||
enable: false
|
||||
# The client id, required for making requests to the wunderlist api
|
||||
# You need to register your vikunja instance at https://developer.wunderlist.com/apps/new to get this
|
||||
clientid:
|
||||
# The client secret, also required for making requests to the wunderlist api
|
||||
clientsecret:
|
||||
# The url where clients are redirected after they authorized Vikunja to access their wunderlist stuff.
|
||||
# This needs to match the url you entered when registering your Vikunja instance at wunderlist.
|
||||
# This is usually the frontend url where the frontend then makes a request to /migration/wunderlist/migrate
|
||||
# with the code obtained from the wunderlist api.
|
||||
# Note that the vikunja frontend expects this to be /migrate/wunderlist
|
||||
redirecturl:
|
||||
todoist:
|
||||
# Wheter to enable the todoist migrator or not
|
||||
enable: false
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
image: vikunja/api:unstable
|
||||
manifests:
|
||||
-
|
||||
image: vikunja/api:unstable-linux-amd64
|
||||
platform:
|
||||
architecture: amd64
|
||||
os: linux
|
||||
-
|
||||
image: vikunja/api:unstable-linux-arm64
|
||||
platform:
|
||||
architecture: arm64
|
||||
os: linux
|
||||
-
|
||||
image: vikunja/api:unstable-linux-arm
|
||||
platform:
|
||||
architecture: arm
|
||||
os: linux
|
|
@ -1,23 +0,0 @@
|
|||
image: vikunja/api:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}}
|
||||
{{#if build.tags}}
|
||||
tags:
|
||||
{{#each build.tags}}
|
||||
- {{this}}
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
manifests:
|
||||
-
|
||||
image: vikunja/api:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-amd64
|
||||
platform:
|
||||
architecture: amd64
|
||||
os: linux
|
||||
-
|
||||
image: vikunja/api:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm64
|
||||
platform:
|
||||
architecture: arm64
|
||||
os: linux
|
||||
-
|
||||
image: vikunja/api:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm
|
||||
platform:
|
||||
architecture: arm
|
||||
os: linux
|
|
@ -969,17 +969,6 @@ Environment path: `VIKUNJA_FILES_MAXSIZE`
|
|||
|
||||
|
||||
|
||||
### wunderlist
|
||||
|
||||
These are the settings for the wunderlist migrator
|
||||
|
||||
Default: `<empty>`
|
||||
|
||||
Full path: `migration.wunderlist`
|
||||
|
||||
Environment path: `VIKUNJA_MIGRATION_WUNDERLIST`
|
||||
|
||||
|
||||
### todoist
|
||||
|
||||
Default: `<empty>`
|
||||
|
|
|
@ -10,7 +10,7 @@ menu:
|
|||
|
||||
# Full docker example
|
||||
|
||||
This docker compose configuration will run Vikunja with backend and frontend with a mariadb as database.
|
||||
This docker compose configuration will run Vikunja with backend and frontend with a mariadb database.
|
||||
It uses an nginx container or traefik on the host to proxy backend and frontend into a single port.
|
||||
|
||||
For all available configuration options, see [configuration]({{< ref "config.md">}}).
|
||||
|
@ -76,7 +76,7 @@ This example lets you host Vikunja without any reverse proxy in front of it. Thi
|
|||
you need to get something up and running. If you want to host Vikunja on one single port instead of two different ones
|
||||
or need tls termination, check out one of the other examples.
|
||||
|
||||
Not that you need to change the `VIKUNJA_API_URL` environment variable to the ip (the docker host you're running this on)
|
||||
Note that you need to change the `VIKUNJA_API_URL` environment variable to the ip (the docker host you're running this on)
|
||||
is reachable at. Because the browser you'll use to access the Vikunja frontend uses that url to make the requests, it
|
||||
has to be able to reach that ip + port from the outside. Putting everything in a private network won't work.
|
||||
|
||||
|
@ -125,7 +125,7 @@ services:
|
|||
|
||||
This example assumes [traefik](https://traefik.io) version 2 installed and configured to [use docker as a configuration provider](https://docs.traefik.io/providers/docker/).
|
||||
|
||||
We also make a few assumtions here which you'll most likely need to adjust for your traefik setup:
|
||||
We also make a few assumptions here which you'll most likely need to adjust for your traefik setup:
|
||||
|
||||
* Your domain is `vikunja.example.com`
|
||||
* The entrypoint you want to make vikunja available from is called `https`
|
||||
|
@ -398,7 +398,7 @@ docker main folders:
|
|||
* vikunja
|
||||
* mariadb
|
||||
|
||||
Synology has it's own GUI for managing Docker containers... But it's easier via docker compose.
|
||||
Synology has its own GUI for managing Docker containers... But it's easier via docker compose.
|
||||
|
||||
To do that, you can
|
||||
|
||||
|
@ -407,7 +407,7 @@ To do that, you can
|
|||
* without activating SSH, by using Portainer (you have to install first, check out [this tutorial](https://www.portainer.io/blog/how-to-install-portainer-on-a-synology-nas) for exmple):
|
||||
1. Go to **Dashboard / Stacks** click the button **"Add Stack"**
|
||||
2. Give it the name Vikunja and paste the adapted docker compose file
|
||||
3. Deploy the Stack with the "Delpoy Stack" button:
|
||||
3. Deploy the Stack with the "Deploy Stack" button:
|
||||
|
||||
![Portainer Stack deploy](/docs/synology-proxy-2.png)
|
||||
|
||||
|
|
|
@ -10,6 +10,6 @@ menu:
|
|||
|
||||
There are two third-party Helm-Charts which can be used to host Vikunja with k8s:
|
||||
|
||||
* [Truecharts](https://truecharts.org/docs/charts/stable/vikunja/)
|
||||
* [Truecharts](https://truecharts.org/charts/stable/vikunja/)
|
||||
* [k8s at Home](https://github.com/k8s-at-home/charts)
|
||||
|
||||
|
|
|
@ -43,3 +43,26 @@ scopes:
|
|||
- 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.
|
|
@ -136,6 +136,21 @@ Flags:
|
|||
* `-p`, `--password`: The password of the new user. You will be asked to enter it if not provided through the flag.
|
||||
* `-u`, `--username`: The username of the new user.
|
||||
|
||||
#### `user delete`
|
||||
|
||||
Start the user deletion process.
|
||||
If called without the `--now` flag, this command will only trigger an email to the user in order for them to confirm and start the deletion process (this is the same behavoir as if the user requested their deletion via the web interface).
|
||||
With the flag the user is deleted **immediately**.
|
||||
|
||||
**USE WITH CAUTION.**
|
||||
|
||||
{{< highlight bash >}}
|
||||
$ vikunja user delete <id> <flags>
|
||||
{{< /highlight >}}
|
||||
|
||||
Flags:
|
||||
* `-n`, `--now` If provided, deletes the user immediately instead of emailing them first.
|
||||
|
||||
#### `user list`
|
||||
|
||||
Shows a list of all users.
|
||||
|
|
53
go.mod
53
go.mod
|
@ -21,56 +21,58 @@ require (
|
|||
gitea.com/xorm/xorm-redis-cache v0.2.0
|
||||
github.com/ThreeDotsLabs/watermill v1.1.1
|
||||
github.com/adlio/trello v1.10.0
|
||||
github.com/arran4/golang-ical v0.0.0-20220517104411-fd89fefb0182
|
||||
github.com/arran4/golang-ical v0.0.0-20221122102835-109346913e54
|
||||
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.4.0
|
||||
github.com/coreos/go-oidc/v3 v3.5.0
|
||||
github.com/cweill/gotests v1.6.0
|
||||
github.com/d4l3k/messagediff v1.2.1
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/dustinkirkland/golang-petname v0.0.0-20191129215211-8e5a1ed0cff0
|
||||
github.com/gabriel-vasile/mimetype v1.4.1
|
||||
github.com/getsentry/sentry-go v0.15.0
|
||||
github.com/getsentry/sentry-go v0.17.0
|
||||
github.com/go-redis/redis/v8 v8.11.5
|
||||
github.com/go-sql-driver/mysql v1.6.0
|
||||
github.com/go-sql-driver/mysql v1.7.0
|
||||
github.com/go-testfixtures/testfixtures/v3 v3.8.1
|
||||
github.com/golang-jwt/jwt/v4 v4.4.2
|
||||
github.com/gocarina/gocsv v0.0.0-20221216233619-1fea7ae8d380
|
||||
github.com/golang-jwt/jwt/v4 v4.4.3
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/iancoleman/strcase v0.2.0
|
||||
github.com/imdario/mergo v0.3.13
|
||||
github.com/jinzhu/copier v0.3.5
|
||||
github.com/labstack/echo/v4 v4.9.1
|
||||
github.com/labstack/echo-jwt/v4 v4.0.1
|
||||
github.com/labstack/echo/v4 v4.10.0
|
||||
github.com/labstack/gommon v0.4.0
|
||||
github.com/lib/pq v1.10.7
|
||||
github.com/magefile/mage v1.14.0
|
||||
github.com/mattn/go-sqlite3 v1.14.16
|
||||
github.com/olekukonko/tablewriter v0.0.5
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||
github.com/pquerna/otp v1.3.0
|
||||
github.com/pquerna/otp v1.4.0
|
||||
github.com/prometheus/client_golang v1.14.0
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/samedi/caldav-go v3.0.0+incompatible
|
||||
github.com/spf13/afero v1.9.2
|
||||
github.com/spf13/afero v1.9.3
|
||||
github.com/spf13/cobra v1.6.1
|
||||
github.com/spf13/viper v1.14.0
|
||||
github.com/spf13/viper v1.15.0
|
||||
github.com/stretchr/testify v1.8.1
|
||||
github.com/swaggo/swag v1.8.7
|
||||
github.com/swaggo/swag v1.8.9
|
||||
github.com/tkuchiki/go-timezone v0.2.2
|
||||
github.com/ulule/limiter/v3 v3.10.0
|
||||
github.com/vectordotdev/go-datemath v0.1.1-0.20211214182920-0a4ac8742b93
|
||||
github.com/wneessen/go-mail v0.3.4
|
||||
github.com/yuin/goldmark v1.5.2
|
||||
golang.org/x/crypto v0.1.0
|
||||
golang.org/x/image v0.1.0
|
||||
golang.org/x/oauth2 v0.1.0
|
||||
github.com/wneessen/go-mail v0.3.8
|
||||
github.com/yuin/goldmark v1.5.3
|
||||
golang.org/x/crypto v0.5.0
|
||||
golang.org/x/image v0.3.0
|
||||
golang.org/x/oauth2 v0.3.0
|
||||
golang.org/x/sync v0.1.0
|
||||
golang.org/x/sys v0.2.0
|
||||
golang.org/x/term v0.2.0
|
||||
golang.org/x/sys v0.4.0
|
||||
golang.org/x/term v0.4.0
|
||||
gopkg.in/d4l3k/messagediff.v1 v1.2.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
src.techknowlogick.com/xgo v1.5.1-0.20220906164532-735bfdfb90d9
|
||||
src.techknowlogick.com/xgo v1.7.1-0.20230117190652-94aee174ab86
|
||||
src.techknowlogick.com/xormigrate v1.5.0
|
||||
xorm.io/builder v0.3.12
|
||||
xorm.io/xorm v1.3.2
|
||||
|
@ -92,6 +94,7 @@ require (
|
|||
github.com/garyburd/redigo v1.6.0 // indirect
|
||||
github.com/ghodss/yaml v1.0.0 // indirect
|
||||
github.com/go-chi/chi v4.0.2+incompatible // indirect
|
||||
github.com/go-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
|
||||
|
@ -108,7 +111,7 @@ require (
|
|||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/laurent22/ical-go v0.1.1-0.20181107184520-7e5d6ade8eef // indirect
|
||||
github.com/lithammer/shortuuid/v3 v3.0.4 // indirect
|
||||
github.com/magiconair/properties v1.8.6 // indirect
|
||||
github.com/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.16 // indirect
|
||||
|
@ -119,7 +122,7 @@ require (
|
|||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/oklog/ulid v1.3.1 // indirect
|
||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.5 // 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
|
||||
|
@ -129,15 +132,15 @@ require (
|
|||
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.1 // indirect
|
||||
github.com/subosito/gotenv v1.4.2 // indirect
|
||||
github.com/syndtr/goleveldb v1.0.0 // indirect
|
||||
github.com/urfave/cli/v2 v2.3.0 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasttemplate v1.2.1 // indirect
|
||||
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
||||
golang.org/x/net v0.1.0 // indirect
|
||||
golang.org/x/text v0.4.0 // indirect
|
||||
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect
|
||||
golang.org/x/net v0.5.0 // indirect
|
||||
golang.org/x/text v0.6.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
golang.org/x/tools v0.1.12 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
|
|
142
go.sum
142
go.sum
|
@ -43,6 +43,7 @@ cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6m
|
|||
cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s=
|
||||
cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU=
|
||||
cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U=
|
||||
cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
|
||||
cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY=
|
||||
|
@ -99,8 +100,8 @@ github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb
|
|||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/arran4/golang-ical v0.0.0-20220517104411-fd89fefb0182 h1:mUsKridvWp4dgfkO/QWtgGwuLtZYpjKgsm15JRRik3o=
|
||||
github.com/arran4/golang-ical v0.0.0-20220517104411-fd89fefb0182/go.mod h1:BSTTrYHuM12oAL8jDdcmPdw02SBThKYWNFHQlvEG6b0=
|
||||
github.com/arran4/golang-ical v0.0.0-20221122102835-109346913e54 h1:HfAA5Vxbo64UTckj+EW/hfBjvvcUcbcwWCASvypy8JU=
|
||||
github.com/arran4/golang-ical v0.0.0-20221122102835-109346913e54/go.mod h1:BSTTrYHuM12oAL8jDdcmPdw02SBThKYWNFHQlvEG6b0=
|
||||
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
|
||||
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef h1:46PFijGLmAjMPwCCCo7Jf0W6f9slllCkkv7vyc1yOSg=
|
||||
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
||||
|
@ -148,6 +149,8 @@ github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:z
|
|||
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
|
||||
github.com/coreos/go-oidc/v3 v3.4.0 h1:xz7elHb/LDwm/ERpwHd+5nb7wFHL32rsr6bBOgaeu6g=
|
||||
github.com/coreos/go-oidc/v3 v3.4.0/go.mod h1:eHUXhZtXPQLgEaDrOVTgwbgmz1xGOkJNye6h3zkD2Pw=
|
||||
github.com/coreos/go-oidc/v3 v3.5.0 h1:VxKtbccHZxs8juq7RdJntSqtXFtde9YpNpGn0yqgEHw=
|
||||
github.com/coreos/go-oidc/v3 v3.5.0/go.mod h1:ecXRtV4romGPeO6ieExAsUK9cb/3fp9hXNz1tlv8PIM=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
|
@ -198,18 +201,16 @@ github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVB
|
|||
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
|
||||
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
|
||||
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
|
||||
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/gabriel-vasile/mimetype v1.4.1 h1:TRWk7se+TOjCYgRth7+1/OYLNiRNIotknkFtf/dnN7Q=
|
||||
github.com/gabriel-vasile/mimetype v1.4.1/go.mod h1:05Vi0w3Y9c/lNvJOdmIwvrrAhX3rYhfQQCaf9VJcv7M=
|
||||
github.com/garyburd/redigo v1.6.0 h1:0VruCpn7yAIIu7pWVClQC8wxCJEcG3nyzpMSHKi1PQc=
|
||||
github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
|
||||
github.com/getsentry/sentry-go v0.14.0 h1:rlOBkuFZRKKdUnKO+0U3JclRDQKlRu5vVQtkWSQvC70=
|
||||
github.com/getsentry/sentry-go v0.14.0/go.mod h1:RZPJKSw+adu8PBNygiri/A98FqVr2HtRckJk9XVxJ9I=
|
||||
github.com/getsentry/sentry-go v0.15.0 h1:CP9bmA7pralrVUedYZsmIHWpq/pBtXTSew7xvVpfLaA=
|
||||
github.com/getsentry/sentry-go v0.15.0/go.mod h1:RZPJKSw+adu8PBNygiri/A98FqVr2HtRckJk9XVxJ9I=
|
||||
github.com/getsentry/sentry-go v0.16.0 h1:owk+S+5XcgJLlGR/3+3s6N4d+uKwqYvh/eS0AIMjPWo=
|
||||
github.com/getsentry/sentry-go v0.16.0/go.mod h1:ZXCloQLj0pG7mja5NK6NPf2V4A88YJ4pNlc2mOHwh6Y=
|
||||
github.com/getsentry/sentry-go v0.17.0 h1:UustVWnOoDFHBS7IJUB2QK/nB5pap748ZEp0swnQJak=
|
||||
github.com/getsentry/sentry-go v0.17.0/go.mod h1:B82dxtBvxG0KaPD8/hfSV+VcHD+Lg/xUS4JuQn1P4cM=
|
||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-chi/chi v4.0.2+incompatible h1:maB6vn6FqCxrpz4FqWdh4+lwpyZIQS7YEAUcHlgXVRs=
|
||||
|
@ -218,6 +219,8 @@ github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxI
|
|||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo=
|
||||
github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
|
||||
|
@ -242,11 +245,14 @@ github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq
|
|||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
|
||||
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-testfixtures/testfixtures/v3 v3.8.1 h1:uonwvepqRvSgddcrReZQhojTlWlmOlHkYAb9ZaOMWgU=
|
||||
github.com/go-testfixtures/testfixtures/v3 v3.8.1/go.mod h1:Kdu7YeMC0KRXVHdaQ91Vmx3pcjoTF63h4f1qTJDdXLA=
|
||||
github.com/gocarina/gocsv v0.0.0-20221216233619-1fea7ae8d380 h1:JJq8YZiS07gFIMYZxkbbiMrXIglG3k5JPPtdvckcnfQ=
|
||||
github.com/gocarina/gocsv v0.0.0-20221216233619-1fea7ae8d380/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI=
|
||||
github.com/goccy/go-json v0.8.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk=
|
||||
github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
|
@ -258,8 +264,8 @@ github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a
|
|||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||
github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs=
|
||||
github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang-jwt/jwt/v4 v4.4.3 h1:Hxl6lhQFj4AnOX6MLrsCb/+7tCj7DxP7VA+2rDIq5AU=
|
||||
github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
|
||||
|
@ -501,9 +507,13 @@ github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
|||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/labstack/echo-jwt/v4 v4.0.0 h1:MFdURJRtBNWzADUdXYlj++71UZ5MmjUtce7nSsCH8NY=
|
||||
github.com/labstack/echo-jwt/v4 v4.0.0/go.mod h1:DHSSaL6cTgczdPXjf8qrTHRbrau2flcddV7CPMs2U/Y=
|
||||
github.com/labstack/echo-jwt/v4 v4.0.1 h1:rxFj0gUPv+1EEhbyfpv463FunuNvW+6MDRGYve7LUxM=
|
||||
github.com/labstack/echo-jwt/v4 v4.0.1/go.mod h1:DHSSaL6cTgczdPXjf8qrTHRbrau2flcddV7CPMs2U/Y=
|
||||
github.com/labstack/echo/v4 v4.1.16/go.mod h1:awO+5TzAjvL8XpibdsfXxPgHr+orhtXZJZIQCVjogKI=
|
||||
github.com/labstack/echo/v4 v4.9.1 h1:GliPYSpzGKlyOhqIbG8nmHBo3i1saKWFOgh41AN3b+Y=
|
||||
github.com/labstack/echo/v4 v4.9.1/go.mod h1:Pop5HLc+xoc4qhTZ1ip6C0RtP7Z+4VzRLWZZFKqbbjo=
|
||||
github.com/labstack/echo/v4 v4.10.0 h1:5CiyngihEO4HXsz3vVsJn7f8xAlWwRr3aY6Ih280ZKA=
|
||||
github.com/labstack/echo/v4 v4.10.0/go.mod h1:S/T/5fy/GigaXnHTkh0ZGe4LpkkQysvRjFMSUTkDRNQ=
|
||||
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
|
||||
github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8=
|
||||
github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
|
||||
|
@ -526,6 +536,8 @@ github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=
|
|||
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
||||
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
|
||||
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
|
@ -558,8 +570,6 @@ github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m
|
|||
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
|
||||
github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
|
||||
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
|
@ -622,6 +632,8 @@ github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3v
|
|||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg=
|
||||
github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas=
|
||||
github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
|
||||
github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
|
||||
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
|
||||
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
|
||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
|
@ -635,8 +647,8 @@ github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qR
|
|||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
github.com/pquerna/otp v1.3.0 h1:oJV/SkzR33anKXwQU3Of42rL4wbrffP4uvUf1SvS5Xs=
|
||||
github.com/pquerna/otp v1.3.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
||||
github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg=
|
||||
github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
|
@ -644,10 +656,6 @@ github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeD
|
|||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
|
||||
github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
|
||||
github.com/prometheus/client_golang v1.13.0 h1:b71QUfeo5M8gq2+evJdTPfZhYMAU0uKPkyPJ7TPsloU=
|
||||
github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ=
|
||||
github.com/prometheus/client_golang v1.13.1 h1:3gMjIY2+/hzmqhtUC/aQNYldJA6DtH3CgQvwS+02K1c=
|
||||
github.com/prometheus/client_golang v1.13.1/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ=
|
||||
github.com/prometheus/client_golang v1.14.0 h1:nJdhIvne2eSX/XRAFV9PcvFFRbrjbcTUj0VP62TMhnw=
|
||||
github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
|
@ -655,7 +663,6 @@ github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:
|
|||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
|
||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
|
||||
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
|
||||
|
@ -709,13 +716,11 @@ github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9
|
|||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw=
|
||||
github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
|
||||
github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk=
|
||||
github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
|
||||
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
|
||||
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
|
||||
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||
github.com/spf13/cobra v1.6.0 h1:42a0n6jwCot1pUmomAp4T7DeMD+20LFv4Q54pxLf2LI=
|
||||
github.com/spf13/cobra v1.6.0/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
|
||||
github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
|
||||
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
|
||||
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
||||
|
@ -723,10 +728,10 @@ github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0
|
|||
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.13.0 h1:BWSJ/M+f+3nmdz9bxB+bWX28kkALN2ok11D0rSo8EJU=
|
||||
github.com/spf13/viper v1.13.0/go.mod h1:Icm2xNL3/8uyh/wFuB1jI7TiTNKp8632Nwegu+zgdYw=
|
||||
github.com/spf13/viper v1.14.0 h1:Rg7d3Lo706X9tHsJMUjdiwMpHB7W8WnSVOssIY+JElU=
|
||||
github.com/spf13/viper v1.14.0/go.mod h1:WT//axPky3FdvXHzGw33dNdXXXfFQqmEalje+egj8As=
|
||||
github.com/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU=
|
||||
github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA=
|
||||
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
|
||||
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
|
||||
github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI=
|
||||
|
@ -742,14 +747,15 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
|
|||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs=
|
||||
github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
|
||||
github.com/swaggo/swag v1.8.7 h1:2K9ivTD3teEO+2fXV6zrZKDqk5IuU2aJtBDo8U7omWU=
|
||||
github.com/swaggo/swag v1.8.7/go.mod h1:ezQVUUhly8dludpVk+/PuwJWvLLanB13ygV5Pr9enSk=
|
||||
github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
|
||||
github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
|
||||
github.com/swaggo/swag v1.8.9 h1:kHtaBe/Ob9AZzAANfcn5c6RyCke9gG9QpH0jky0I/sA=
|
||||
github.com/swaggo/swag v1.8.9/go.mod h1:ezQVUUhly8dludpVk+/PuwJWvLLanB13ygV5Pr9enSk=
|
||||
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
|
||||
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
|
||||
github.com/tkuchiki/go-timezone v0.2.2 h1:MdHR65KwgVTwWFQrota4SKzc4L5EfuH5SdZZGtk/P2Q=
|
||||
|
@ -766,18 +772,17 @@ github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyC
|
|||
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||
github.com/valyala/fasttemplate v1.1.0/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||
github.com/valyala/fasttemplate v1.2.0/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||
github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4=
|
||||
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
|
||||
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||
github.com/vectordotdev/go-datemath v0.1.1-0.20211214182920-0a4ac8742b93 h1:bT0ZMfsMi2Xh8dopgxhFT+OJH88QITHpdppdkG1rXJQ=
|
||||
github.com/vectordotdev/go-datemath v0.1.1-0.20211214182920-0a4ac8742b93/go.mod h1:PnwzbSst7KD3vpBzzlntZU5gjVa455Uqa5QPiKSYJzQ=
|
||||
github.com/wneessen/go-mail v0.3.1 h1:9hSthi8T57gDHMI0fgl58O667OQJm9wZ1EHyuy3hclA=
|
||||
github.com/wneessen/go-mail v0.3.1/go.mod h1:m25lkU2GYQnlVr6tdwK533/UXxo57V0kLOjaFYmub0E=
|
||||
github.com/wneessen/go-mail v0.3.2 h1:nTjAF4Ek2+JG7qunyk6oImf5YKrAE5a7A3uIazYsdM0=
|
||||
github.com/wneessen/go-mail v0.3.2/go.mod h1:m25lkU2GYQnlVr6tdwK533/UXxo57V0kLOjaFYmub0E=
|
||||
github.com/wneessen/go-mail v0.3.3 h1:mhqM18uWiBFA3TdfO2IyFfQ6dEj4kGKW1KQJJNSKLME=
|
||||
github.com/wneessen/go-mail v0.3.3/go.mod h1:m25lkU2GYQnlVr6tdwK533/UXxo57V0kLOjaFYmub0E=
|
||||
github.com/wneessen/go-mail v0.3.4 h1:75G6lojt3CxwSq73csMduxF7DJ3hLF2s2KJXJVDOr0k=
|
||||
github.com/wneessen/go-mail v0.3.4/go.mod h1:m25lkU2GYQnlVr6tdwK533/UXxo57V0kLOjaFYmub0E=
|
||||
github.com/wneessen/go-mail v0.3.6 h1:hT8PMIBdcTkoiDwoUGJssPYOe1Gg1/cUcp2o9+ls63o=
|
||||
github.com/wneessen/go-mail v0.3.6/go.mod h1:m25lkU2GYQnlVr6tdwK533/UXxo57V0kLOjaFYmub0E=
|
||||
github.com/wneessen/go-mail v0.3.7 h1:loEAGLvsDZLSiE6c+keBfg0gpias/R3ocFU8Eoh3Pq4=
|
||||
github.com/wneessen/go-mail v0.3.7/go.mod h1:m25lkU2GYQnlVr6tdwK533/UXxo57V0kLOjaFYmub0E=
|
||||
github.com/wneessen/go-mail v0.3.8 h1:ja5D/o/RVwrtRIYFlrO7GmtcjDNeMakGQuwQRZYv0JM=
|
||||
github.com/wneessen/go-mail v0.3.8/go.mod h1:m25lkU2GYQnlVr6tdwK533/UXxo57V0kLOjaFYmub0E=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
|
@ -785,8 +790,8 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
|
|||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/goldmark v1.5.2 h1:ALmeCk/px5FSm1MAcFBAsVKZjDuMVj8Tm7FFIlMJnqU=
|
||||
github.com/yuin/goldmark v1.5.2/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/goldmark v1.5.3 h1:3HUJmBFbQW9fhQOzMgseU134xfi6hU+mjWywx5Ty+/M=
|
||||
github.com/yuin/goldmark v1.5.3/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
|
||||
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
|
@ -833,8 +838,10 @@ golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm
|
|||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
|
||||
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
||||
golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8=
|
||||
golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80=
|
||||
golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
|
||||
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
|
@ -848,8 +855,10 @@ golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMk
|
|||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.1.0 h1:r8Oj8ZA2Xy12/b5KZYj3tuv7NG/fBz3TwQVvpJ9l8Rk=
|
||||
golang.org/x/image v0.1.0/go.mod h1:iyPr49SD/G/TBxYVB/9RRtGUT5eNbo2u4NamWeQcD5c=
|
||||
golang.org/x/image v0.2.0 h1:/DcQ0w3VHKCC5p0/P2B0JpAZ9Z++V2KOo2fyU89CXBQ=
|
||||
golang.org/x/image v0.2.0/go.mod h1:la7oBXb9w3YFjBqaAwtynVioc1ZvOnNteUNrifGNmAI=
|
||||
golang.org/x/image v0.3.0 h1:HTDXbdK9bjfSWkPzDJIw89W8CAtfFGduujWs33NLLsg=
|
||||
golang.org/x/image v0.3.0/go.mod h1:fXd9211C/0VTlYuAcOhW8dY/RtEJqODXOWBDpmYBf+A=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
|
@ -931,8 +940,11 @@ golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug
|
|||
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
|
||||
golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0=
|
||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
||||
golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU=
|
||||
golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
||||
golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
|
||||
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
|
@ -954,8 +966,8 @@ golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j
|
|||
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
|
||||
golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE=
|
||||
golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg=
|
||||
golang.org/x/oauth2 v0.1.0 h1:isLCZuhj4v+tYv7eskaN4v/TM+A1begWWgyVJDdl1+Y=
|
||||
golang.org/x/oauth2 v0.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A=
|
||||
golang.org/x/oauth2 v0.3.0 h1:6l90koy8/LaBLmLu8jpHeHexzMwEita0zFfYlggy2F8=
|
||||
golang.org/x/oauth2 v0.3.0/go.mod h1:rQrIauxkUhJ6CuwEXwymO2/eh4xz2ZWF1nBkcxS+tGk=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
@ -1064,17 +1076,17 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
|
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
|
||||
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.1.0 h1:g6Z6vPFA9dYBAF7DWcH6sCcOntplXsDKcliusYijMlw=
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.2.0 h1:z85xZCsEl7bi/KwbNADeBYoOP0++7W1ipu+aGnpwzRM=
|
||||
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
|
||||
golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI=
|
||||
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
|
||||
golang.org/x/term v0.4.0 h1:O7UWfv5+A2qiuulQk30kVinPoMtoIPeVaKLEgLpVkvg=
|
||||
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
@ -1084,15 +1096,17 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
|
||||
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
|
||||
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af h1:Yx9k8YCG3dvF87UAn2tu2HQLf2dt/eR1bXxpLMWeH+Y=
|
||||
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
|
@ -1510,6 +1524,10 @@ sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
|||
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
|
||||
src.techknowlogick.com/xgo v1.5.1-0.20220906164532-735bfdfb90d9 h1:7u6pOCURebyXlDy0OhWdnsEBf/KgVKnExA9/w/yCT0A=
|
||||
src.techknowlogick.com/xgo v1.5.1-0.20220906164532-735bfdfb90d9/go.mod h1:31CE1YKtDOrKTk9PSnjTpe6YbO6W/0LTYZ1VskL09oU=
|
||||
src.techknowlogick.com/xgo v1.6.1-0.20230110184414-1fd3d5b59de3 h1:y5MVUua3J82o91nQk1gBGrJA1FJn1X6sLXar4Ec08W8=
|
||||
src.techknowlogick.com/xgo v1.6.1-0.20230110184414-1fd3d5b59de3/go.mod h1:31CE1YKtDOrKTk9PSnjTpe6YbO6W/0LTYZ1VskL09oU=
|
||||
src.techknowlogick.com/xgo v1.7.1-0.20230117190652-94aee174ab86 h1:VybPMHRdCLbdCttI8fMXOaGpoJGSG9+W/5cfRgr1Xjc=
|
||||
src.techknowlogick.com/xgo v1.7.1-0.20230117190652-94aee174ab86/go.mod h1:31CE1YKtDOrKTk9PSnjTpe6YbO6W/0LTYZ1VskL09oU=
|
||||
src.techknowlogick.com/xormigrate v1.5.0 h1:6mWTh8d0sWjMTLUgJqiLe0e0Teu+1j+RgI7ErAeOEV0=
|
||||
src.techknowlogick.com/xormigrate v1.5.0/go.mod h1:QOCnBeWralVncPn9eZlM4w/rglFK8o1vYpemzPenkBM=
|
||||
xorm.io/builder v0.3.7/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
|
||||
|
|
16
magefile.go
16
magefile.go
|
@ -546,6 +546,20 @@ func (Release) Darwin() error {
|
|||
return runXgo("darwin-10.15/*")
|
||||
}
|
||||
|
||||
func (Release) Xgo(target string) error {
|
||||
parts := strings.Split(target, "/")
|
||||
if len(parts) < 2 {
|
||||
return fmt.Errorf("invalid target")
|
||||
}
|
||||
|
||||
variant := ""
|
||||
if len(parts) > 2 && parts[2] != "" {
|
||||
variant = "-" + strings.ReplaceAll(parts[2], "v", "")
|
||||
}
|
||||
|
||||
return runXgo(parts[0] + "/" + parts[1] + variant)
|
||||
}
|
||||
|
||||
// Compresses the built binaries in dist/binaries/ to reduce their filesize
|
||||
func (Release) Compress(ctx context.Context) error {
|
||||
// $(foreach file,$(filter-out $(wildcard $(wildcard $(DIST)/binaries/$(EXECUTABLE)-*mips*)),$(wildcard $(DIST)/binaries/$(EXECUTABLE)-*)), upx -9 $(file);)
|
||||
|
@ -689,7 +703,7 @@ func (Release) Packages() error {
|
|||
binpath := "nfpm"
|
||||
err = exec.Command(binpath).Run()
|
||||
if err != nil && strings.Contains(err.Error(), "executable file not found") {
|
||||
binpath = "/nfpm"
|
||||
binpath = "/usr/bin/nfpm"
|
||||
err = exec.Command(binpath).Run()
|
||||
}
|
||||
if err != nil && strings.Contains(err.Error(), "executable file not found") {
|
||||
|
|
|
@ -22,7 +22,8 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"code.vikunja.io/api/pkg/utils"
|
||||
)
|
||||
|
@ -62,6 +63,8 @@ type Todo struct {
|
|||
End time.Time
|
||||
DueDate time.Time
|
||||
Duration time.Duration
|
||||
RepeatAfter int64
|
||||
RepeatMode models.TaskRepeatMode
|
||||
|
||||
Created time.Time
|
||||
Updated time.Time // last-mod
|
||||
|
@ -226,6 +229,16 @@ CREATED:` + makeCalDavTimeFromTimeStamp(t.Created)
|
|||
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)
|
||||
}
|
||||
}
|
||||
|
||||
caldavtodos += `
|
||||
LAST-MODIFIED:` + makeCalDavTimeFromTimeStamp(t.Updated)
|
||||
|
||||
|
@ -240,7 +253,7 @@ END:VCALENDAR` // Need a line break
|
|||
}
|
||||
|
||||
func makeCalDavTimeFromTimeStamp(ts time.Time) (caldavtime string) {
|
||||
return ts.In(config.GetTimeZone()).Format(DateFormat)
|
||||
return ts.In(time.UTC).Format(DateFormat) + "Z"
|
||||
}
|
||||
|
||||
func calcAlarmDateFromReminder(eventStart, reminder time.Time) (alarmTime string) {
|
||||
|
|
|
@ -20,6 +20,8 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
@ -84,25 +86,25 @@ X-APPLE-CALENDAR-COLOR:#affffeFF
|
|||
X-OUTLOOK-COLOR:#affffeFF
|
||||
X-FUNAMBOL-COLOR:#affffeFF
|
||||
DESCRIPTION:Lorem Ipsum
|
||||
DTSTAMP:20181201T011204
|
||||
DTSTART:20181201T011204
|
||||
DTEND:20181201T013024
|
||||
DTSTAMP:20181201T011204Z
|
||||
DTSTART:20181201T011204Z
|
||||
DTEND:20181201T013024Z
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:randommduidd
|
||||
SUMMARY:Event #2
|
||||
DESCRIPTION:
|
||||
DTSTAMP:20181202T045844
|
||||
DTSTART:20181202T045844
|
||||
DTEND:20181202T081844
|
||||
DTSTAMP:20181202T045844Z
|
||||
DTSTART:20181202T045844Z
|
||||
DTEND:20181202T081844Z
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:20181202T0600242aaef4a81d770c1e775e26bc5abebc87f1d3d7bffaa83
|
||||
SUMMARY:Event #3 with empty uid
|
||||
DESCRIPTION:
|
||||
DTSTAMP:20181202T050024
|
||||
DTSTART:20181202T050024
|
||||
DTEND:20181202T050320
|
||||
DTSTAMP:20181202T050024Z
|
||||
DTSTART:20181202T050024Z
|
||||
DTEND:20181202T050320Z
|
||||
END:VEVENT
|
||||
END:VCALENDAR`,
|
||||
},
|
||||
|
@ -169,9 +171,9 @@ BEGIN:VEVENT
|
|||
UID:randommduid
|
||||
SUMMARY:Event #1
|
||||
DESCRIPTION:Lorem Ipsum
|
||||
DTSTAMP:20181201T011204
|
||||
DTSTART:20181201T011204
|
||||
DTEND:20181201T013024
|
||||
DTSTAMP:20181201T011204Z
|
||||
DTSTART:20181201T011204Z
|
||||
DTEND:20181201T013024Z
|
||||
BEGIN:VALARM
|
||||
TRIGGER:-PT3M20S
|
||||
ACTION:DISPLAY
|
||||
|
@ -192,9 +194,9 @@ BEGIN:VEVENT
|
|||
UID:randommduidd
|
||||
SUMMARY:Event #2
|
||||
DESCRIPTION:
|
||||
DTSTAMP:20181202T045844
|
||||
DTSTART:20181202T045844
|
||||
DTEND:20181202T081844
|
||||
DTSTAMP:20181202T045844Z
|
||||
DTSTART:20181202T045844Z
|
||||
DTEND:20181202T081844Z
|
||||
BEGIN:VALARM
|
||||
TRIGGER:-PT27H50M0S
|
||||
ACTION:DISPLAY
|
||||
|
@ -212,12 +214,12 @@ DESCRIPTION:Event #2
|
|||
END:VALARM
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:20181202T0500242aaef4a81d770c1e775e26bc5abebc87f1d3d7bffaa83
|
||||
UID:20181202T050024Z2aaef4a81d770c1e775e26bc5abebc87f1d3d7bffaa83
|
||||
SUMMARY:Event #3 with empty uid
|
||||
DESCRIPTION:
|
||||
DTSTAMP:20181202T050024
|
||||
DTSTART:20181202T050024
|
||||
DTEND:20181202T050320
|
||||
DTSTAMP:20181202T050024Z
|
||||
DTSTART:20181202T050024Z
|
||||
DTEND:20181202T050320Z
|
||||
BEGIN:VALARM
|
||||
TRIGGER:-PT27H51M40S
|
||||
ACTION:DISPLAY
|
||||
|
@ -240,12 +242,12 @@ DESCRIPTION:Event #3 with empty uid
|
|||
END:VALARM
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:20181202T050024ae7548ce9556df85038abe90dc674d4741a61ce74d1cf
|
||||
UID:20181202T050024Zae7548ce9556df85038abe90dc674d4741a61ce74d1cf
|
||||
SUMMARY:Event #4 without any
|
||||
DESCRIPTION:
|
||||
DTSTAMP:20181202T050024
|
||||
DTSTART:20181202T050024
|
||||
DTEND:20181202T050320
|
||||
DTSTAMP:20181202T050024Z
|
||||
DTSTART:20181202T050024Z
|
||||
DTEND:20181202T050320Z
|
||||
END:VEVENT
|
||||
END:VCALENDAR`,
|
||||
},
|
||||
|
@ -278,9 +280,9 @@ BEGIN:VEVENT
|
|||
UID:randommduid
|
||||
SUMMARY:Event #1
|
||||
DESCRIPTION:Lorem Ipsum\nDolor sit amet
|
||||
DTSTAMP:20181201T011204
|
||||
DTSTART:20181201T011204
|
||||
DTEND:20181201T013024
|
||||
DTSTAMP:20181201T011204Z
|
||||
DTSTART:20181201T011204Z
|
||||
DTEND:20181201T013024Z
|
||||
END:VEVENT
|
||||
END:VCALENDAR`,
|
||||
},
|
||||
|
@ -333,13 +335,13 @@ X-OUTLOOK-COLOR:#ffffffFF
|
|||
X-FUNAMBOL-COLOR:#ffffffFF
|
||||
BEGIN:VTODO
|
||||
UID:randommduid
|
||||
DTSTAMP:20181201T011204
|
||||
DTSTAMP:20181201T011204Z
|
||||
SUMMARY:Todo #1
|
||||
X-APPLE-CALENDAR-COLOR:#affffeFF
|
||||
X-OUTLOOK-COLOR:#affffeFF
|
||||
X-FUNAMBOL-COLOR:#affffeFF
|
||||
DESCRIPTION:Lorem Ipsum\nDolor sit amet
|
||||
LAST-MODIFIED:00010101T000000
|
||||
LAST-MODIFIED:00010101T000000Z
|
||||
END:VTODO
|
||||
END:VCALENDAR`,
|
||||
},
|
||||
|
@ -368,12 +370,12 @@ X-WR-CALNAME:test
|
|||
PRODID:-//RandomProdID which is not random//EN
|
||||
BEGIN:VTODO
|
||||
UID:randommduid
|
||||
DTSTAMP:20181201T011204
|
||||
DTSTAMP:20181201T011204Z
|
||||
SUMMARY:Todo #1
|
||||
DESCRIPTION:Lorem Ipsum
|
||||
COMPLETED:20181201T013024
|
||||
COMPLETED:20181201T013024Z
|
||||
STATUS:COMPLETED
|
||||
LAST-MODIFIED:00010101T000000
|
||||
LAST-MODIFIED:00010101T000000Z
|
||||
END:VTODO
|
||||
END:VCALENDAR`,
|
||||
},
|
||||
|
@ -402,11 +404,82 @@ X-WR-CALNAME:test
|
|||
PRODID:-//RandomProdID which is not random//EN
|
||||
BEGIN:VTODO
|
||||
UID:randommduid
|
||||
DTSTAMP:20181201T011204
|
||||
DTSTAMP:20181201T011204Z
|
||||
SUMMARY:Todo #1
|
||||
DESCRIPTION:Lorem Ipsum
|
||||
PRIORITY:9
|
||||
LAST-MODIFIED:00010101T000000
|
||||
LAST-MODIFIED:00010101T000000Z
|
||||
END:VTODO
|
||||
END:VCALENDAR`,
|
||||
},
|
||||
{
|
||||
name: "with repeating monthly",
|
||||
args: args{
|
||||
config: &Config{
|
||||
Name: "test",
|
||||
ProdID: "RandomProdID which is not random",
|
||||
},
|
||||
todos: []*Todo{
|
||||
{
|
||||
Summary: "Todo #1",
|
||||
Description: "Lorem Ipsum",
|
||||
UID: "randommduid",
|
||||
Timestamp: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||
RepeatMode: models.TaskRepeatModeMonth,
|
||||
DueDate: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantCaldavtasks: `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
X-PUBLISHED-TTL:PT4H
|
||||
X-WR-CALNAME:test
|
||||
PRODID:-//RandomProdID which is not random//EN
|
||||
BEGIN:VTODO
|
||||
UID:randommduid
|
||||
DTSTAMP:20181201T011204Z
|
||||
SUMMARY:Todo #1
|
||||
DESCRIPTION:Lorem Ipsum
|
||||
DUE:20181201T011204Z
|
||||
RRULE:FREQ=MONTHLY;BYMONTHDAY=01
|
||||
LAST-MODIFIED:00010101T000000Z
|
||||
END:VTODO
|
||||
END:VCALENDAR`,
|
||||
},
|
||||
{
|
||||
name: "with repeat mode default",
|
||||
args: args{
|
||||
config: &Config{
|
||||
Name: "test",
|
||||
ProdID: "RandomProdID which is not random",
|
||||
},
|
||||
todos: []*Todo{
|
||||
{
|
||||
Summary: "Todo #1",
|
||||
Description: "Lorem Ipsum",
|
||||
UID: "randommduid",
|
||||
Timestamp: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||
RepeatMode: models.TaskRepeatModeDefault,
|
||||
DueDate: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||
RepeatAfter: 435,
|
||||
},
|
||||
},
|
||||
},
|
||||
wantCaldavtasks: `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
X-PUBLISHED-TTL:PT4H
|
||||
X-WR-CALNAME:test
|
||||
PRODID:-//RandomProdID which is not random//EN
|
||||
BEGIN:VTODO
|
||||
UID:randommduid
|
||||
DTSTAMP:20181201T011204Z
|
||||
SUMMARY:Todo #1
|
||||
DESCRIPTION:Lorem Ipsum
|
||||
DUE:20181201T011204Z
|
||||
RRULE:FREQ=SECONDLY;INTERVAL=435
|
||||
LAST-MODIFIED:00010101T000000Z
|
||||
END:VTODO
|
||||
END:VCALENDAR`,
|
||||
},
|
||||
|
|
|
@ -49,6 +49,8 @@ func GetCaldavTodosForTasks(list *models.ListWithTasksAndBuckets, listTasks []*m
|
|||
Updated: t.Updated,
|
||||
DueDate: t.DueDate,
|
||||
Duration: duration,
|
||||
RepeatAfter: t.RepeatAfter,
|
||||
RepeatMode: t.RepeatMode,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -225,7 +225,7 @@ var userUpdateCmd = &cobra.Command{
|
|||
u.AvatarProvider = userFlagAvatar
|
||||
}
|
||||
|
||||
_, err := user.UpdateUser(s, u)
|
||||
_, err := user.UpdateUser(s, u, false)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
log.Fatalf("Error updating the user: %s", err)
|
||||
|
@ -299,7 +299,7 @@ var userChangeEnabledCmd = &cobra.Command{
|
|||
u.Status = user.StatusActive
|
||||
}
|
||||
}
|
||||
_, err := user.UpdateUser(s, u)
|
||||
_, err := user.UpdateUser(s, u, false)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
log.Fatalf("Could not enable the user")
|
||||
|
|
|
@ -128,10 +128,6 @@ const (
|
|||
FilesBasePath Key = `files.basepath`
|
||||
FilesMaxSize Key = `files.maxsize`
|
||||
|
||||
MigrationWunderlistEnable Key = `migration.wunderlist.enable`
|
||||
MigrationWunderlistClientID Key = `migration.wunderlist.clientid`
|
||||
MigrationWunderlistClientSecret Key = `migration.wunderlist.clientsecret`
|
||||
MigrationWunderlistRedirectURL Key = `migration.wunderlist.redirecturl`
|
||||
MigrationTodoistEnable Key = `migration.todoist.enable`
|
||||
MigrationTodoistClientID Key = `migration.todoist.clientid`
|
||||
MigrationTodoistClientSecret Key = `migration.todoist.clientsecret`
|
||||
|
@ -369,7 +365,6 @@ func InitDefaultConfig() {
|
|||
CorsOrigins.setDefault([]string{"*"})
|
||||
CorsMaxAge.setDefault(0)
|
||||
// Migration
|
||||
MigrationWunderlistEnable.setDefault(false)
|
||||
MigrationTodoistEnable.setDefault(false)
|
||||
MigrationTrelloEnable.setDefault(false)
|
||||
MigrationMicrosoftTodoEnable.setDefault(false)
|
||||
|
|
|
@ -18,6 +18,7 @@ package db
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
|
||||
|
@ -53,7 +54,35 @@ func Restore(table string, contents []map[string]interface{}) (err error) {
|
|||
return err
|
||||
}
|
||||
|
||||
meta, err := x.DBMetas()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var metaForCurrentTable *schemas.Table
|
||||
for _, m := range meta {
|
||||
if m.Name == table {
|
||||
metaForCurrentTable = m
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if metaForCurrentTable == nil {
|
||||
log.Fatalf("Could not find table definition for table %s", table)
|
||||
}
|
||||
|
||||
for _, content := range contents {
|
||||
for colName, value := range content {
|
||||
// Date fields might get restored as 0001-01-01 from null dates. This can have unintended side-effects like
|
||||
// users being scheduled for deletion after a restore.
|
||||
// To avoid this, we set these dates to nil so that they'll end up as null in the db.
|
||||
col := metaForCurrentTable.GetColumn(colName)
|
||||
strVal, is := value.(string)
|
||||
if is && col.SQLType.IsTime() && (strVal == "" || strings.HasPrefix(strVal, "0001-")) {
|
||||
content[colName] = nil
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := x.Table(table).Insert(content); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -337,6 +337,7 @@
|
|||
bucket_id: 20
|
||||
created: 2018-12-01 01:12:04
|
||||
updated: 2018-12-01 01:12:04
|
||||
due_date: 2018-10-30 22:25:24
|
||||
- id: 37
|
||||
title: 'task #37'
|
||||
done: false
|
||||
|
|
|
@ -83,7 +83,7 @@ func CreateWithMime(f io.Reader, realname string, realsize uint64, a web.Auth, m
|
|||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
file, err = CreateWithMimeAndSession(s, f, realname, realsize, a, mime)
|
||||
file, err = CreateWithMimeAndSession(s, f, realname, realsize, a, mime, true)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return
|
||||
|
@ -91,14 +91,14 @@ func CreateWithMime(f io.Reader, realname string, realsize uint64, a web.Auth, m
|
|||
return
|
||||
}
|
||||
|
||||
func CreateWithMimeAndSession(s *xorm.Session, f io.Reader, realname string, realsize uint64, a web.Auth, mime string) (file *File, err error) {
|
||||
func CreateWithMimeAndSession(s *xorm.Session, f io.Reader, realname string, realsize uint64, a web.Auth, mime string, checkFileSizeLimit bool) (file *File, err error) {
|
||||
// Get and parse the configured file size
|
||||
var maxSize datasize.ByteSize
|
||||
err = maxSize.UnmarshalText([]byte(config.FilesMaxSize.GetString()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if realsize > maxSize.Bytes() {
|
||||
if realsize > maxSize.Bytes() && checkFileSizeLimit {
|
||||
return nil, ErrFileIsTooLarge{Size: realsize}
|
||||
}
|
||||
|
||||
|
|
|
@ -58,6 +58,10 @@ func getClient() (*mail.Client, error) {
|
|||
mail.WithTimeout((config.MailerQueueTimeout.GetDuration() + 3) * time.Second), // 3s more for us to close before mail server timeout
|
||||
}
|
||||
|
||||
if config.MailerForceSSL.GetBool() {
|
||||
opts = append(opts, mail.WithSSL())
|
||||
}
|
||||
|
||||
if config.MailerUsername.GetString() != "" && config.MailerPassword.GetString() != "" {
|
||||
opts = append(opts, mail.WithSMTPAuth(authType))
|
||||
}
|
||||
|
|
|
@ -85,7 +85,7 @@ func getMessage(opts *Opts) *mail.Msg {
|
|||
m.Subject(opts.Subject)
|
||||
|
||||
for _, h := range opts.Headers {
|
||||
m.SetHeader(h.Field, h.Content)
|
||||
m.SetGenHeader(h.Field, h.Content)
|
||||
}
|
||||
|
||||
for name, content := range opts.Embeds {
|
||||
|
|
|
@ -97,7 +97,7 @@ func ExportUserData(s *xorm.Session, u *user.User) (err error) {
|
|||
return err
|
||||
}
|
||||
|
||||
exportFile, err := files.CreateWithMimeAndSession(s, exported, tmpFilename, uint64(stat.Size()), u, "application/zip")
|
||||
exportFile, err := files.CreateWithMimeAndSession(s, exported, tmpFilename, uint64(stat.Size()), u, "application/zip", false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -156,7 +156,7 @@ func GetListsByNamespaceID(s *xorm.Session, nID int64, doer *user.User) (lists [
|
|||
Alias("l").
|
||||
Join("LEFT", []string{"namespaces", "n"}, "l.namespace_id = n.id").
|
||||
Where("l.is_archived = false").
|
||||
Where("n.is_archived = false").
|
||||
Where("n.is_archived = false OR n.is_archived IS NULL").
|
||||
Where("namespace_id = ?", nID).
|
||||
Find(&lists)
|
||||
}
|
||||
|
|
|
@ -37,7 +37,9 @@ func getUndoneOverdueTasks(s *xorm.Session, now time.Time) (usersWithTasks map[i
|
|||
|
||||
var tasks []*Task
|
||||
err = s.
|
||||
Where("due_date is not null and due_date < ?", nextMinute.Add(time.Hour*14).Format(dbTimeFormat)).
|
||||
Where("due_date is not null AND due_date < ? AND lists.is_archived = false AND namespaces.is_archived = false", nextMinute.Add(time.Hour*14).Format(dbTimeFormat)).
|
||||
Join("LEFT", "lists", "lists.id = tasks.list_id").
|
||||
Join("LEFT", "namespaces", "lists.namespace_id = namespaces.id").
|
||||
And("done = false").
|
||||
Find(&tasks)
|
||||
if err != nil {
|
||||
|
@ -138,9 +140,13 @@ func RegisterOverdueReminderCron() {
|
|||
}
|
||||
|
||||
if len(ut.tasks) == 1 {
|
||||
// We know there's only one entry in the map so this is actually O(1) and we can use it to get the
|
||||
// first entry without knowing the key of it.
|
||||
for _, t := range ut.tasks {
|
||||
n = &UndoneTaskOverdueNotification{
|
||||
User: ut.user,
|
||||
Task: ut.tasks[0],
|
||||
Task: t,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -202,7 +202,7 @@ func (rel *TaskRelation) Create(s *xorm.Session, a web.Auth) error {
|
|||
// @Param relation body models.TaskRelation true "The relation object"
|
||||
// @Param taskID path int true "Task ID"
|
||||
// @Param relationKind path string true "The kind of the relation. See the TaskRelation type for more info."
|
||||
// @Param otherTaskID path int true "The id of the other task."
|
||||
// @Param otherTaskId path int true "The id of the other task."
|
||||
// @Success 200 {object} models.Message "The task relation was successfully deleted."
|
||||
// @Failure 400 {object} web.HTTPError "Invalid task relation object provided."
|
||||
// @Failure 404 {object} web.HTTPError "The task relation was not found."
|
||||
|
|
|
@ -817,23 +817,28 @@ func checkBucketLimit(s *xorm.Session, t *Task, bucket *Bucket) (err error) {
|
|||
}
|
||||
|
||||
// Contains all the task logic to figure out what bucket to use for this task.
|
||||
func setTaskBucket(s *xorm.Session, task *Task, originalTask *Task, doCheckBucketLimit bool) (err error) {
|
||||
func setTaskBucket(s *xorm.Session, task *Task, originalTask *Task, doCheckBucketLimit bool) (targetBucket *Bucket, err error) {
|
||||
// Make sure we have a bucket
|
||||
var bucket *Bucket
|
||||
if task.Done && originalTask != nil && !originalTask.Done {
|
||||
bucket, err := getDoneBucketForList(s, task.ListID)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
if bucket != nil {
|
||||
task.BucketID = bucket.ID
|
||||
}
|
||||
}
|
||||
|
||||
if task.BucketID == 0 && originalTask != nil && originalTask.BucketID != 0 {
|
||||
task.BucketID = originalTask.BucketID
|
||||
}
|
||||
|
||||
// Either no bucket was provided or the task was moved between lists
|
||||
if task.BucketID == 0 || (originalTask != nil && task.ListID != 0 && originalTask.ListID != task.ListID) {
|
||||
bucket, err = getDefaultBucket(s, task.ListID)
|
||||
if err != nil {
|
||||
return err
|
||||
return
|
||||
}
|
||||
task.BucketID = bucket.ID
|
||||
}
|
||||
|
@ -841,7 +846,7 @@ func setTaskBucket(s *xorm.Session, task *Task, originalTask *Task, doCheckBucke
|
|||
if bucket == nil {
|
||||
bucket, err = getBucketByID(s, task.BucketID)
|
||||
if err != nil {
|
||||
return err
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -855,7 +860,7 @@ func setTaskBucket(s *xorm.Session, task *Task, originalTask *Task, doCheckBucke
|
|||
// Only check the bucket limit if the task is being moved between buckets, allow reordering the task within a bucket
|
||||
if doCheckBucketLimit {
|
||||
if err := checkBucketLimit(s, task, bucket); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -863,7 +868,7 @@ func setTaskBucket(s *xorm.Session, task *Task, originalTask *Task, doCheckBucke
|
|||
task.Done = true
|
||||
}
|
||||
|
||||
return nil
|
||||
return bucket, nil
|
||||
}
|
||||
|
||||
func calculateDefaultPosition(entityID int64, position float64) float64 {
|
||||
|
@ -932,7 +937,7 @@ func createTask(s *xorm.Session, t *Task, a web.Auth, updateAssignees bool) (err
|
|||
}
|
||||
|
||||
// Get the default bucket and move the task there
|
||||
err = setTaskBucket(s, t, nil, true)
|
||||
_, err = setTaskBucket(s, t, nil, true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -1023,13 +1028,21 @@ func (t *Task) Update(s *xorm.Session, a web.Auth) (err error) {
|
|||
ot.Reminders[i] = r.Reminder
|
||||
}
|
||||
|
||||
// When a repeating task is marked as done, we update all deadlines and reminders and set it as undone
|
||||
updateDone(&ot, t)
|
||||
|
||||
if err := setTaskBucket(s, t, &ot, t.BucketID != ot.BucketID); err != nil {
|
||||
targetBucket, err := setTaskBucket(s, t, &ot, t.BucketID != 0 && t.BucketID != ot.BucketID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If the task was moved into the done bucket and the task has a repeating cycle we should not update
|
||||
// the bucket.
|
||||
if targetBucket.IsDoneBucket && t.RepeatAfter > 0 {
|
||||
t.Done = true // This will trigger the correct re-scheduling of the task (happening in updateDone later)
|
||||
t.BucketID = ot.BucketID
|
||||
}
|
||||
|
||||
// When a repeating task is marked as done, we update all deadlines and reminders and set it as undone
|
||||
updateDone(&ot, t)
|
||||
|
||||
// Update the assignees
|
||||
if err := ot.updateTaskAssignees(s, t.Assignees, a); err != nil {
|
||||
return err
|
||||
|
@ -1406,9 +1419,9 @@ func (t *Task) updateReminders(s *xorm.Session, reminders []time.Time) (err erro
|
|||
}
|
||||
|
||||
// Resolve duplicates and sort them
|
||||
reminderMap := make(map[string]time.Time, len(reminders))
|
||||
reminderMap := make(map[int64]time.Time, len(reminders))
|
||||
for _, reminder := range reminders {
|
||||
reminderMap[reminder.UTC().String()] = reminder
|
||||
reminderMap[reminder.UTC().Unix()] = reminder
|
||||
}
|
||||
|
||||
// Loop through all reminders and add them
|
||||
|
|
|
@ -243,6 +243,33 @@ func TestTask_Update(t *testing.T) {
|
|||
"bucket_id": 3,
|
||||
}, false)
|
||||
})
|
||||
t.Run("moving a repeating task to the done bucket", func(t *testing.T) {
|
||||
db.LoadAndAssertFixtures(t)
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
task := &Task{
|
||||
ID: 28,
|
||||
Title: "test updated",
|
||||
ListID: 1,
|
||||
BucketID: 3, // Bucket 3 is the done bucket
|
||||
RepeatAfter: 3600,
|
||||
}
|
||||
err := task.Update(s, u)
|
||||
assert.NoError(t, err)
|
||||
err = s.Commit()
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, task.Done)
|
||||
assert.Equal(t, int64(1), task.BucketID) // Bucket should not be updated
|
||||
|
||||
db.AssertExists(t, "tasks", map[string]interface{}{
|
||||
"id": 28,
|
||||
"done": false,
|
||||
"title": "test updated",
|
||||
"list_id": 1,
|
||||
"bucket_id": 1,
|
||||
}, false)
|
||||
})
|
||||
t.Run("default bucket when moving a task between lists", func(t *testing.T) {
|
||||
db.LoadAndAssertFixtures(t)
|
||||
s := db.NewSession()
|
||||
|
@ -310,6 +337,7 @@ func TestTask_Update(t *testing.T) {
|
|||
task := &Task{
|
||||
ID: 28,
|
||||
Done: true,
|
||||
RepeatAfter: 3600,
|
||||
}
|
||||
err := task.Update(s, u)
|
||||
assert.NoError(t, err)
|
||||
|
|
|
@ -266,7 +266,7 @@ func getOrCreateUser(s *xorm.Session, cl *claims, issuer, subject string) (u *us
|
|||
Name: u.Name,
|
||||
Issuer: issuer,
|
||||
Subject: subject,
|
||||
})
|
||||
}, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ func unsplashImage(url string, c echo.Context) error {
|
|||
// @Description Get an unsplash image. **Returns json on error.**
|
||||
// @tags list
|
||||
// @Produce octet-stream
|
||||
// @Param thumb path int true "Unsplash Image ID"
|
||||
// @Param image path int true "Unsplash Image ID"
|
||||
// @Security JWTKeyAuth
|
||||
// @Success 200 {} string "The image"
|
||||
// @Failure 404 {object} models.Message "The image does not exist."
|
||||
|
@ -67,7 +67,7 @@ func ProxyUnsplashImage(c echo.Context) error {
|
|||
// @Description Get an unsplash thumbnail image. The thumbnail is cropped to a max width of 200px. **Returns json on error.**
|
||||
// @tags list
|
||||
// @Produce octet-stream
|
||||
// @Param thumb path int true "Unsplash Image ID"
|
||||
// @Param image path int true "Unsplash Image ID"
|
||||
// @Security JWTKeyAuth
|
||||
// @Success 200 {} string "The thumbnail"
|
||||
// @Failure 404 {object} models.Message "The image does not exist."
|
||||
|
|
|
@ -22,6 +22,7 @@ import (
|
|||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
@ -144,11 +145,16 @@ func Restore(filename string) error {
|
|||
// FIXME: There has to be a general way to do this but this works for now.
|
||||
if table == "notifications" {
|
||||
for i := range content {
|
||||
decoded, err := base64.StdEncoding.DecodeString(content[i]["notification"].(string))
|
||||
if err != nil {
|
||||
var decoded []byte
|
||||
decoded, err = base64.StdEncoding.DecodeString(content[i]["notification"].(string))
|
||||
if err != nil && !errors.Is(err, base64.CorruptInputError(0)) {
|
||||
return fmt.Errorf("could not decode notification %s: %w", content[i]["notification"], err)
|
||||
}
|
||||
|
||||
if err != nil && errors.Is(err, base64.CorruptInputError(0)) {
|
||||
decoded = []byte(content[i]["notification"].(string))
|
||||
}
|
||||
|
||||
content[i]["notification"] = string(decoded)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,12 +56,22 @@ func DownloadFileWithHeaders(url string, headers http.Header) (buf *bytes.Buffer
|
|||
|
||||
// DoPost makes a form encoded post request
|
||||
func DoPost(url string, form url.Values) (resp *http.Response, err error) {
|
||||
return DoPostWithHeaders(url, form, map[string]string{})
|
||||
}
|
||||
|
||||
// DoPostWithHeaders does an api request and allows to pass in arbitrary headers
|
||||
func DoPostWithHeaders(url string, form url.Values, headers map[string]string) (resp *http.Response, err error) {
|
||||
req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, url, strings.NewReader(form.Encode()))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
for key, value := range headers {
|
||||
req.Header.Add(key, value)
|
||||
}
|
||||
|
||||
hc := http.Client{}
|
||||
return hc.Do(req)
|
||||
}
|
||||
|
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
@ -27,10 +27,11 @@ import (
|
|||
"time"
|
||||
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/api/pkg/modules/migration"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
|
||||
"github.com/gocarina/gocsv"
|
||||
)
|
||||
|
||||
const timeISO = "2006-01-02T15:04:05-0700"
|
||||
|
@ -39,23 +40,39 @@ type Migrator struct {
|
|||
}
|
||||
|
||||
type tickTickTask struct {
|
||||
FolderName string
|
||||
ListName string
|
||||
Title string
|
||||
Tags []string
|
||||
Content string
|
||||
IsChecklist bool
|
||||
StartDate time.Time
|
||||
DueDate time.Time
|
||||
Reminder time.Duration
|
||||
Repeat string
|
||||
Priority int
|
||||
Status string
|
||||
CreatedTime time.Time
|
||||
CompletedTime time.Time
|
||||
Order float64
|
||||
TaskID int64
|
||||
ParentID int64
|
||||
FolderName string `csv:"Folder Name"`
|
||||
ListName string `csv:"List Name"`
|
||||
Title string `csv:"Title"`
|
||||
TagsList string `csv:"Tags"`
|
||||
Tags []string `csv:"-"`
|
||||
Content string `csv:"Content"`
|
||||
IsChecklistString string `csv:"Is Check list"`
|
||||
IsChecklist bool `csv:"-"`
|
||||
StartDate tickTickTime `csv:"Start Date"`
|
||||
DueDate tickTickTime `csv:"Due Date"`
|
||||
ReminderDuration string `csv:"Reminder"`
|
||||
Reminder time.Duration `csv:"-"`
|
||||
Repeat string `csv:"Repeat"`
|
||||
Priority int `csv:"Priority"`
|
||||
Status string `csv:"Status"`
|
||||
CreatedTime tickTickTime `csv:"Created Time"`
|
||||
CompletedTime tickTickTime `csv:"Completed Time"`
|
||||
Order float64 `csv:"Order"`
|
||||
TaskID int64 `csv:"taskId"`
|
||||
ParentID int64 `csv:"parentId"`
|
||||
}
|
||||
|
||||
type tickTickTime struct {
|
||||
time.Time
|
||||
}
|
||||
|
||||
func (date *tickTickTime) UnmarshalCSV(csv string) (err error) {
|
||||
date.Time = time.Time{}
|
||||
if csv == "" {
|
||||
return nil
|
||||
}
|
||||
date.Time, err = time.Parse(timeISO, csv)
|
||||
return err
|
||||
}
|
||||
|
||||
// Copied from https://stackoverflow.com/a/57617885
|
||||
|
@ -119,19 +136,22 @@ func convertTickTickToVikunja(tasks []*tickTickTask) (result []*models.Namespace
|
|||
ID: t.TaskID,
|
||||
Title: t.Title,
|
||||
Description: t.Content,
|
||||
StartDate: t.StartDate,
|
||||
EndDate: t.DueDate,
|
||||
DueDate: t.DueDate,
|
||||
Reminders: []time.Time{
|
||||
t.DueDate.Add(t.Reminder * -1),
|
||||
},
|
||||
StartDate: t.StartDate.Time,
|
||||
EndDate: t.DueDate.Time,
|
||||
DueDate: t.DueDate.Time,
|
||||
Done: t.Status == "1",
|
||||
DoneAt: t.CompletedTime,
|
||||
DoneAt: t.CompletedTime.Time,
|
||||
Position: t.Order,
|
||||
Labels: labels,
|
||||
},
|
||||
}
|
||||
|
||||
if !t.DueDate.IsZero() && t.Reminder > 0 {
|
||||
task.Task.Reminders = []time.Time{
|
||||
t.DueDate.Add(t.Reminder * -1),
|
||||
}
|
||||
}
|
||||
|
||||
if t.ParentID != 0 {
|
||||
task.RelatedTasks = map[models.RelationKind][]*models.Task{
|
||||
models.RelationKindParenttask: {{ID: t.ParentID}},
|
||||
|
@ -165,6 +185,22 @@ func (m *Migrator) Name() string {
|
|||
return "ticktick"
|
||||
}
|
||||
|
||||
func newLineSkipDecoder(r io.Reader, linesToSkip int) gocsv.SimpleDecoder {
|
||||
reader := csv.NewReader(r)
|
||||
// reader.FieldsPerRecord = -1
|
||||
for i := 0; i < linesToSkip; i++ {
|
||||
_, err := reader.Read()
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
log.Debugf("[TickTick Migration] CSV parse error: %s", err)
|
||||
}
|
||||
}
|
||||
reader.FieldsPerRecord = 0
|
||||
return gocsv.NewSimpleDecoderFromCSVReader(reader)
|
||||
}
|
||||
|
||||
// Migrate takes a ticktick export, parses it and imports everything in it into Vikunja.
|
||||
// @Summary Import all lists, tasks etc. from a TickTick backup export
|
||||
// @Description Imports all projects, tasks, notes, reminders, subtasks and files from a TickTick backup export into Vikunja.
|
||||
|
@ -178,85 +214,26 @@ func (m *Migrator) Name() string {
|
|||
// @Router /migration/ticktick/migrate [post]
|
||||
func (m *Migrator) Migrate(user *user.User, file io.ReaderAt, size int64) error {
|
||||
fr := io.NewSectionReader(file, 0, size)
|
||||
r := csv.NewReader(fr)
|
||||
//r := csv.NewReader(fr)
|
||||
|
||||
allTasks := []*tickTickTask{}
|
||||
line := 0
|
||||
for {
|
||||
|
||||
record, err := r.Read()
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
log.Debugf("[TickTick Migration] CSV parse error: %s", err)
|
||||
}
|
||||
|
||||
line++
|
||||
if line <= 4 {
|
||||
continue
|
||||
}
|
||||
|
||||
priority, err := strconv.Atoi(record[10])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
order, err := strconv.ParseFloat(record[14], 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
taskID, err := strconv.ParseInt(record[21], 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
parentID, err := strconv.ParseInt(record[21], 10, 64)
|
||||
decode := newLineSkipDecoder(fr, 3)
|
||||
err := gocsv.UnmarshalDecoder(decode, &allTasks)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reminder := parseDuration(record[8])
|
||||
|
||||
t := &tickTickTask{
|
||||
ListName: record[1],
|
||||
Title: record[2],
|
||||
Tags: strings.Split(record[3], ", "),
|
||||
Content: record[4],
|
||||
IsChecklist: record[5] == "Y",
|
||||
Reminder: reminder,
|
||||
Repeat: record[9],
|
||||
Priority: priority,
|
||||
Status: record[11],
|
||||
Order: order,
|
||||
TaskID: taskID,
|
||||
ParentID: parentID,
|
||||
for _, task := range allTasks {
|
||||
if task.IsChecklistString == "Y" {
|
||||
task.IsChecklist = true
|
||||
}
|
||||
|
||||
if record[6] != "" {
|
||||
t.StartDate, err = time.Parse(timeISO, record[6])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if record[7] != "" {
|
||||
t.DueDate, err = time.Parse(timeISO, record[7])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if record[12] != "" {
|
||||
t.StartDate, err = time.Parse(timeISO, record[12])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if record[13] != "" {
|
||||
t.CompletedTime, err = time.Parse(timeISO, record[13])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
reminder := parseDuration(task.ReminderDuration)
|
||||
if reminder > 0 {
|
||||
task.Reminder = reminder
|
||||
}
|
||||
|
||||
allTasks = append(allTasks, t)
|
||||
task.Tags = strings.Split(task.TagsList, ", ")
|
||||
}
|
||||
|
||||
vikunjaTasks := convertTickTickToVikunja(allTasks)
|
||||
|
|
|
@ -26,12 +26,15 @@ import (
|
|||
)
|
||||
|
||||
func TestConvertTicktickTasksToVikunja(t *testing.T) {
|
||||
time1, err := time.Parse(time.RFC3339Nano, "2022-11-18T03:00:00.4770000Z")
|
||||
t1, err := time.Parse(time.RFC3339Nano, "2022-11-18T03:00:00.4770000Z")
|
||||
require.NoError(t, err)
|
||||
time2, err := time.Parse(time.RFC3339Nano, "2022-12-18T03:00:00.4770000Z")
|
||||
time1 := tickTickTime{Time: t1}
|
||||
t2, err := time.Parse(time.RFC3339Nano, "2022-12-18T03:00:00.4770000Z")
|
||||
require.NoError(t, err)
|
||||
time3, err := time.Parse(time.RFC3339Nano, "2022-12-10T03:00:00.4770000Z")
|
||||
time2 := tickTickTime{Time: t2}
|
||||
t3, err := time.Parse(time.RFC3339Nano, "2022-12-10T03:00:00.4770000Z")
|
||||
require.NoError(t, err)
|
||||
time3 := tickTickTime{Time: t3}
|
||||
duration, err := time.ParseDuration("24h")
|
||||
require.NoError(t, err)
|
||||
|
||||
|
@ -91,9 +94,9 @@ func TestConvertTicktickTasksToVikunja(t *testing.T) {
|
|||
|
||||
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[0].Title, tickTickTasks[0].Title)
|
||||
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[0].Description, tickTickTasks[0].Content)
|
||||
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[0].StartDate, tickTickTasks[0].StartDate)
|
||||
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[0].EndDate, tickTickTasks[0].DueDate)
|
||||
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[0].DueDate, tickTickTasks[0].DueDate)
|
||||
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[0].StartDate, tickTickTasks[0].StartDate.Time)
|
||||
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[0].EndDate, tickTickTasks[0].DueDate.Time)
|
||||
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[0].DueDate, tickTickTasks[0].DueDate.Time)
|
||||
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[0].Labels, []*models.Label{
|
||||
{Title: "label1"},
|
||||
{Title: "label2"},
|
||||
|
@ -105,7 +108,7 @@ func TestConvertTicktickTasksToVikunja(t *testing.T) {
|
|||
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[1].Title, tickTickTasks[1].Title)
|
||||
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[1].Position, tickTickTasks[1].Order)
|
||||
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[1].Done, true)
|
||||
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[1].DoneAt, tickTickTasks[1].CompletedTime)
|
||||
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[1].DoneAt, tickTickTasks[1].CompletedTime.Time)
|
||||
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[1].RelatedTasks, models.RelatedTaskMap{
|
||||
models.RelationKindParenttask: []*models.Task{
|
||||
{
|
||||
|
@ -116,9 +119,9 @@ func TestConvertTicktickTasksToVikunja(t *testing.T) {
|
|||
|
||||
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[2].Title, tickTickTasks[2].Title)
|
||||
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[2].Description, tickTickTasks[2].Content)
|
||||
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[2].StartDate, tickTickTasks[2].StartDate)
|
||||
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[2].EndDate, tickTickTasks[2].DueDate)
|
||||
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[2].DueDate, tickTickTasks[2].DueDate)
|
||||
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[2].StartDate, tickTickTasks[2].StartDate.Time)
|
||||
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[2].EndDate, tickTickTasks[2].DueDate.Time)
|
||||
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[2].DueDate, tickTickTasks[2].DueDate.Time)
|
||||
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[2].Labels, []*models.Label{
|
||||
{Title: "label1"},
|
||||
{Title: "label2"},
|
||||
|
|
|
@ -20,6 +20,7 @@ import (
|
|||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
@ -45,28 +46,25 @@ type apiTokenResponse struct {
|
|||
}
|
||||
|
||||
type label struct {
|
||||
ID int64 `json:"id"`
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Color int64 `json:"color"`
|
||||
Color string `json:"color"`
|
||||
ItemOrder int64 `json:"item_order"`
|
||||
IsDeleted int64 `json:"is_deleted"`
|
||||
IsFavorite int64 `json:"is_favorite"`
|
||||
IsDeleted bool `json:"is_deleted"`
|
||||
IsFavorite bool `json:"is_favorite"`
|
||||
}
|
||||
|
||||
type project struct {
|
||||
ID int64 `json:"id"`
|
||||
LegacyID int64 `json:"legacy_id"`
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Color int64 `json:"color"`
|
||||
ParentID int64 `json:"parent_id"`
|
||||
Color string `json:"color"`
|
||||
ParentID string `json:"parent_id"`
|
||||
ChildOrder int64 `json:"child_order"`
|
||||
Collapsed int64 `json:"collapsed"`
|
||||
Collapsed bool `json:"collapsed"`
|
||||
Shared bool `json:"shared"`
|
||||
LegacyParentID int64 `json:"legacy_parent_id"`
|
||||
SyncID int64 `json:"sync_id"`
|
||||
IsDeleted int64 `json:"is_deleted"`
|
||||
IsArchived int64 `json:"is_archived"`
|
||||
IsFavorite int64 `json:"is_favorite"`
|
||||
IsDeleted bool `json:"is_deleted"`
|
||||
IsArchived bool `json:"is_archived"`
|
||||
IsFavorite bool `json:"is_favorite"`
|
||||
}
|
||||
|
||||
type dueDate struct {
|
||||
|
@ -78,31 +76,25 @@ type dueDate struct {
|
|||
}
|
||||
|
||||
type item struct {
|
||||
ID int64 `json:"id"`
|
||||
LegacyID int64 `json:"legacy_id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
ProjectID int64 `json:"project_id"`
|
||||
LegacyProjectID int64 `json:"legacy_project_id"`
|
||||
ID string `json:"id"`
|
||||
UserID string `json:"user_id"`
|
||||
ProjectID string `json:"project_id"`
|
||||
Content string `json:"content"`
|
||||
Priority int64 `json:"priority"`
|
||||
Due *dueDate `json:"due"`
|
||||
ParentID int64 `json:"parent_id"`
|
||||
LegacyParentID int64 `json:"legacy_parent_id"`
|
||||
ParentID string `json:"parent_id"`
|
||||
ChildOrder int64 `json:"child_order"`
|
||||
SectionID int64 `json:"section_id"`
|
||||
DayOrder int64 `json:"day_order"`
|
||||
Collapsed int64 `json:"collapsed"`
|
||||
SectionID string `json:"section_id"`
|
||||
Children interface{} `json:"children"`
|
||||
Labels []int64 `json:"labels"`
|
||||
AddedByUID int64 `json:"added_by_uid"`
|
||||
AssignedByUID int64 `json:"assigned_by_uid"`
|
||||
ResponsibleUID int64 `json:"responsible_uid"`
|
||||
Checked int64 `json:"checked"`
|
||||
InHistory int64 `json:"in_history"`
|
||||
IsDeleted int64 `json:"is_deleted"`
|
||||
DateAdded time.Time `json:"date_added"`
|
||||
Labels []string `json:"labels"`
|
||||
AddedByUID string `json:"added_by_uid"`
|
||||
AssignedByUID string `json:"assigned_by_uid"`
|
||||
ResponsibleUID string `json:"responsible_uid"`
|
||||
Checked bool `json:"checked"`
|
||||
IsDeleted bool `json:"is_deleted"`
|
||||
DateAdded time.Time `json:"added_at"`
|
||||
HasMoreNotes bool `json:"has_more_notes"`
|
||||
DateCompleted time.Time `json:"date_completed"`
|
||||
DateCompleted time.Time `json:"completed_at"`
|
||||
}
|
||||
|
||||
type itemWrapper struct {
|
||||
|
@ -110,12 +102,11 @@ type itemWrapper struct {
|
|||
}
|
||||
|
||||
type doneItem struct {
|
||||
CompletedDate time.Time `json:"completed_date"`
|
||||
CompletedDate time.Time `json:"completed_at"`
|
||||
Content string `json:"content"`
|
||||
ID int64 `json:"id"`
|
||||
ProjectID int64 `json:"project_id"`
|
||||
TaskID int64 `json:"task_id"`
|
||||
UserID int `json:"user_id"`
|
||||
ID string `json:"id"`
|
||||
ProjectID string `json:"project_id"`
|
||||
TaskID string `json:"task_id"`
|
||||
}
|
||||
|
||||
type doneItemSync struct {
|
||||
|
@ -132,18 +123,14 @@ type fileAttachment struct {
|
|||
}
|
||||
|
||||
type note struct {
|
||||
ID int64 `json:"id"`
|
||||
LegacyID int64 `json:"legacy_id"`
|
||||
ID string `json:"id"`
|
||||
PostedUID int64 `json:"posted_uid"`
|
||||
ProjectID int64 `json:"project_id"`
|
||||
LegacyProjectID int64 `json:"legacy_project_id"`
|
||||
ItemID int64 `json:"item_id"`
|
||||
LegacyItemID int64 `json:"legacy_item_id"`
|
||||
ProjectID string `json:"project_id"`
|
||||
ItemID string `json:"item_id"`
|
||||
Content string `json:"content"`
|
||||
FileAttachment *fileAttachment `json:"file_attachment"`
|
||||
UidsToNotify []int64 `json:"uids_to_notify"`
|
||||
IsDeleted int64 `json:"is_deleted"`
|
||||
Posted time.Time `json:"posted"`
|
||||
IsDeleted bool `json:"is_deleted"`
|
||||
Posted time.Time `json:"posted_at"`
|
||||
}
|
||||
|
||||
type projectNote struct {
|
||||
|
@ -153,15 +140,13 @@ type projectNote struct {
|
|||
IsDeleted int64 `json:"is_deleted"`
|
||||
Posted time.Time `json:"posted"`
|
||||
PostedUID int64 `json:"posted_uid"`
|
||||
ProjectID int64 `json:"project_id"`
|
||||
ProjectID string `json:"project_id"`
|
||||
UidsToNotify []int64 `json:"uids_to_notify"`
|
||||
}
|
||||
|
||||
type reminder struct {
|
||||
ID int64 `json:"id"`
|
||||
NotifyUID int64 `json:"notify_uid"`
|
||||
ItemID int64 `json:"item_id"`
|
||||
Service string `json:"service"`
|
||||
ID string `json:"id"`
|
||||
ItemID string `json:"item_id"`
|
||||
Type string `json:"type"`
|
||||
Due *dueDate `json:"due"`
|
||||
MmOffset int64 `json:"mm_offset"`
|
||||
|
@ -169,11 +154,11 @@ type reminder struct {
|
|||
}
|
||||
|
||||
type section struct {
|
||||
ID int64 `json:"id"`
|
||||
DateAdded time.Time `json:"date_added"`
|
||||
ID string `json:"id"`
|
||||
DateAdded time.Time `json:"added_at"`
|
||||
IsDeleted bool `json:"is_deleted"`
|
||||
Name string `json:"name"`
|
||||
ProjectID int64 `json:"project_id"`
|
||||
ProjectID string `json:"project_id"`
|
||||
SectionOrder int64 `json:"section_order"`
|
||||
}
|
||||
|
||||
|
@ -187,32 +172,32 @@ type sync struct {
|
|||
Sections []*section `json:"sections"`
|
||||
}
|
||||
|
||||
var todoistColors = map[int64]string{}
|
||||
var todoistColors = map[string]string{}
|
||||
|
||||
func init() {
|
||||
todoistColors = make(map[int64]string, 19)
|
||||
// The todoists colors are static, taken from https://developer.todoist.com/sync/v8/#colors
|
||||
todoistColors = map[int64]string{
|
||||
30: "b8256f",
|
||||
31: "db4035",
|
||||
32: "ff9933",
|
||||
33: "fad000",
|
||||
34: "afb83b",
|
||||
35: "7ecc49",
|
||||
36: "299438",
|
||||
37: "6accbc",
|
||||
38: "158fad",
|
||||
39: "14aaf5",
|
||||
40: "96c3eb",
|
||||
41: "4073ff",
|
||||
42: "884dff",
|
||||
43: "af38eb",
|
||||
44: "eb96eb",
|
||||
45: "e05194",
|
||||
46: "ff8d85",
|
||||
47: "808080",
|
||||
48: "b8b8b8",
|
||||
49: "ccac93",
|
||||
todoistColors = make(map[string]string, 19)
|
||||
// The todoists colors are static, taken from https://developer.todoist.com/guides/#colors
|
||||
todoistColors = map[string]string{
|
||||
"berry_red": "b8256f",
|
||||
"red": "db4035",
|
||||
"orange": "ff9933",
|
||||
"yellow": "fad000",
|
||||
"olive_green": "afb83b",
|
||||
"lime_green": "7ecc49",
|
||||
"green": "299438",
|
||||
"mint_green": "6accbc",
|
||||
"teal": "158fad",
|
||||
"sky_blue": "14aaf5",
|
||||
"light_blue": "96c3eb",
|
||||
"blue": "4073ff",
|
||||
"grape": "884dff",
|
||||
"violet": "af38eb",
|
||||
"lavender": "eb96eb",
|
||||
"magenta": "e05194",
|
||||
"salmon": "ff8d85",
|
||||
"charcoal": "808080",
|
||||
"grey": "b8b8b8",
|
||||
"taupe": "ccac93",
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -266,7 +251,7 @@ func parseDate(dateString string) (date time.Time, err error) {
|
|||
return date, err
|
||||
}
|
||||
|
||||
func convertTodoistToVikunja(sync *sync, doneItems map[int64]*doneItem) (fullVikunjaHierachie []*models.NamespaceWithListsAndTasks, err error) {
|
||||
func convertTodoistToVikunja(sync *sync, doneItems map[string]*doneItem) (fullVikunjaHierachie []*models.NamespaceWithListsAndTasks, err error) {
|
||||
|
||||
newNamespace := &models.NamespaceWithListsAndTasks{
|
||||
Namespace: models.Namespace{
|
||||
|
@ -275,20 +260,22 @@ func convertTodoistToVikunja(sync *sync, doneItems map[int64]*doneItem) (fullVik
|
|||
}
|
||||
|
||||
// A map for all vikunja lists with the project id they're coming from as key
|
||||
lists := make(map[int64]*models.ListWithTasksAndBuckets, len(sync.Projects))
|
||||
lists := make(map[string]*models.ListWithTasksAndBuckets, len(sync.Projects))
|
||||
|
||||
// A map for all vikunja tasks with the todoist task id as key to find them easily and add more data
|
||||
tasks := make(map[int64]*models.TaskWithComments, len(sync.Items))
|
||||
tasks := make(map[string]*models.TaskWithComments, len(sync.Items))
|
||||
|
||||
// A map for all vikunja labels with the todoist id as key to find them easier
|
||||
labels := make(map[int64]*models.Label, len(sync.Labels))
|
||||
labels := make(map[string]*models.Label, len(sync.Labels))
|
||||
|
||||
sections := make(map[string]int64)
|
||||
|
||||
for _, p := range sync.Projects {
|
||||
list := &models.ListWithTasksAndBuckets{
|
||||
List: models.List{
|
||||
Title: p.Name,
|
||||
HexColor: todoistColors[p.Color],
|
||||
IsArchived: p.IsArchived == 1,
|
||||
IsArchived: p.IsArchived,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -301,32 +288,40 @@ func convertTodoistToVikunja(sync *sync, doneItems map[int64]*doneItem) (fullVik
|
|||
return sync.Sections[i].SectionOrder < sync.Sections[j].SectionOrder
|
||||
})
|
||||
|
||||
var fabricatedSectionID int64 = 1
|
||||
for _, section := range sync.Sections {
|
||||
if section.IsDeleted || section.ProjectID == 0 {
|
||||
if section.IsDeleted || section.ProjectID == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
lists[section.ProjectID].Buckets = append(lists[section.ProjectID].Buckets, &models.Bucket{
|
||||
ID: section.ID,
|
||||
ID: fabricatedSectionID,
|
||||
Title: section.Name,
|
||||
Created: section.DateAdded,
|
||||
})
|
||||
sections[section.ID] = fabricatedSectionID
|
||||
}
|
||||
|
||||
for _, label := range sync.Labels {
|
||||
labels[label.ID] = &models.Label{
|
||||
labels[label.Name] = &models.Label{
|
||||
Title: label.Name,
|
||||
HexColor: todoistColors[label.Color],
|
||||
}
|
||||
}
|
||||
|
||||
for _, i := range sync.Items {
|
||||
|
||||
if i == nil {
|
||||
// This should never happen
|
||||
continue
|
||||
}
|
||||
|
||||
task := &models.TaskWithComments{
|
||||
Task: models.Task{
|
||||
Title: i.Content,
|
||||
Created: i.DateAdded.In(config.GetTimeZone()),
|
||||
Done: i.Checked == 1,
|
||||
BucketID: i.SectionID,
|
||||
Done: i.Checked,
|
||||
BucketID: sections[i.SectionID],
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -357,29 +352,31 @@ func convertTodoistToVikunja(sync *sync, doneItems map[int64]*doneItem) (fullVik
|
|||
}
|
||||
|
||||
// Put all labels together from earlier
|
||||
for _, lID := range i.Labels {
|
||||
task.Labels = append(task.Labels, labels[lID])
|
||||
for _, lName := range i.Labels {
|
||||
task.Labels = append(task.Labels, labels[lName])
|
||||
}
|
||||
|
||||
tasks[i.ID] = task
|
||||
|
||||
if _, exists := lists[i.ProjectID]; !exists {
|
||||
log.Debugf("[Todoist Migration] Tried to put item %d in project %d but the project does not exist", i.ID, i.ProjectID)
|
||||
log.Debugf("[Todoist Migration] Tried to put item %s in project %s but the project does not exist", i.ID, i.ProjectID)
|
||||
continue
|
||||
}
|
||||
|
||||
lists[i.ProjectID].Tasks = append(lists[i.ProjectID].Tasks, task)
|
||||
|
||||
fabricatedSectionID++
|
||||
}
|
||||
|
||||
// If the parenId of a task is not 0, create a task relation
|
||||
// We're looping again here to make sure we have seem all tasks before and have them in our map
|
||||
for _, i := range sync.Items {
|
||||
if i.ParentID == 0 {
|
||||
if i.ParentID == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, exists := tasks[i.ParentID]; !exists {
|
||||
log.Debugf("[Todoist Migration] Could not find task %d in tasks map while trying to get resolve subtasks for task %d", i.ParentID, i.ID)
|
||||
log.Debugf("[Todoist Migration] Could not find task %s in tasks map while trying to get resolve subtasks for task %s", i.ParentID, i.ID)
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -407,7 +404,7 @@ func convertTodoistToVikunja(sync *sync, doneItems map[int64]*doneItem) (fullVik
|
|||
// FIXME: Should be comments
|
||||
for _, n := range sync.Notes {
|
||||
if _, exists := tasks[n.ItemID]; !exists {
|
||||
log.Debugf("[Todoist Migration] Could not find task %d for note %d", n.ItemID, n.ID)
|
||||
log.Debugf("[Todoist Migration] Could not find task %s for note %s", n.ItemID, n.ID)
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -460,7 +457,7 @@ func convertTodoistToVikunja(sync *sync, doneItems map[int64]*doneItem) (fullVik
|
|||
}
|
||||
|
||||
if _, exists := tasks[r.ItemID]; !exists {
|
||||
log.Debugf("Could not find task %d for reminder %d while trying to resolve reminders", r.ItemID, r.ID)
|
||||
log.Debugf("Could not find task %s for reminder %s while trying to resolve reminders", r.ItemID, r.ID)
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -533,11 +530,14 @@ func (m *Migration) Migrate(u *user.User) (err error) {
|
|||
|
||||
// Get everything with the sync api
|
||||
form := url.Values{
|
||||
"token": []string{token},
|
||||
"sync_token": []string{"*"},
|
||||
"resource_types": []string{"[\"all\"]"},
|
||||
}
|
||||
resp, err := migration.DoPost("https://api.todoist.com/sync/v8/sync", form)
|
||||
bearerHeader := map[string]string{
|
||||
"Authorization": "Bearer " + token,
|
||||
}
|
||||
|
||||
resp, err := migration.DoPostWithHeaders("https://api.todoist.com/sync/v9/sync", form, bearerHeader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -553,10 +553,10 @@ func (m *Migration) Migrate(u *user.User) (err error) {
|
|||
|
||||
// Get all done tasks and projects
|
||||
offset := 0
|
||||
doneItems := make(map[int64]*doneItem)
|
||||
doneItems := make(map[string]*doneItem)
|
||||
|
||||
for {
|
||||
resp, err = migration.DoPost("https://api.todoist.com/sync/v8/completed/get_all?limit=200&offset="+strconv.Itoa(offset), form)
|
||||
resp, err = migration.DoPostWithHeaders("https://api.todoist.com/sync/v9/completed/get_all?limit=200&offset="+strconv.Itoa(offset), form, bearerHeader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -580,21 +580,26 @@ func (m *Migration) Migrate(u *user.User) (err error) {
|
|||
doneItems[i.TaskID] = i
|
||||
|
||||
// need to get done item data
|
||||
resp, err = migration.DoPost("https://api.todoist.com/sync/v8/items/get", url.Values{
|
||||
"token": []string{token},
|
||||
"item_id": []string{strconv.FormatInt(i.TaskID, 10)},
|
||||
})
|
||||
resp, err = migration.DoPostWithHeaders("https://api.todoist.com/sync/v9/items/get", url.Values{
|
||||
"item_id": []string{i.TaskID},
|
||||
}, bearerHeader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode == http.StatusNotFound {
|
||||
// Done items of deleted projects may show up here but since the project is already deleted
|
||||
// we can't show them individually and the api returns a 404.
|
||||
continue
|
||||
}
|
||||
|
||||
doneI := &itemWrapper{}
|
||||
err = json.NewDecoder(resp.Body).Decode(doneI)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
log.Debugf("[Todoist Migration] Retrieved full task data for done task %d", i.TaskID)
|
||||
log.Debugf("[Todoist Migration] Retrieved full task data for done task %s", i.ID)
|
||||
syncResponse.Items = append(syncResponse.Items, doneI.Item)
|
||||
}
|
||||
|
||||
|
@ -609,7 +614,7 @@ func (m *Migration) Migrate(u *user.User) (err error) {
|
|||
log.Debugf("[Todoist Migration] Getting archived projects for user %d", u.ID)
|
||||
|
||||
// Get all archived projects
|
||||
resp, err = migration.DoPost("https://api.todoist.com/sync/v8/projects/get_archived", form)
|
||||
resp, err = migration.DoPostWithHeaders("https://api.todoist.com/sync/v9/projects/get_archived", form, bearerHeader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -625,10 +630,9 @@ func (m *Migration) Migrate(u *user.User) (err error) {
|
|||
log.Debugf("[Todoist Migration] Got %d archived projects for user %d", len(archivedProjects), u.ID)
|
||||
log.Debugf("[Todoist Migration] Getting data for archived projects for user %d", u.ID)
|
||||
|
||||
// Project data is not included in the regular sync for archived projects so we need to get all of those by hand
|
||||
//https://api.todoist.com/sync/v8/projects/get_data\?project_id\=2269005399
|
||||
// Project data is not included in the regular sync for archived projects, so we need to get all of those by hand
|
||||
for _, p := range archivedProjects {
|
||||
resp, err = migration.DoPost("https://api.todoist.com/sync/v8/projects/get_data?project_id="+strconv.FormatInt(p.ID, 10), form)
|
||||
resp, err = migration.DoPostWithHeaders("https://api.todoist.com/sync/v9/projects/get_data?project_id="+p.ID, form, bearerHeader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -18,7 +18,6 @@ package todoist
|
|||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
@ -47,33 +46,32 @@ func TestConvertTodoistToVikunja(t *testing.T) {
|
|||
dueTimeWithTime = dueTimeWithTime.In(config.GetTimeZone())
|
||||
nilTime, err := time.Parse(time.RFC3339Nano, "0001-01-01T00:00:00Z")
|
||||
assert.NoError(t, err)
|
||||
exampleFile, err := os.ReadFile(config.ServiceRootpath.GetString() + "/pkg/modules/migration/wunderlist/testimage.jpg")
|
||||
exampleFile, err := os.ReadFile(config.ServiceRootpath.GetString() + "/pkg/modules/migration/testimage.jpg")
|
||||
assert.NoError(t, err)
|
||||
|
||||
makeTestItem := func(id, projectId int64, hasDueDate, hasLabels, done bool) *item {
|
||||
makeTestItem := func(id, projectId string, hasDueDate, hasLabels, done bool) *item {
|
||||
item := &item{
|
||||
ID: id,
|
||||
UserID: 1855589,
|
||||
UserID: "1855589",
|
||||
ProjectID: projectId,
|
||||
Content: "Task" + strconv.FormatInt(id, 10),
|
||||
Content: "Task" + id,
|
||||
Priority: 1,
|
||||
ParentID: 0,
|
||||
ChildOrder: 1,
|
||||
DateAdded: time1,
|
||||
DateCompleted: nilTime,
|
||||
}
|
||||
|
||||
if done {
|
||||
item.Checked = 1
|
||||
item.Checked = true
|
||||
item.DateCompleted = time3
|
||||
}
|
||||
|
||||
if hasLabels {
|
||||
item.Labels = []int64{
|
||||
80000,
|
||||
80001,
|
||||
80002,
|
||||
80003,
|
||||
item.Labels = []string{
|
||||
"Label1",
|
||||
"Label2",
|
||||
"Label3",
|
||||
"Label4",
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -91,163 +89,163 @@ func TestConvertTodoistToVikunja(t *testing.T) {
|
|||
testSync := &sync{
|
||||
Projects: []*project{
|
||||
{
|
||||
ID: 396936926,
|
||||
ID: "396936926",
|
||||
Name: "Project1",
|
||||
Color: 30,
|
||||
Color: "berry_red",
|
||||
ChildOrder: 1,
|
||||
Collapsed: 0,
|
||||
Collapsed: false,
|
||||
Shared: false,
|
||||
IsDeleted: 0,
|
||||
IsArchived: 0,
|
||||
IsFavorite: 0,
|
||||
IsDeleted: false,
|
||||
IsArchived: false,
|
||||
IsFavorite: false,
|
||||
},
|
||||
{
|
||||
ID: 396936927,
|
||||
ID: "396936927",
|
||||
Name: "Project2",
|
||||
Color: 37,
|
||||
Color: "mint_green",
|
||||
ChildOrder: 1,
|
||||
Collapsed: 0,
|
||||
Collapsed: false,
|
||||
Shared: false,
|
||||
IsDeleted: 0,
|
||||
IsArchived: 0,
|
||||
IsFavorite: 0,
|
||||
IsDeleted: false,
|
||||
IsArchived: false,
|
||||
IsFavorite: false,
|
||||
},
|
||||
{
|
||||
ID: 396936928,
|
||||
ID: "396936928",
|
||||
Name: "Project3 - Archived",
|
||||
Color: 37,
|
||||
Color: "mint_green",
|
||||
ChildOrder: 1,
|
||||
Collapsed: 0,
|
||||
Collapsed: false,
|
||||
Shared: false,
|
||||
IsDeleted: 0,
|
||||
IsArchived: 1,
|
||||
IsFavorite: 0,
|
||||
IsDeleted: false,
|
||||
IsArchived: true,
|
||||
IsFavorite: false,
|
||||
},
|
||||
},
|
||||
Items: []*item{
|
||||
makeTestItem(400000000, 396936926, false, false, false),
|
||||
makeTestItem(400000001, 396936926, false, false, false),
|
||||
makeTestItem(400000002, 396936926, false, false, false),
|
||||
makeTestItem(400000003, 396936926, true, true, true),
|
||||
makeTestItem(400000004, 396936926, false, true, false),
|
||||
makeTestItem(400000005, 396936926, true, false, true),
|
||||
makeTestItem(400000006, 396936926, true, false, true),
|
||||
makeTestItem("400000000", "396936926", false, false, false),
|
||||
makeTestItem("400000001", "396936926", false, false, false),
|
||||
makeTestItem("400000002", "396936926", false, false, false),
|
||||
makeTestItem("400000003", "396936926", true, true, true),
|
||||
makeTestItem("400000004", "396936926", false, true, false),
|
||||
makeTestItem("400000005", "396936926", true, false, true),
|
||||
makeTestItem("400000006", "396936926", true, false, true),
|
||||
{
|
||||
ID: 400000110,
|
||||
UserID: 1855589,
|
||||
ProjectID: 396936926,
|
||||
ID: "400000110",
|
||||
UserID: "1855589",
|
||||
ProjectID: "396936926",
|
||||
Content: "Task with parent",
|
||||
Priority: 2,
|
||||
ParentID: 400000006,
|
||||
ParentID: "400000006",
|
||||
ChildOrder: 1,
|
||||
Checked: 0,
|
||||
Checked: false,
|
||||
DateAdded: time1,
|
||||
},
|
||||
{
|
||||
ID: 400000106,
|
||||
UserID: 1855589,
|
||||
ProjectID: 396936926,
|
||||
ID: "400000106",
|
||||
UserID: "1855589",
|
||||
ProjectID: "396936926",
|
||||
Content: "Task400000106",
|
||||
Priority: 1,
|
||||
ParentID: 0,
|
||||
ParentID: "",
|
||||
ChildOrder: 1,
|
||||
DateAdded: time1,
|
||||
Checked: 1,
|
||||
Checked: true,
|
||||
DateCompleted: time3,
|
||||
Due: &dueDate{
|
||||
Date: "2021-01-31T19:00:00Z",
|
||||
Timezone: nil,
|
||||
IsRecurring: false,
|
||||
},
|
||||
Labels: []int64{
|
||||
80000,
|
||||
80001,
|
||||
80002,
|
||||
80003,
|
||||
Labels: []string{
|
||||
"Label1",
|
||||
"Label2",
|
||||
"Label3",
|
||||
"Label4",
|
||||
},
|
||||
},
|
||||
makeTestItem(400000107, 396936926, false, false, true),
|
||||
makeTestItem(400000108, 396936926, false, false, true),
|
||||
makeTestItem("400000107", "396936926", false, false, true),
|
||||
makeTestItem("400000108", "396936926", false, false, true),
|
||||
{
|
||||
ID: 400000109,
|
||||
UserID: 1855589,
|
||||
ProjectID: 396936926,
|
||||
ID: "400000109",
|
||||
UserID: "1855589",
|
||||
ProjectID: "396936926",
|
||||
Content: "Task400000109",
|
||||
Priority: 1,
|
||||
ChildOrder: 1,
|
||||
Checked: 1,
|
||||
Checked: true,
|
||||
DateAdded: time1,
|
||||
DateCompleted: time3,
|
||||
SectionID: 1234,
|
||||
SectionID: "1234",
|
||||
},
|
||||
|
||||
makeTestItem(400000007, 396936927, true, false, false),
|
||||
makeTestItem(400000008, 396936927, true, false, false),
|
||||
makeTestItem(400000009, 396936927, false, false, false),
|
||||
makeTestItem(400000010, 396936927, false, false, true),
|
||||
makeTestItem(400000101, 396936927, false, false, false),
|
||||
makeTestItem(400000102, 396936927, true, true, false),
|
||||
makeTestItem(400000103, 396936927, false, true, false),
|
||||
makeTestItem(400000104, 396936927, false, true, false),
|
||||
makeTestItem(400000105, 396936927, true, true, false),
|
||||
makeTestItem("400000007", "396936927", true, false, false),
|
||||
makeTestItem("400000008", "396936927", true, false, false),
|
||||
makeTestItem("400000009", "396936927", false, false, false),
|
||||
makeTestItem("400000010", "396936927", false, false, true),
|
||||
makeTestItem("400000101", "396936927", false, false, false),
|
||||
makeTestItem("400000102", "396936927", true, true, false),
|
||||
makeTestItem("400000103", "396936927", false, true, false),
|
||||
makeTestItem("400000104", "396936927", false, true, false),
|
||||
makeTestItem("400000105", "396936927", true, true, false),
|
||||
|
||||
makeTestItem(400000111, 396936928, false, false, true),
|
||||
makeTestItem("400000111", "396936928", false, false, true),
|
||||
},
|
||||
Labels: []*label{
|
||||
{
|
||||
ID: 80000,
|
||||
ID: "80000",
|
||||
Name: "Label1",
|
||||
Color: 30,
|
||||
Color: "berry_red",
|
||||
},
|
||||
{
|
||||
ID: 80001,
|
||||
ID: "80001",
|
||||
Name: "Label2",
|
||||
Color: 31,
|
||||
Color: "red",
|
||||
},
|
||||
{
|
||||
ID: 80002,
|
||||
ID: "80002",
|
||||
Name: "Label3",
|
||||
Color: 32,
|
||||
Color: "orange",
|
||||
},
|
||||
{
|
||||
ID: 80003,
|
||||
ID: "80003",
|
||||
Name: "Label4",
|
||||
Color: 33,
|
||||
Color: "yellow",
|
||||
},
|
||||
},
|
||||
Notes: []*note{
|
||||
{
|
||||
ID: 101476,
|
||||
ID: "101476",
|
||||
PostedUID: 1855589,
|
||||
ItemID: 400000000,
|
||||
ItemID: "400000000",
|
||||
Content: "Lorem Ipsum dolor sit amet",
|
||||
Posted: time1,
|
||||
},
|
||||
{
|
||||
ID: 101477,
|
||||
ID: "101477",
|
||||
PostedUID: 1855589,
|
||||
ItemID: 400000001,
|
||||
ItemID: "400000001",
|
||||
Content: "Lorem Ipsum dolor sit amet",
|
||||
Posted: time1,
|
||||
},
|
||||
{
|
||||
ID: 101478,
|
||||
ID: "101478",
|
||||
PostedUID: 1855589,
|
||||
ItemID: 400000003,
|
||||
ItemID: "400000003",
|
||||
Content: "Lorem Ipsum dolor sit amet",
|
||||
Posted: time1,
|
||||
},
|
||||
{
|
||||
ID: 101479,
|
||||
ID: "101479",
|
||||
PostedUID: 1855589,
|
||||
ItemID: 400000010,
|
||||
ItemID: "400000010",
|
||||
Content: "Lorem Ipsum dolor sit amet",
|
||||
Posted: time1,
|
||||
},
|
||||
{
|
||||
ID: 101480,
|
||||
ID: "101480",
|
||||
PostedUID: 1855589,
|
||||
ItemID: 400000101,
|
||||
ItemID: "400000101",
|
||||
Content: "Lorem Ipsum dolor sit amet",
|
||||
FileAttachment: &fileAttachment{
|
||||
FileName: "file.md",
|
||||
|
@ -263,43 +261,43 @@ func TestConvertTodoistToVikunja(t *testing.T) {
|
|||
{
|
||||
ID: 102000,
|
||||
Content: "Lorem Ipsum dolor sit amet",
|
||||
ProjectID: 396936926,
|
||||
ProjectID: "396936926",
|
||||
Posted: time3,
|
||||
PostedUID: 1855589,
|
||||
},
|
||||
{
|
||||
ID: 102001,
|
||||
Content: "Lorem Ipsum dolor sit amet 2",
|
||||
ProjectID: 396936926,
|
||||
ProjectID: "396936926",
|
||||
Posted: time3,
|
||||
PostedUID: 1855589,
|
||||
},
|
||||
{
|
||||
ID: 102002,
|
||||
Content: "Lorem Ipsum dolor sit amet 3",
|
||||
ProjectID: 396936926,
|
||||
ProjectID: "396936926",
|
||||
Posted: time3,
|
||||
PostedUID: 1855589,
|
||||
},
|
||||
{
|
||||
ID: 102003,
|
||||
Content: "Lorem Ipsum dolor sit amet 4",
|
||||
ProjectID: 396936927,
|
||||
ProjectID: "396936927",
|
||||
Posted: time3,
|
||||
PostedUID: 1855589,
|
||||
},
|
||||
{
|
||||
ID: 102004,
|
||||
Content: "Lorem Ipsum dolor sit amet 5",
|
||||
ProjectID: 396936927,
|
||||
ProjectID: "396936927",
|
||||
Posted: time3,
|
||||
PostedUID: 1855589,
|
||||
},
|
||||
},
|
||||
Reminders: []*reminder{
|
||||
{
|
||||
ID: 103000,
|
||||
ItemID: 400000000,
|
||||
ID: "103000",
|
||||
ItemID: "400000000",
|
||||
Due: &dueDate{
|
||||
Date: "2020-06-15",
|
||||
IsRecurring: false,
|
||||
|
@ -307,40 +305,40 @@ func TestConvertTodoistToVikunja(t *testing.T) {
|
|||
MmOffset: 180,
|
||||
},
|
||||
{
|
||||
ID: 103001,
|
||||
ItemID: 400000000,
|
||||
ID: "103001",
|
||||
ItemID: "400000000",
|
||||
Due: &dueDate{
|
||||
Date: "2020-06-16T07:00:00",
|
||||
IsRecurring: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: 103002,
|
||||
ItemID: 400000002,
|
||||
ID: "103002",
|
||||
ItemID: "400000002",
|
||||
Due: &dueDate{
|
||||
Date: "2020-07-15T07:00:00Z",
|
||||
IsRecurring: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: 103003,
|
||||
ItemID: 400000003,
|
||||
ID: "103003",
|
||||
ItemID: "400000003",
|
||||
Due: &dueDate{
|
||||
Date: "2020-06-15T07:00:00",
|
||||
IsRecurring: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: 103004,
|
||||
ItemID: 400000005,
|
||||
ID: "103004",
|
||||
ItemID: "400000005",
|
||||
Due: &dueDate{
|
||||
Date: "2020-06-15T07:00:00",
|
||||
IsRecurring: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: 103006,
|
||||
ItemID: 400000009,
|
||||
ID: "103006",
|
||||
ItemID: "400000009",
|
||||
Due: &dueDate{
|
||||
Date: "2020-06-15T07:00:00",
|
||||
IsRecurring: false,
|
||||
|
@ -349,9 +347,9 @@ func TestConvertTodoistToVikunja(t *testing.T) {
|
|||
},
|
||||
Sections: []*section{
|
||||
{
|
||||
ID: 1234,
|
||||
ID: "1234",
|
||||
Name: "Some Bucket",
|
||||
ProjectID: 396936926,
|
||||
ProjectID: "396936926",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -359,19 +357,19 @@ func TestConvertTodoistToVikunja(t *testing.T) {
|
|||
vikunjaLabels := []*models.Label{
|
||||
{
|
||||
Title: "Label1",
|
||||
HexColor: todoistColors[30],
|
||||
HexColor: todoistColors["berry_red"],
|
||||
},
|
||||
{
|
||||
Title: "Label2",
|
||||
HexColor: todoistColors[31],
|
||||
HexColor: todoistColors["red"],
|
||||
},
|
||||
{
|
||||
Title: "Label3",
|
||||
HexColor: todoistColors[32],
|
||||
HexColor: todoistColors["orange"],
|
||||
},
|
||||
{
|
||||
Title: "Label4",
|
||||
HexColor: todoistColors[33],
|
||||
HexColor: todoistColors["yellow"],
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -385,11 +383,11 @@ func TestConvertTodoistToVikunja(t *testing.T) {
|
|||
List: models.List{
|
||||
Title: "Project1",
|
||||
Description: "Lorem Ipsum dolor sit amet\nLorem Ipsum dolor sit amet 2\nLorem Ipsum dolor sit amet 3",
|
||||
HexColor: todoistColors[30],
|
||||
HexColor: todoistColors["berry_red"],
|
||||
},
|
||||
Buckets: []*models.Bucket{
|
||||
{
|
||||
ID: 1234,
|
||||
ID: 1,
|
||||
Title: "Some Bucket",
|
||||
},
|
||||
},
|
||||
|
@ -510,7 +508,7 @@ func TestConvertTodoistToVikunja(t *testing.T) {
|
|||
Done: true,
|
||||
Created: time1,
|
||||
DoneAt: time3,
|
||||
BucketID: 1234,
|
||||
BucketID: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -519,7 +517,7 @@ func TestConvertTodoistToVikunja(t *testing.T) {
|
|||
List: models.List{
|
||||
Title: "Project2",
|
||||
Description: "Lorem Ipsum dolor sit amet 4\nLorem Ipsum dolor sit amet 5",
|
||||
HexColor: todoistColors[37],
|
||||
HexColor: todoistColors["mint_green"],
|
||||
},
|
||||
Tasks: []*models.TaskWithComments{
|
||||
{
|
||||
|
@ -616,7 +614,7 @@ func TestConvertTodoistToVikunja(t *testing.T) {
|
|||
{
|
||||
List: models.List{
|
||||
Title: "Project3 - Archived",
|
||||
HexColor: todoistColors[37],
|
||||
HexColor: todoistColors["mint_green"],
|
||||
IsArchived: true,
|
||||
},
|
||||
Tasks: []*models.TaskWithComments{
|
||||
|
@ -634,7 +632,7 @@ func TestConvertTodoistToVikunja(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
doneItems := make(map[int64]*doneItem)
|
||||
doneItems := make(map[string]*doneItem)
|
||||
hierachie, err := convertTodoistToVikunja(testSync, doneItems)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, hierachie)
|
||||
|
|
|
@ -36,7 +36,7 @@ func TestConvertTrelloToVikunja(t *testing.T) {
|
|||
|
||||
time1, err := time.Parse(time.RFC3339Nano, "2014-09-26T08:25:05Z")
|
||||
assert.NoError(t, err)
|
||||
exampleFile, err := os.ReadFile(config.ServiceRootpath.GetString() + "/pkg/modules/migration/wunderlist/testimage.jpg")
|
||||
exampleFile, err := os.ReadFile(config.ServiceRootpath.GetString() + "/pkg/modules/migration/testimage.jpg")
|
||||
assert.NoError(t, err)
|
||||
|
||||
trelloData := []*trello.Board{
|
||||
|
|
|
@ -1,512 +0,0 @@
|
|||
// 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 wunderlist
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/files"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/api/pkg/modules/migration"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"code.vikunja.io/api/pkg/utils"
|
||||
)
|
||||
|
||||
// Migration represents the implementation of the migration for wunderlist
|
||||
type Migration struct {
|
||||
// Code is the code used to get a user api token
|
||||
Code string `query:"code" json:"code"`
|
||||
}
|
||||
|
||||
// This represents all necessary fields for getting an api token for the wunderlist api from a code
|
||||
type wunderlistAuthRequest struct {
|
||||
ClientID string `json:"client_id"`
|
||||
ClientSecret string `json:"client_secret"`
|
||||
Code string `json:"code"`
|
||||
}
|
||||
|
||||
type wunderlistAuthToken struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
}
|
||||
|
||||
type task struct {
|
||||
AssigneeID int `json:"assignee_id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
CreatedByID int `json:"created_by_id"`
|
||||
Completed bool `json:"completed"`
|
||||
CompletedAt time.Time `json:"completed_at"`
|
||||
DueDate string `json:"due_date"`
|
||||
ID int `json:"id"`
|
||||
ListID int `json:"list_id"`
|
||||
Revision int `json:"revision"`
|
||||
Starred bool `json:"starred"`
|
||||
Title string `json:"title"`
|
||||
}
|
||||
|
||||
type list struct {
|
||||
ID int `json:"id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
Title string `json:"title"`
|
||||
ListType string `json:"list_type"`
|
||||
Type string `json:"type"`
|
||||
Revision int `json:"revision"`
|
||||
|
||||
Migrated bool `json:"-"`
|
||||
}
|
||||
|
||||
type folder struct {
|
||||
ID int `json:"id"`
|
||||
Title string `json:"title"`
|
||||
ListIds []int `json:"list_ids"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
CreatedByRequestID string `json:"created_by_request_id"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
Type string `json:"type"`
|
||||
Revision int `json:"revision"`
|
||||
}
|
||||
|
||||
type note struct {
|
||||
ID int `json:"id"`
|
||||
TaskID int `json:"task_id"`
|
||||
Content string `json:"content"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
Revision int `json:"revision"`
|
||||
}
|
||||
|
||||
type file struct {
|
||||
ID int `json:"id"`
|
||||
URL string `json:"url"`
|
||||
TaskID int `json:"task_id"`
|
||||
ListID int `json:"list_id"`
|
||||
UserID int `json:"user_id"`
|
||||
FileName string `json:"file_name"`
|
||||
ContentType string `json:"content_type"`
|
||||
FileSize int `json:"file_size"`
|
||||
LocalCreatedAt time.Time `json:"local_created_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
Type string `json:"type"`
|
||||
Revision int `json:"revision"`
|
||||
}
|
||||
|
||||
type reminder struct {
|
||||
ID int `json:"id"`
|
||||
Date time.Time `json:"date"`
|
||||
TaskID int `json:"task_id"`
|
||||
Revision int `json:"revision"`
|
||||
Type string `json:"type"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type subtask struct {
|
||||
ID int `json:"id"`
|
||||
TaskID int `json:"task_id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
CreatedByID int `json:"created_by_id"`
|
||||
Revision int `json:"revision"`
|
||||
Title string `json:"title"`
|
||||
}
|
||||
|
||||
type wunderlistContents struct {
|
||||
tasks []*task
|
||||
lists []*list
|
||||
folders []*folder
|
||||
notes []*note
|
||||
files []*file
|
||||
reminders []*reminder
|
||||
subtasks []*subtask
|
||||
}
|
||||
|
||||
func convertListForFolder(listID int, list *list, content *wunderlistContents) (*models.ListWithTasksAndBuckets, error) {
|
||||
|
||||
l := &models.ListWithTasksAndBuckets{
|
||||
List: models.List{
|
||||
Title: list.Title,
|
||||
Created: list.CreatedAt,
|
||||
},
|
||||
}
|
||||
|
||||
// Find all tasks belonging to this list and put them in
|
||||
for _, t := range content.tasks {
|
||||
if t.ListID == listID {
|
||||
newTask := &models.Task{
|
||||
Title: t.Title,
|
||||
Created: t.CreatedAt,
|
||||
Done: t.Completed,
|
||||
}
|
||||
|
||||
// Set Done At
|
||||
if newTask.Done {
|
||||
newTask.DoneAt = t.CompletedAt.In(config.GetTimeZone())
|
||||
}
|
||||
|
||||
// Parse the due date
|
||||
if t.DueDate != "" {
|
||||
dueDate, err := time.Parse("2006-01-02", t.DueDate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newTask.DueDate = dueDate.In(config.GetTimeZone())
|
||||
}
|
||||
|
||||
// Find related notes
|
||||
for _, n := range content.notes {
|
||||
if n.TaskID == t.ID {
|
||||
newTask.Description = n.Content
|
||||
}
|
||||
}
|
||||
|
||||
// Attachments
|
||||
for _, f := range content.files {
|
||||
if f.TaskID == t.ID {
|
||||
// Download the attachment and put it in the file
|
||||
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, f.URL, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
buf := &bytes.Buffer{}
|
||||
_, err = buf.ReadFrom(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newTask.Attachments = append(newTask.Attachments, &models.TaskAttachment{
|
||||
File: &files.File{
|
||||
Name: f.FileName,
|
||||
Mime: f.ContentType,
|
||||
Size: uint64(f.FileSize),
|
||||
Created: f.CreatedAt,
|
||||
// We directly pass the file contents here to have a way to link the attachment to the file later.
|
||||
// Because we don't have an ID for our task at this point of the migration, we cannot just throw all
|
||||
// attachments in a slice and do the work of downloading and properly storing them later.
|
||||
FileContent: buf.Bytes(),
|
||||
},
|
||||
Created: f.CreatedAt,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Subtasks
|
||||
for _, s := range content.subtasks {
|
||||
if s.TaskID == t.ID {
|
||||
if newTask.RelatedTasks[models.RelationKindSubtask] == nil {
|
||||
newTask.RelatedTasks = make(models.RelatedTaskMap)
|
||||
}
|
||||
newTask.RelatedTasks[models.RelationKindSubtask] = append(newTask.RelatedTasks[models.RelationKindSubtask], &models.Task{
|
||||
Title: s.Title,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Reminders
|
||||
for _, r := range content.reminders {
|
||||
if r.TaskID == t.ID {
|
||||
newTask.Reminders = append(newTask.Reminders, r.Date.In(config.GetTimeZone()))
|
||||
}
|
||||
}
|
||||
|
||||
l.Tasks = append(l.Tasks, &models.TaskWithComments{Task: *newTask})
|
||||
}
|
||||
}
|
||||
return l, nil
|
||||
}
|
||||
|
||||
func convertWunderlistToVikunja(content *wunderlistContents) (fullVikunjaHierachie []*models.NamespaceWithListsAndTasks, err error) {
|
||||
|
||||
// Make a map from the list with the key being list id for easier handling
|
||||
listMap := make(map[int]*list, len(content.lists))
|
||||
for _, l := range content.lists {
|
||||
listMap[l.ID] = l
|
||||
}
|
||||
|
||||
// First, we look through all folders and create namespaces for them.
|
||||
for _, folder := range content.folders {
|
||||
namespace := &models.NamespaceWithListsAndTasks{
|
||||
Namespace: models.Namespace{
|
||||
Title: folder.Title,
|
||||
Created: folder.CreatedAt,
|
||||
Updated: folder.UpdatedAt,
|
||||
},
|
||||
}
|
||||
|
||||
// Then find all lists for that folder
|
||||
for _, listID := range folder.ListIds {
|
||||
if list, exists := listMap[listID]; exists {
|
||||
l, err := convertListForFolder(listID, list, content)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
namespace.Lists = append(namespace.Lists, l)
|
||||
// And mark the list as migrated so we don't iterate over it again
|
||||
list.Migrated = true
|
||||
}
|
||||
}
|
||||
|
||||
// And then finally put the namespace (which now has all the details) back in the full array.
|
||||
fullVikunjaHierachie = append(fullVikunjaHierachie, namespace)
|
||||
}
|
||||
|
||||
// At the end, loop over all lists which don't belong to a namespace and put them in a default namespace
|
||||
if len(listMap) > 0 {
|
||||
newNamespace := &models.NamespaceWithListsAndTasks{
|
||||
Namespace: models.Namespace{
|
||||
Title: "Migrated from wunderlist",
|
||||
},
|
||||
}
|
||||
|
||||
for _, list := range listMap {
|
||||
|
||||
if list.Migrated {
|
||||
continue
|
||||
}
|
||||
|
||||
l, err := convertListForFolder(list.ID, list, content)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newNamespace.Lists = append(newNamespace.Lists, l)
|
||||
}
|
||||
|
||||
fullVikunjaHierachie = append(fullVikunjaHierachie, newNamespace)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func makeAuthGetRequest(token *wunderlistAuthToken, urlPart string, v interface{}, urlParams url.Values) error {
|
||||
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "https://a.wunderlist.com/api/v1/"+urlPart, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("X-Access-Token", token.AccessToken)
|
||||
req.Header.Set("X-Client-ID", config.MigrationWunderlistClientID.GetString())
|
||||
req.URL.RawQuery = urlParams.Encode()
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
_, err = buf.ReadFrom(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode > 399 {
|
||||
return fmt.Errorf("wunderlist API Error: Status Code: %d, Response was: %s", resp.StatusCode, buf.String())
|
||||
}
|
||||
|
||||
// If the response is an empty json array, we need to exit here, otherwise this breaks the json parser since it
|
||||
// expects a null for an empty slice
|
||||
str := buf.String()
|
||||
if str == "[]" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return json.Unmarshal(buf.Bytes(), v)
|
||||
}
|
||||
|
||||
// Migrate migrates a user's wunderlist lists, tasks, etc.
|
||||
// @Summary Migrate all lists, tasks etc. from wunderlist
|
||||
// @Description Migrates all folders, lists, tasks, notes, reminders, subtasks and files from wunderlist to vikunja.
|
||||
// @tags migration
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security JWTKeyAuth
|
||||
// @Param migrationCode body wunderlist.Migration true "The auth code previously obtained from the auth url. See the docs for /migration/wunderlist/auth."
|
||||
// @Success 200 {object} models.Message "A message telling you everything was migrated successfully."
|
||||
// @Failure 500 {object} models.Message "Internal server error"
|
||||
// @Router /migration/wunderlist/migrate [post]
|
||||
func (w *Migration) Migrate(user *user.User) (err error) {
|
||||
|
||||
log.Debugf("[Wunderlist migration] Starting wunderlist migration for user %d", user.ID)
|
||||
|
||||
// Struct init
|
||||
wContent := &wunderlistContents{
|
||||
tasks: []*task{},
|
||||
lists: []*list{},
|
||||
folders: []*folder{},
|
||||
notes: []*note{},
|
||||
files: []*file{},
|
||||
reminders: []*reminder{},
|
||||
subtasks: []*subtask{},
|
||||
}
|
||||
|
||||
// 0. Get api token from oauth user token
|
||||
authRequest := wunderlistAuthRequest{
|
||||
ClientID: config.MigrationWunderlistClientID.GetString(),
|
||||
ClientSecret: config.MigrationWunderlistClientSecret.GetString(),
|
||||
Code: w.Code,
|
||||
}
|
||||
jsonAuth, err := json.Marshal(authRequest)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, "https://www.wunderlist.com/oauth/access_token", bytes.NewBuffer(jsonAuth))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
authToken := &wunderlistAuthToken{}
|
||||
err = json.NewDecoder(resp.Body).Decode(authToken)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
log.Debugf("[Wunderlist migration] Start getting all data from wunderlist for user %d", user.ID)
|
||||
|
||||
// 1. Get all folders
|
||||
err = makeAuthGetRequest(authToken, "folders", &wContent.folders, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 2. Get all lists
|
||||
err = makeAuthGetRequest(authToken, "lists", &wContent.lists, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, l := range wContent.lists {
|
||||
|
||||
listQueryParam := url.Values{"list_id": []string{strconv.Itoa(l.ID)}}
|
||||
|
||||
// 3. Get all tasks for each list
|
||||
tasks := []*task{}
|
||||
err = makeAuthGetRequest(authToken, "tasks", &tasks, listQueryParam)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
wContent.tasks = append(wContent.tasks, tasks...)
|
||||
|
||||
// 3. Get all done tasks for each list
|
||||
doneTasks := []*task{}
|
||||
err = makeAuthGetRequest(authToken, "tasks", &doneTasks, url.Values{"list_id": []string{strconv.Itoa(l.ID)}, "completed": []string{"true"}})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
wContent.tasks = append(wContent.tasks, doneTasks...)
|
||||
|
||||
// 4. Get all notes for all lists
|
||||
notes := []*note{}
|
||||
err = makeAuthGetRequest(authToken, "notes", ¬es, listQueryParam)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
wContent.notes = append(wContent.notes, notes...)
|
||||
|
||||
// 5. Get all files for all lists
|
||||
fils := []*file{}
|
||||
err = makeAuthGetRequest(authToken, "files", &fils, listQueryParam)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
wContent.files = append(wContent.files, fils...)
|
||||
|
||||
// 6. Get all reminders for all lists
|
||||
reminders := []*reminder{}
|
||||
err = makeAuthGetRequest(authToken, "reminders", &reminders, listQueryParam)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
wContent.reminders = append(wContent.reminders, reminders...)
|
||||
|
||||
// 7. Get all subtasks for all lists
|
||||
subtasks := []*subtask{}
|
||||
err = makeAuthGetRequest(authToken, "subtasks", &subtasks, listQueryParam)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
wContent.subtasks = append(wContent.subtasks, subtasks...)
|
||||
}
|
||||
|
||||
log.Debugf("[Wunderlist migration] Got all data from wunderlist for user %d", user.ID)
|
||||
log.Debugf("[Wunderlist migration] Migrating data to vikunja format for user %d", user.ID)
|
||||
|
||||
// Convert + Insert everything
|
||||
fullVikunjaHierachie, err := convertWunderlistToVikunja(wContent)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
log.Debugf("[Wunderlist migration] Done migrating data to vikunja format for user %d", user.ID)
|
||||
log.Debugf("[Wunderlist migration] Insert data into db for user %d", user.ID)
|
||||
|
||||
err = migration.InsertFromStructure(fullVikunjaHierachie, user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("[Wunderlist migration] Done inserting data into db for user %d", user.ID)
|
||||
log.Debugf("[Wunderlist migration] Wunderlist migration for user %d done", user.ID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AuthURL returns the url users need to authenticate against
|
||||
// @Summary Get the auth url from wunderlist
|
||||
// @Description Returns the auth url where the user needs to get its auth code. This code can then be used to migrate everything from wunderlist to Vikunja.
|
||||
// @tags migration
|
||||
// @Produce json
|
||||
// @Security JWTKeyAuth
|
||||
// @Success 200 {object} handler.AuthURL "The auth url."
|
||||
// @Failure 500 {object} models.Message "Internal server error"
|
||||
// @Router /migration/wunderlist/auth [get]
|
||||
func (w *Migration) AuthURL() string {
|
||||
return "https://www.wunderlist.com/oauth/authorize?client_id=" +
|
||||
config.MigrationWunderlistClientID.GetString() +
|
||||
"&redirect_uri=" +
|
||||
config.MigrationWunderlistRedirectURL.GetString() +
|
||||
"&state=" + utils.MakeRandomString(32)
|
||||
}
|
||||
|
||||
// Name is used to get the name of the wunderlist migration
|
||||
// @Summary Get migration status
|
||||
// @Description Returns if the current user already did the migation or not. This is useful to show a confirmation message in the frontend if the user is trying to do the same migration again.
|
||||
// @tags migration
|
||||
// @Produce json
|
||||
// @Security JWTKeyAuth
|
||||
// @Success 200 {object} migration.Status "The migration status"
|
||||
// @Failure 500 {object} models.Message "Internal server error"
|
||||
// @Router /migration/wunderlist/status [get]
|
||||
func (w *Migration) Name() string {
|
||||
return "wunderlist"
|
||||
}
|
|
@ -1,386 +0,0 @@
|
|||
// 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 wunderlist
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/files"
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gopkg.in/d4l3k/messagediff.v1"
|
||||
)
|
||||
|
||||
func TestWunderlistParsing(t *testing.T) {
|
||||
|
||||
config.InitConfig()
|
||||
|
||||
time1, err := time.Parse(time.RFC3339Nano, "2013-08-30T08:29:46.203Z")
|
||||
assert.NoError(t, err)
|
||||
time1 = time1.In(config.GetTimeZone())
|
||||
time2, err := time.Parse(time.RFC3339Nano, "2013-08-30T08:36:13.273Z")
|
||||
assert.NoError(t, err)
|
||||
time2 = time2.In(config.GetTimeZone())
|
||||
time3, err := time.Parse(time.RFC3339Nano, "2013-09-05T08:36:13.273Z")
|
||||
assert.NoError(t, err)
|
||||
time3 = time3.In(config.GetTimeZone())
|
||||
time4, err := time.Parse(time.RFC3339Nano, "2013-08-02T11:58:55Z")
|
||||
assert.NoError(t, err)
|
||||
time4 = time4.In(config.GetTimeZone())
|
||||
|
||||
exampleFile, err := os.ReadFile(config.ServiceRootpath.GetString() + "/pkg/modules/migration/wunderlist/testimage.jpg")
|
||||
assert.NoError(t, err)
|
||||
|
||||
createTestTask := func(id, listID int, done bool) *task {
|
||||
completedAt, err := time.Parse(time.RFC3339Nano, "1970-01-01T00:00:00Z")
|
||||
assert.NoError(t, err)
|
||||
if done {
|
||||
completedAt = time1
|
||||
}
|
||||
completedAt = completedAt.In(config.GetTimeZone())
|
||||
return &task{
|
||||
ID: id,
|
||||
AssigneeID: 123,
|
||||
CreatedAt: time1,
|
||||
DueDate: "2013-09-05",
|
||||
ListID: listID,
|
||||
Title: "Ipsum" + strconv.Itoa(id),
|
||||
Completed: done,
|
||||
CompletedAt: completedAt,
|
||||
}
|
||||
}
|
||||
|
||||
createTestNote := func(id, taskID int) *note {
|
||||
return ¬e{
|
||||
ID: id,
|
||||
TaskID: taskID,
|
||||
Content: "Lorem Ipsum dolor sit amet",
|
||||
CreatedAt: time3,
|
||||
UpdatedAt: time2,
|
||||
}
|
||||
}
|
||||
|
||||
fixtures := &wunderlistContents{
|
||||
folders: []*folder{
|
||||
{
|
||||
ID: 123,
|
||||
Title: "Lorem Ipsum",
|
||||
ListIds: []int{1, 2, 3, 4},
|
||||
CreatedAt: time1,
|
||||
UpdatedAt: time2,
|
||||
},
|
||||
},
|
||||
lists: []*list{
|
||||
{
|
||||
ID: 1,
|
||||
CreatedAt: time1,
|
||||
Title: "Lorem1",
|
||||
},
|
||||
{
|
||||
ID: 2,
|
||||
CreatedAt: time1,
|
||||
Title: "Lorem2",
|
||||
},
|
||||
{
|
||||
ID: 3,
|
||||
CreatedAt: time1,
|
||||
Title: "Lorem3",
|
||||
},
|
||||
{
|
||||
ID: 4,
|
||||
CreatedAt: time1,
|
||||
Title: "Lorem4",
|
||||
},
|
||||
{
|
||||
ID: 5,
|
||||
CreatedAt: time4,
|
||||
Title: "List without a namespace",
|
||||
},
|
||||
},
|
||||
tasks: []*task{
|
||||
createTestTask(1, 1, false),
|
||||
createTestTask(2, 1, false),
|
||||
createTestTask(3, 2, true),
|
||||
createTestTask(4, 2, false),
|
||||
createTestTask(5, 3, false),
|
||||
createTestTask(6, 3, true),
|
||||
createTestTask(7, 3, true),
|
||||
createTestTask(8, 3, false),
|
||||
createTestTask(9, 4, true),
|
||||
createTestTask(10, 4, true),
|
||||
},
|
||||
notes: []*note{
|
||||
createTestNote(1, 1),
|
||||
createTestNote(2, 2),
|
||||
createTestNote(3, 3),
|
||||
},
|
||||
files: []*file{
|
||||
{
|
||||
ID: 1,
|
||||
URL: "https://vikunja.io/testimage.jpg", // Using an image which we are hosting, so it'll still be up
|
||||
TaskID: 1,
|
||||
ListID: 1,
|
||||
FileName: "file.md",
|
||||
ContentType: "text/plain",
|
||||
FileSize: 12345,
|
||||
CreatedAt: time2,
|
||||
UpdatedAt: time4,
|
||||
},
|
||||
{
|
||||
ID: 2,
|
||||
URL: "https://vikunja.io/testimage.jpg",
|
||||
TaskID: 3,
|
||||
ListID: 2,
|
||||
FileName: "file2.md",
|
||||
ContentType: "text/plain",
|
||||
FileSize: 12345,
|
||||
CreatedAt: time3,
|
||||
UpdatedAt: time4,
|
||||
},
|
||||
},
|
||||
reminders: []*reminder{
|
||||
{
|
||||
ID: 1,
|
||||
Date: time4,
|
||||
TaskID: 1,
|
||||
CreatedAt: time4,
|
||||
UpdatedAt: time4,
|
||||
},
|
||||
{
|
||||
ID: 2,
|
||||
Date: time3,
|
||||
TaskID: 4,
|
||||
CreatedAt: time3,
|
||||
UpdatedAt: time3,
|
||||
},
|
||||
},
|
||||
subtasks: []*subtask{
|
||||
{
|
||||
ID: 1,
|
||||
TaskID: 2,
|
||||
CreatedAt: time4,
|
||||
Title: "LoremSub1",
|
||||
},
|
||||
{
|
||||
ID: 2,
|
||||
TaskID: 2,
|
||||
CreatedAt: time4,
|
||||
Title: "LoremSub2",
|
||||
},
|
||||
{
|
||||
ID: 3,
|
||||
TaskID: 4,
|
||||
CreatedAt: time4,
|
||||
Title: "LoremSub3",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
expectedHierachie := []*models.NamespaceWithListsAndTasks{
|
||||
{
|
||||
Namespace: models.Namespace{
|
||||
Title: "Lorem Ipsum",
|
||||
Created: time1,
|
||||
Updated: time2,
|
||||
},
|
||||
Lists: []*models.ListWithTasksAndBuckets{
|
||||
{
|
||||
List: models.List{
|
||||
Created: time1,
|
||||
Title: "Lorem1",
|
||||
},
|
||||
Tasks: []*models.TaskWithComments{
|
||||
{
|
||||
Task: models.Task{
|
||||
Title: "Ipsum1",
|
||||
DueDate: time.Unix(1378339200, 0).In(config.GetTimeZone()),
|
||||
Created: time1,
|
||||
Description: "Lorem Ipsum dolor sit amet",
|
||||
Attachments: []*models.TaskAttachment{
|
||||
{
|
||||
File: &files.File{
|
||||
Name: "file.md",
|
||||
Mime: "text/plain",
|
||||
Size: 12345,
|
||||
Created: time2,
|
||||
FileContent: exampleFile,
|
||||
},
|
||||
Created: time2,
|
||||
},
|
||||
},
|
||||
Reminders: []time.Time{time4},
|
||||
},
|
||||
},
|
||||
{
|
||||
Task: models.Task{
|
||||
Title: "Ipsum2",
|
||||
DueDate: time.Unix(1378339200, 0).In(config.GetTimeZone()),
|
||||
Created: time1,
|
||||
Description: "Lorem Ipsum dolor sit amet",
|
||||
RelatedTasks: map[models.RelationKind][]*models.Task{
|
||||
models.RelationKindSubtask: {
|
||||
{
|
||||
Title: "LoremSub1",
|
||||
},
|
||||
{
|
||||
Title: "LoremSub2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
List: models.List{
|
||||
Created: time1,
|
||||
Title: "Lorem2",
|
||||
},
|
||||
Tasks: []*models.TaskWithComments{
|
||||
{
|
||||
Task: models.Task{
|
||||
Title: "Ipsum3",
|
||||
Done: true,
|
||||
DoneAt: time1,
|
||||
DueDate: time.Unix(1378339200, 0).In(config.GetTimeZone()),
|
||||
Created: time1,
|
||||
Description: "Lorem Ipsum dolor sit amet",
|
||||
Attachments: []*models.TaskAttachment{
|
||||
{
|
||||
File: &files.File{
|
||||
Name: "file2.md",
|
||||
Mime: "text/plain",
|
||||
Size: 12345,
|
||||
Created: time3,
|
||||
FileContent: exampleFile,
|
||||
},
|
||||
Created: time3,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Task: models.Task{
|
||||
Title: "Ipsum4",
|
||||
DueDate: time.Unix(1378339200, 0).In(config.GetTimeZone()),
|
||||
Created: time1,
|
||||
Reminders: []time.Time{time3},
|
||||
RelatedTasks: map[models.RelationKind][]*models.Task{
|
||||
models.RelationKindSubtask: {
|
||||
{
|
||||
Title: "LoremSub3",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
List: models.List{
|
||||
Created: time1,
|
||||
Title: "Lorem3",
|
||||
},
|
||||
Tasks: []*models.TaskWithComments{
|
||||
{
|
||||
Task: models.Task{
|
||||
Title: "Ipsum5",
|
||||
DueDate: time.Unix(1378339200, 0).In(config.GetTimeZone()),
|
||||
Created: time1,
|
||||
},
|
||||
},
|
||||
{
|
||||
Task: models.Task{
|
||||
Title: "Ipsum6",
|
||||
DueDate: time.Unix(1378339200, 0).In(config.GetTimeZone()),
|
||||
Created: time1,
|
||||
Done: true,
|
||||
DoneAt: time1,
|
||||
},
|
||||
},
|
||||
{
|
||||
Task: models.Task{
|
||||
Title: "Ipsum7",
|
||||
DueDate: time.Unix(1378339200, 0).In(config.GetTimeZone()),
|
||||
Created: time1,
|
||||
Done: true,
|
||||
DoneAt: time1,
|
||||
},
|
||||
},
|
||||
{
|
||||
Task: models.Task{
|
||||
Title: "Ipsum8",
|
||||
DueDate: time.Unix(1378339200, 0).In(config.GetTimeZone()),
|
||||
Created: time1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
List: models.List{
|
||||
Created: time1,
|
||||
Title: "Lorem4",
|
||||
},
|
||||
Tasks: []*models.TaskWithComments{
|
||||
{
|
||||
Task: models.Task{
|
||||
Title: "Ipsum9",
|
||||
DueDate: time.Unix(1378339200, 0).In(config.GetTimeZone()),
|
||||
Created: time1,
|
||||
Done: true,
|
||||
DoneAt: time1,
|
||||
},
|
||||
},
|
||||
{
|
||||
Task: models.Task{
|
||||
Title: "Ipsum10",
|
||||
DueDate: time.Unix(1378339200, 0).In(config.GetTimeZone()),
|
||||
Created: time1,
|
||||
Done: true,
|
||||
DoneAt: time1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Namespace: models.Namespace{
|
||||
Title: "Migrated from wunderlist",
|
||||
},
|
||||
Lists: []*models.ListWithTasksAndBuckets{
|
||||
{
|
||||
List: models.List{
|
||||
Created: time4,
|
||||
Title: "List without a namespace",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
hierachie, err := convertWunderlistToVikunja(fixtures)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, hierachie)
|
||||
if diff, equal := messagediff.PrettyDiff(hierachie, expectedHierachie); !equal {
|
||||
t.Errorf("converted wunderlist data = %v, want %v, diff: %v", hierachie, expectedHierachie, diff)
|
||||
}
|
||||
}
|
|
@ -205,7 +205,7 @@ func UploadAvatar(c echo.Context) (err error) {
|
|||
u.AvatarFileID = f.ID
|
||||
u.AvatarProvider = "upload"
|
||||
|
||||
if _, err := user.UpdateUser(s, u); err != nil {
|
||||
if _, err := user.UpdateUser(s, u, false); err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err, c)
|
||||
}
|
||||
|
|
|
@ -27,7 +27,6 @@ import (
|
|||
"code.vikunja.io/api/pkg/modules/migration/todoist"
|
||||
"code.vikunja.io/api/pkg/modules/migration/trello"
|
||||
vikunja_file "code.vikunja.io/api/pkg/modules/migration/vikunja-file"
|
||||
"code.vikunja.io/api/pkg/modules/migration/wunderlist"
|
||||
"code.vikunja.io/api/pkg/version"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
|
@ -121,10 +120,6 @@ func Info(c echo.Context) error {
|
|||
info.AuthInfo.OpenIDConnect.Providers = providers
|
||||
|
||||
// Migrators
|
||||
if config.MigrationWunderlistEnable.GetBool() {
|
||||
m := &wunderlist.Migration{}
|
||||
info.AvailableMigrators = append(info.AvailableMigrators, m.Name())
|
||||
}
|
||||
if config.MigrationTodoistEnable.GetBool() {
|
||||
m := &todoist.Migration{}
|
||||
info.AvailableMigrators = append(info.AvailableMigrators, m.Name())
|
||||
|
|
|
@ -133,7 +133,7 @@ func ChangeUserAvatarProvider(c echo.Context) error {
|
|||
|
||||
user.AvatarProvider = uap.AvatarProvider
|
||||
|
||||
_, err = user2.UpdateUser(s, user)
|
||||
_, err = user2.UpdateUser(s, user, false)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err, c)
|
||||
|
@ -199,7 +199,7 @@ func UpdateGeneralUserSettings(c echo.Context) error {
|
|||
user.Timezone = us.Timezone
|
||||
user.OverdueTasksRemindersTime = us.OverdueTasksRemindersTime
|
||||
|
||||
_, err = user2.UpdateUser(s, user)
|
||||
_, err = user2.UpdateUser(s, user, true)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err, c)
|
||||
|
|
|
@ -48,7 +48,6 @@ package routes
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -70,7 +69,6 @@ import (
|
|||
"code.vikunja.io/api/pkg/modules/migration/todoist"
|
||||
"code.vikunja.io/api/pkg/modules/migration/trello"
|
||||
vikunja_file "code.vikunja.io/api/pkg/modules/migration/vikunja-file"
|
||||
"code.vikunja.io/api/pkg/modules/migration/wunderlist"
|
||||
apiv1 "code.vikunja.io/api/pkg/routes/api/v1"
|
||||
"code.vikunja.io/api/pkg/routes/caldav"
|
||||
_ "code.vikunja.io/api/pkg/swagger" // To generate swagger docs
|
||||
|
@ -80,7 +78,7 @@ import (
|
|||
|
||||
"github.com/getsentry/sentry-go"
|
||||
sentryecho "github.com/getsentry/sentry-go/echo"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
echojwt "github.com/labstack/echo-jwt/v4"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/labstack/echo/v4/middleware"
|
||||
elog "github.com/labstack/gommon/log"
|
||||
|
@ -273,29 +271,8 @@ func registerAPIRoutes(a *echo.Group) {
|
|||
ur.POST("/shares/:share/auth", apiv1.AuthenticateLinkShare)
|
||||
}
|
||||
|
||||
// ===== Routes with Authetication =====
|
||||
// Authetification
|
||||
a.Use(middleware.JWTWithConfig(middleware.JWTConfig{
|
||||
// Custom parse function to make the middleware work with the github.com/golang-jwt/jwt/v4 package.
|
||||
// See https://github.com/labstack/echo/pull/1916#issuecomment-878046299
|
||||
ParseTokenFunc: func(auth string, c echo.Context) (interface{}, error) {
|
||||
keyFunc := func(t *jwt.Token) (interface{}, error) {
|
||||
if t.Method.Alg() != "HS256" {
|
||||
return nil, fmt.Errorf("unexpected jwt signing method=%v", t.Header["alg"])
|
||||
}
|
||||
return []byte(config.ServiceJWTSecret.GetString()), nil
|
||||
}
|
||||
|
||||
token, err := jwt.Parse(auth, keyFunc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !token.Valid {
|
||||
return nil, errors.New("invalid token")
|
||||
}
|
||||
return token, nil
|
||||
},
|
||||
}))
|
||||
// ===== Routes with Authentication =====
|
||||
a.Use(echojwt.JWT([]byte(config.ServiceJWTSecret.GetString())))
|
||||
|
||||
// Rate limit
|
||||
setupRateLimit(a, config.RateLimitKind.GetString())
|
||||
|
@ -612,16 +589,6 @@ func registerAPIRoutes(a *echo.Group) {
|
|||
}
|
||||
|
||||
func registerMigrations(m *echo.Group) {
|
||||
// Wunderlist
|
||||
if config.MigrationWunderlistEnable.GetBool() {
|
||||
wunderlistMigrationHandler := &migrationHandler.MigrationWeb{
|
||||
MigrationStruct: func() migration.Migrator {
|
||||
return &wunderlist.Migration{}
|
||||
},
|
||||
}
|
||||
wunderlistMigrationHandler.RegisterRoutes(m)
|
||||
}
|
||||
|
||||
// Todoist
|
||||
if config.MigrationTodoistEnable.GetBool() {
|
||||
todoistMigrationHandler := &migrationHandler.MigrationWeb{
|
||||
|
|
|
@ -95,7 +95,7 @@ const docTemplate = `{
|
|||
{
|
||||
"type": "integer",
|
||||
"description": "Unsplash Image ID",
|
||||
"name": "thumb",
|
||||
"name": "image",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
|
@ -141,7 +141,7 @@ const docTemplate = `{
|
|||
{
|
||||
"type": "integer",
|
||||
"description": "Unsplash Image ID",
|
||||
"name": "thumb",
|
||||
"name": "image",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
|
@ -3061,113 +3061,6 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"/migration/wunderlist/auth": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"JWTKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Returns the auth url where the user needs to get its auth code. This code can then be used to migrate everything from wunderlist to Vikunja.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"migration"
|
||||
],
|
||||
"summary": "Get the auth url from wunderlist",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The auth url.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handler.AuthURL"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Message"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/migration/wunderlist/migrate": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"JWTKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Migrates all folders, lists, tasks, notes, reminders, subtasks and files from wunderlist to vikunja.",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"migration"
|
||||
],
|
||||
"summary": "Migrate all lists, tasks etc. from wunderlist",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "The auth code previously obtained from the auth url. See the docs for /migration/wunderlist/auth.",
|
||||
"name": "migrationCode",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/wunderlist.Migration"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A message telling you everything was migrated successfully.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Message"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Message"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/migration/wunderlist/status": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"JWTKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Returns if the current user already did the migation or not. This is useful to show a confirmation message in the frontend if the user is trying to do the same migration again.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"migration"
|
||||
],
|
||||
"summary": "Get migration status",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The migration status",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/migration.Status"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Message"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/namespace/{id}": {
|
||||
"post": {
|
||||
"security": [
|
||||
|
@ -5688,7 +5581,7 @@ const docTemplate = `{
|
|||
{
|
||||
"type": "integer",
|
||||
"description": "The id of the other task.",
|
||||
"name": "otherTaskID",
|
||||
"name": "otherTaskId",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
|
@ -7727,36 +7620,11 @@ const docTemplate = `{
|
|||
},
|
||||
"created_by": {
|
||||
"description": "The user who initially created the bucket.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/user.User"
|
||||
},
|
||||
"filter_by": {
|
||||
"description": "The field name of the field to filter by",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"filter_comparator": {
|
||||
"description": "The comparator for field and value",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"filter_concat": {
|
||||
"description": "The way all filter conditions are concatenated together, can be either \"and\" or \"or\".,",
|
||||
"type": "string"
|
||||
},
|
||||
"filter_include_nulls": {
|
||||
"description": "If set to true, the result will also include null values",
|
||||
"type": "boolean"
|
||||
},
|
||||
"filter_value": {
|
||||
"description": "The value of the field name to filter by",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"description": "The unique, numeric id of this bucket.",
|
||||
|
@ -7775,24 +7643,10 @@ const docTemplate = `{
|
|||
"description": "The list this bucket belongs to.",
|
||||
"type": "integer"
|
||||
},
|
||||
"order_by": {
|
||||
"description": "The query parameter to order the items by. This can be either asc or desc, with asc being the default.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"position": {
|
||||
"description": "The position this bucket has when querying all buckets. See the tasks.position property on how to use this.",
|
||||
"type": "number"
|
||||
},
|
||||
"sort_by": {
|
||||
"description": "The query parameter to sort by. This is for ex. done, priority, etc.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"tasks": {
|
||||
"description": "All tasks which belong to this bucket.",
|
||||
"type": "array",
|
||||
|
@ -7808,9 +7662,7 @@ const docTemplate = `{
|
|||
"updated": {
|
||||
"description": "A timestamp when this bucket was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.BulkAssignees": {
|
||||
|
@ -7822,9 +7674,7 @@ const docTemplate = `{
|
|||
"items": {
|
||||
"$ref": "#/definitions/user.User"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.BulkTask": {
|
||||
|
@ -7858,7 +7708,11 @@ const docTemplate = `{
|
|||
},
|
||||
"created_by": {
|
||||
"description": "The user who initially created the task.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/user.User"
|
||||
}
|
||||
]
|
||||
},
|
||||
"description": {
|
||||
"description": "The task description.",
|
||||
|
@ -7930,7 +7784,11 @@ const docTemplate = `{
|
|||
},
|
||||
"related_tasks": {
|
||||
"description": "All related tasks, grouped by their relation kind",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.RelatedTaskMap"
|
||||
}
|
||||
]
|
||||
},
|
||||
"reminder_dates": {
|
||||
"description": "An array of datetimes when the user wants to be reminded of the task.",
|
||||
|
@ -7945,7 +7803,11 @@ const docTemplate = `{
|
|||
},
|
||||
"repeat_mode": {
|
||||
"description": "Can have three possible values which will trigger when the task is marked as done: 0 = repeats after the amount specified in repeat_after, 1 = repeats all dates each months (ignoring repeat_after), 3 = repeats from the current date rather than the last set date.",
|
||||
"type": "integer"
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.TaskRepeatMode"
|
||||
}
|
||||
]
|
||||
},
|
||||
"start_date": {
|
||||
"description": "When this task starts.",
|
||||
|
@ -7953,7 +7815,11 @@ const docTemplate = `{
|
|||
},
|
||||
"subscription": {
|
||||
"description": "The subscription status for the user reading this task. You can only read this property, use the subscription endpoints to modify it.\nWill only returned when retreiving one task.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.Subscription"
|
||||
}
|
||||
]
|
||||
},
|
||||
"task_ids": {
|
||||
"description": "A list of task ids to update",
|
||||
|
@ -7970,9 +7836,7 @@ const docTemplate = `{
|
|||
"updated": {
|
||||
"description": "A timestamp when this task was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.DatabaseNotifications": {
|
||||
|
@ -8000,9 +7864,7 @@ const docTemplate = `{
|
|||
"read_at": {
|
||||
"description": "When this notification is marked as read, this will be updated with the current timestamp.",
|
||||
"type": "string"
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.Label": {
|
||||
|
@ -8014,7 +7876,11 @@ const docTemplate = `{
|
|||
},
|
||||
"created_by": {
|
||||
"description": "The user who created this label",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/user.User"
|
||||
}
|
||||
]
|
||||
},
|
||||
"description": {
|
||||
"description": "The label description.",
|
||||
|
@ -8038,9 +7904,7 @@ const docTemplate = `{
|
|||
"updated": {
|
||||
"description": "A timestamp when this label was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.LabelTask": {
|
||||
|
@ -8053,9 +7917,7 @@ const docTemplate = `{
|
|||
"label_id": {
|
||||
"description": "The label id you want to associate with a task.",
|
||||
"type": "integer"
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.LabelTaskBulk": {
|
||||
|
@ -8067,9 +7929,7 @@ const docTemplate = `{
|
|||
"items": {
|
||||
"$ref": "#/definitions/models.Label"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.LinkSharing": {
|
||||
|
@ -8097,26 +7957,36 @@ const docTemplate = `{
|
|||
},
|
||||
"right": {
|
||||
"description": "The right this list is shared with. 0 = Read only, 1 = Read \u0026 Write, 2 = Admin. See the docs for more details.",
|
||||
"type": "integer",
|
||||
"default": 0,
|
||||
"maximum": 2
|
||||
"maximum": 2,
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.Right"
|
||||
}
|
||||
]
|
||||
},
|
||||
"shared_by": {
|
||||
"description": "The user who shared this list",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/user.User"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sharing_type": {
|
||||
"description": "The kind of this link. 0 = undefined, 1 = without password, 2 = with password.",
|
||||
"type": "integer",
|
||||
"default": 0,
|
||||
"maximum": 2
|
||||
"maximum": 2,
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.SharingType"
|
||||
}
|
||||
]
|
||||
},
|
||||
"updated": {
|
||||
"description": "A timestamp when this share was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.List": {
|
||||
|
@ -8165,7 +8035,11 @@ const docTemplate = `{
|
|||
},
|
||||
"owner": {
|
||||
"description": "The user who created this list.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/user.User"
|
||||
}
|
||||
]
|
||||
},
|
||||
"position": {
|
||||
"description": "The position this list has when querying all lists. See the tasks.position property on how to use this.",
|
||||
|
@ -8173,7 +8047,11 @@ const docTemplate = `{
|
|||
},
|
||||
"subscription": {
|
||||
"description": "The subscription status for the user reading this list. You can only read this property, use the subscription endpoints to modify it.\nWill only returned when retreiving one list.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.Subscription"
|
||||
}
|
||||
]
|
||||
},
|
||||
"title": {
|
||||
"description": "The title of the list. You'll see this in the namespace overview.",
|
||||
|
@ -8184,9 +8062,7 @@ const docTemplate = `{
|
|||
"updated": {
|
||||
"description": "A timestamp when this list was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.ListDuplicate": {
|
||||
|
@ -8194,14 +8070,16 @@ const docTemplate = `{
|
|||
"properties": {
|
||||
"list": {
|
||||
"description": "The copied list",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.List"
|
||||
}
|
||||
]
|
||||
},
|
||||
"namespace_id": {
|
||||
"description": "The target namespace ID",
|
||||
"type": "integer"
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.ListUser": {
|
||||
|
@ -8217,9 +8095,13 @@ const docTemplate = `{
|
|||
},
|
||||
"right": {
|
||||
"description": "The right this user has. 0 = Read only, 1 = Read \u0026 Write, 2 = Admin. See the docs for more details.",
|
||||
"type": "integer",
|
||||
"default": 0,
|
||||
"maximum": 2
|
||||
"maximum": 2,
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.Right"
|
||||
}
|
||||
]
|
||||
},
|
||||
"updated": {
|
||||
"description": "A timestamp when this relation was last updated. You cannot change this value.",
|
||||
|
@ -8228,9 +8110,7 @@ const docTemplate = `{
|
|||
"user_id": {
|
||||
"description": "The username.",
|
||||
"type": "string"
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.Message": {
|
||||
|
@ -8268,11 +8148,19 @@ const docTemplate = `{
|
|||
},
|
||||
"owner": {
|
||||
"description": "The user who owns this namespace",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/user.User"
|
||||
}
|
||||
]
|
||||
},
|
||||
"subscription": {
|
||||
"description": "The subscription status for the user reading this namespace. You can only read this property, use the subscription endpoints to modify it.\nWill only returned when retreiving one namespace.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.Subscription"
|
||||
}
|
||||
]
|
||||
},
|
||||
"title": {
|
||||
"description": "The name of this namespace.",
|
||||
|
@ -8283,9 +8171,7 @@ const docTemplate = `{
|
|||
"updated": {
|
||||
"description": "A timestamp when this namespace was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.NamespaceUser": {
|
||||
|
@ -8301,9 +8187,13 @@ const docTemplate = `{
|
|||
},
|
||||
"right": {
|
||||
"description": "The right this user has. 0 = Read only, 1 = Read \u0026 Write, 2 = Admin. See the docs for more details.",
|
||||
"type": "integer",
|
||||
"default": 0,
|
||||
"maximum": 2
|
||||
"maximum": 2,
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.Right"
|
||||
}
|
||||
]
|
||||
},
|
||||
"updated": {
|
||||
"description": "A timestamp when this relation was last updated. You cannot change this value.",
|
||||
|
@ -8312,9 +8202,7 @@ const docTemplate = `{
|
|||
"user_id": {
|
||||
"description": "The username.",
|
||||
"type": "string"
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.NamespaceWithLists": {
|
||||
|
@ -8349,11 +8237,19 @@ const docTemplate = `{
|
|||
},
|
||||
"owner": {
|
||||
"description": "The user who owns this namespace",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/user.User"
|
||||
}
|
||||
]
|
||||
},
|
||||
"subscription": {
|
||||
"description": "The subscription status for the user reading this namespace. You can only read this property, use the subscription endpoints to modify it.\nWill only returned when retreiving one namespace.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.Subscription"
|
||||
}
|
||||
]
|
||||
},
|
||||
"title": {
|
||||
"description": "The name of this namespace.",
|
||||
|
@ -8364,9 +8260,7 @@ const docTemplate = `{
|
|||
"updated": {
|
||||
"description": "A timestamp when this namespace was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.RelatedTaskMap": {
|
||||
|
@ -8378,6 +8272,50 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"models.RelationKind": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"unknown",
|
||||
"subtask",
|
||||
"parenttask",
|
||||
"related",
|
||||
"duplicateof",
|
||||
"duplicates",
|
||||
"blocking",
|
||||
"blocked",
|
||||
"precedes",
|
||||
"follows",
|
||||
"copiedfrom",
|
||||
"copiedto"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"RelationKindUnknown",
|
||||
"RelationKindSubtask",
|
||||
"RelationKindParenttask",
|
||||
"RelationKindRelated",
|
||||
"RelationKindDuplicateOf",
|
||||
"RelationKindDuplicates",
|
||||
"RelationKindBlocking",
|
||||
"RelationKindBlocked",
|
||||
"RelationKindPreceeds",
|
||||
"RelationKindFollows",
|
||||
"RelationKindCopiedFrom",
|
||||
"RelationKindCopiedTo"
|
||||
]
|
||||
},
|
||||
"models.Right": {
|
||||
"type": "integer",
|
||||
"enum": [
|
||||
0,
|
||||
1,
|
||||
2
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"RightRead",
|
||||
"RightWrite",
|
||||
"RightAdmin"
|
||||
]
|
||||
},
|
||||
"models.SavedFilter": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -8391,7 +8329,11 @@ const docTemplate = `{
|
|||
},
|
||||
"filters": {
|
||||
"description": "The actual filters this filter contains",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.TaskCollection"
|
||||
}
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"description": "The unique numeric id of this saved filter",
|
||||
|
@ -8403,7 +8345,11 @@ const docTemplate = `{
|
|||
},
|
||||
"owner": {
|
||||
"description": "The user who owns this filter",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/user.User"
|
||||
}
|
||||
]
|
||||
},
|
||||
"title": {
|
||||
"description": "The title of the filter.",
|
||||
|
@ -8414,10 +8360,21 @@ const docTemplate = `{
|
|||
"updated": {
|
||||
"description": "A timestamp when this filter was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.SharingType": {
|
||||
"type": "integer",
|
||||
"enum": [
|
||||
0,
|
||||
1,
|
||||
2
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"SharingTypeUnknown",
|
||||
"SharingTypeWithoutPassword",
|
||||
"SharingTypeWithPassword"
|
||||
]
|
||||
},
|
||||
"models.Subscription": {
|
||||
"type": "object",
|
||||
|
@ -8439,10 +8396,12 @@ const docTemplate = `{
|
|||
},
|
||||
"user": {
|
||||
"description": "The user who made this subscription",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/user.User"
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.Task": {
|
||||
|
@ -8476,7 +8435,11 @@ const docTemplate = `{
|
|||
},
|
||||
"created_by": {
|
||||
"description": "The user who initially created the task.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/user.User"
|
||||
}
|
||||
]
|
||||
},
|
||||
"description": {
|
||||
"description": "The task description.",
|
||||
|
@ -8548,7 +8511,11 @@ const docTemplate = `{
|
|||
},
|
||||
"related_tasks": {
|
||||
"description": "All related tasks, grouped by their relation kind",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.RelatedTaskMap"
|
||||
}
|
||||
]
|
||||
},
|
||||
"reminder_dates": {
|
||||
"description": "An array of datetimes when the user wants to be reminded of the task.",
|
||||
|
@ -8563,7 +8530,11 @@ const docTemplate = `{
|
|||
},
|
||||
"repeat_mode": {
|
||||
"description": "Can have three possible values which will trigger when the task is marked as done: 0 = repeats after the amount specified in repeat_after, 1 = repeats all dates each months (ignoring repeat_after), 3 = repeats from the current date rather than the last set date.",
|
||||
"type": "integer"
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.TaskRepeatMode"
|
||||
}
|
||||
]
|
||||
},
|
||||
"start_date": {
|
||||
"description": "When this task starts.",
|
||||
|
@ -8571,7 +8542,11 @@ const docTemplate = `{
|
|||
},
|
||||
"subscription": {
|
||||
"description": "The subscription status for the user reading this task. You can only read this property, use the subscription endpoints to modify it.\nWill only returned when retreiving one task.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.Subscription"
|
||||
}
|
||||
]
|
||||
},
|
||||
"title": {
|
||||
"description": "The task text. This is what you'll see in the list.",
|
||||
|
@ -8581,9 +8556,7 @@ const docTemplate = `{
|
|||
"updated": {
|
||||
"description": "A timestamp when this task was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.TaskAssginee": {
|
||||
|
@ -8594,9 +8567,7 @@ const docTemplate = `{
|
|||
},
|
||||
"user_id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.TaskAttachment": {
|
||||
|
@ -8616,9 +8587,7 @@ const docTemplate = `{
|
|||
},
|
||||
"task_id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.TaskCollection": {
|
||||
|
@ -8666,9 +8635,7 @@ const docTemplate = `{
|
|||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.TaskComment": {
|
||||
|
@ -8688,9 +8655,7 @@ const docTemplate = `{
|
|||
},
|
||||
"updated": {
|
||||
"type": "string"
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.TaskRelation": {
|
||||
|
@ -8702,7 +8667,11 @@ const docTemplate = `{
|
|||
},
|
||||
"created_by": {
|
||||
"description": "The user who created this relation",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/user.User"
|
||||
}
|
||||
]
|
||||
},
|
||||
"other_task_id": {
|
||||
"description": "The ID of the other task, the task which is being related.",
|
||||
|
@ -8710,15 +8679,30 @@ const docTemplate = `{
|
|||
},
|
||||
"relation_kind": {
|
||||
"description": "The kind of the relation.",
|
||||
"type": "string"
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.RelationKind"
|
||||
}
|
||||
]
|
||||
},
|
||||
"task_id": {
|
||||
"description": "The ID of the \"base\" task, the task which has a relation to another.",
|
||||
"type": "integer"
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.TaskRepeatMode": {
|
||||
"type": "integer",
|
||||
"enum": [
|
||||
0,
|
||||
1,
|
||||
2
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"TaskRepeatModeDefault",
|
||||
"TaskRepeatModeMonth",
|
||||
"TaskRepeatModeFromCurrentDate"
|
||||
]
|
||||
},
|
||||
"models.Team": {
|
||||
"type": "object",
|
||||
|
@ -8729,7 +8713,11 @@ const docTemplate = `{
|
|||
},
|
||||
"created_by": {
|
||||
"description": "The user who created this team.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/user.User"
|
||||
}
|
||||
]
|
||||
},
|
||||
"description": {
|
||||
"description": "The team's description.",
|
||||
|
@ -8755,9 +8743,7 @@ const docTemplate = `{
|
|||
"updated": {
|
||||
"description": "A timestamp when this relation was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.TeamList": {
|
||||
|
@ -8773,9 +8759,13 @@ const docTemplate = `{
|
|||
},
|
||||
"right": {
|
||||
"description": "The right this team has. 0 = Read only, 1 = Read \u0026 Write, 2 = Admin. See the docs for more details.",
|
||||
"type": "integer",
|
||||
"default": 0,
|
||||
"maximum": 2
|
||||
"maximum": 2,
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.Right"
|
||||
}
|
||||
]
|
||||
},
|
||||
"team_id": {
|
||||
"description": "The team id.",
|
||||
|
@ -8784,9 +8774,7 @@ const docTemplate = `{
|
|||
"updated": {
|
||||
"description": "A timestamp when this relation was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.TeamMember": {
|
||||
|
@ -8807,9 +8795,7 @@ const docTemplate = `{
|
|||
"username": {
|
||||
"description": "The username of the member. We use this to prevent automated user id entering.",
|
||||
"type": "string"
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.TeamNamespace": {
|
||||
|
@ -8825,9 +8811,13 @@ const docTemplate = `{
|
|||
},
|
||||
"right": {
|
||||
"description": "The right this team has. 0 = Read only, 1 = Read \u0026 Write, 2 = Admin. See the docs for more details.",
|
||||
"type": "integer",
|
||||
"default": 0,
|
||||
"maximum": 2
|
||||
"maximum": 2,
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.Right"
|
||||
}
|
||||
]
|
||||
},
|
||||
"team_id": {
|
||||
"description": "The team id.",
|
||||
|
@ -8836,9 +8826,7 @@ const docTemplate = `{
|
|||
"updated": {
|
||||
"description": "A timestamp when this relation was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.TeamUser": {
|
||||
|
@ -8874,8 +8862,7 @@ const docTemplate = `{
|
|||
"type": "string",
|
||||
"maxLength": 250,
|
||||
"minLength": 1
|
||||
},
|
||||
"web.Auth": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.TeamWithRight": {
|
||||
|
@ -8887,7 +8874,11 @@ const docTemplate = `{
|
|||
},
|
||||
"created_by": {
|
||||
"description": "The user who created this team.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/user.User"
|
||||
}
|
||||
]
|
||||
},
|
||||
"description": {
|
||||
"description": "The team's description.",
|
||||
|
@ -8911,16 +8902,12 @@ const docTemplate = `{
|
|||
"minLength": 1
|
||||
},
|
||||
"right": {
|
||||
"description": "The right this team has. 0 = Read only, 1 = Read \u0026 Write, 2 = Admin. See the docs for more details.",
|
||||
"type": "integer",
|
||||
"default": 0
|
||||
"$ref": "#/definitions/models.Right"
|
||||
},
|
||||
"updated": {
|
||||
"description": "A timestamp when this relation was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.UserWithRight": {
|
||||
|
@ -8944,9 +8931,7 @@ const docTemplate = `{
|
|||
"type": "string"
|
||||
},
|
||||
"right": {
|
||||
"description": "The right this user has. 0 = Read only, 1 = Read \u0026 Write, 2 = Admin. See the docs for more details.",
|
||||
"type": "integer",
|
||||
"default": 0
|
||||
"$ref": "#/definitions/models.Right"
|
||||
},
|
||||
"updated": {
|
||||
"description": "A timestamp when this task was last updated. You cannot change this value.",
|
||||
|
@ -8957,8 +8942,7 @@ const docTemplate = `{
|
|||
"type": "string",
|
||||
"maxLength": 250,
|
||||
"minLength": 1
|
||||
},
|
||||
"web.Auth": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"notifications.DatabaseNotification": {
|
||||
|
@ -9187,8 +9171,7 @@ const docTemplate = `{
|
|||
"type": "string",
|
||||
"maxLength": 250,
|
||||
"minLength": 1
|
||||
},
|
||||
"web.Auth": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1.LinkShareAuth": {
|
||||
|
@ -9396,15 +9379,6 @@ const docTemplate = `{
|
|||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"wunderlist.Migration": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"description": "Code is the code used to get a user api token",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"securityDefinitions": {
|
||||
|
|
|
@ -86,7 +86,7 @@
|
|||
{
|
||||
"type": "integer",
|
||||
"description": "Unsplash Image ID",
|
||||
"name": "thumb",
|
||||
"name": "image",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
|
@ -132,7 +132,7 @@
|
|||
{
|
||||
"type": "integer",
|
||||
"description": "Unsplash Image ID",
|
||||
"name": "thumb",
|
||||
"name": "image",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
|
@ -3052,113 +3052,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/migration/wunderlist/auth": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"JWTKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Returns the auth url where the user needs to get its auth code. This code can then be used to migrate everything from wunderlist to Vikunja.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"migration"
|
||||
],
|
||||
"summary": "Get the auth url from wunderlist",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The auth url.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handler.AuthURL"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Message"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/migration/wunderlist/migrate": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"JWTKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Migrates all folders, lists, tasks, notes, reminders, subtasks and files from wunderlist to vikunja.",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"migration"
|
||||
],
|
||||
"summary": "Migrate all lists, tasks etc. from wunderlist",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "The auth code previously obtained from the auth url. See the docs for /migration/wunderlist/auth.",
|
||||
"name": "migrationCode",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/wunderlist.Migration"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A message telling you everything was migrated successfully.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Message"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Message"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/migration/wunderlist/status": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"JWTKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Returns if the current user already did the migation or not. This is useful to show a confirmation message in the frontend if the user is trying to do the same migration again.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"migration"
|
||||
],
|
||||
"summary": "Get migration status",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The migration status",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/migration.Status"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Message"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/namespace/{id}": {
|
||||
"post": {
|
||||
"security": [
|
||||
|
@ -5679,7 +5572,7 @@
|
|||
{
|
||||
"type": "integer",
|
||||
"description": "The id of the other task.",
|
||||
"name": "otherTaskID",
|
||||
"name": "otherTaskId",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
|
@ -7718,36 +7611,11 @@
|
|||
},
|
||||
"created_by": {
|
||||
"description": "The user who initially created the bucket.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/user.User"
|
||||
},
|
||||
"filter_by": {
|
||||
"description": "The field name of the field to filter by",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"filter_comparator": {
|
||||
"description": "The comparator for field and value",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"filter_concat": {
|
||||
"description": "The way all filter conditions are concatenated together, can be either \"and\" or \"or\".,",
|
||||
"type": "string"
|
||||
},
|
||||
"filter_include_nulls": {
|
||||
"description": "If set to true, the result will also include null values",
|
||||
"type": "boolean"
|
||||
},
|
||||
"filter_value": {
|
||||
"description": "The value of the field name to filter by",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"description": "The unique, numeric id of this bucket.",
|
||||
|
@ -7766,24 +7634,10 @@
|
|||
"description": "The list this bucket belongs to.",
|
||||
"type": "integer"
|
||||
},
|
||||
"order_by": {
|
||||
"description": "The query parameter to order the items by. This can be either asc or desc, with asc being the default.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"position": {
|
||||
"description": "The position this bucket has when querying all buckets. See the tasks.position property on how to use this.",
|
||||
"type": "number"
|
||||
},
|
||||
"sort_by": {
|
||||
"description": "The query parameter to sort by. This is for ex. done, priority, etc.",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"tasks": {
|
||||
"description": "All tasks which belong to this bucket.",
|
||||
"type": "array",
|
||||
|
@ -7799,9 +7653,7 @@
|
|||
"updated": {
|
||||
"description": "A timestamp when this bucket was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.BulkAssignees": {
|
||||
|
@ -7813,9 +7665,7 @@
|
|||
"items": {
|
||||
"$ref": "#/definitions/user.User"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.BulkTask": {
|
||||
|
@ -7849,7 +7699,11 @@
|
|||
},
|
||||
"created_by": {
|
||||
"description": "The user who initially created the task.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/user.User"
|
||||
}
|
||||
]
|
||||
},
|
||||
"description": {
|
||||
"description": "The task description.",
|
||||
|
@ -7921,7 +7775,11 @@
|
|||
},
|
||||
"related_tasks": {
|
||||
"description": "All related tasks, grouped by their relation kind",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.RelatedTaskMap"
|
||||
}
|
||||
]
|
||||
},
|
||||
"reminder_dates": {
|
||||
"description": "An array of datetimes when the user wants to be reminded of the task.",
|
||||
|
@ -7936,7 +7794,11 @@
|
|||
},
|
||||
"repeat_mode": {
|
||||
"description": "Can have three possible values which will trigger when the task is marked as done: 0 = repeats after the amount specified in repeat_after, 1 = repeats all dates each months (ignoring repeat_after), 3 = repeats from the current date rather than the last set date.",
|
||||
"type": "integer"
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.TaskRepeatMode"
|
||||
}
|
||||
]
|
||||
},
|
||||
"start_date": {
|
||||
"description": "When this task starts.",
|
||||
|
@ -7944,7 +7806,11 @@
|
|||
},
|
||||
"subscription": {
|
||||
"description": "The subscription status for the user reading this task. You can only read this property, use the subscription endpoints to modify it.\nWill only returned when retreiving one task.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.Subscription"
|
||||
}
|
||||
]
|
||||
},
|
||||
"task_ids": {
|
||||
"description": "A list of task ids to update",
|
||||
|
@ -7961,9 +7827,7 @@
|
|||
"updated": {
|
||||
"description": "A timestamp when this task was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.DatabaseNotifications": {
|
||||
|
@ -7991,9 +7855,7 @@
|
|||
"read_at": {
|
||||
"description": "When this notification is marked as read, this will be updated with the current timestamp.",
|
||||
"type": "string"
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.Label": {
|
||||
|
@ -8005,7 +7867,11 @@
|
|||
},
|
||||
"created_by": {
|
||||
"description": "The user who created this label",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/user.User"
|
||||
}
|
||||
]
|
||||
},
|
||||
"description": {
|
||||
"description": "The label description.",
|
||||
|
@ -8029,9 +7895,7 @@
|
|||
"updated": {
|
||||
"description": "A timestamp when this label was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.LabelTask": {
|
||||
|
@ -8044,9 +7908,7 @@
|
|||
"label_id": {
|
||||
"description": "The label id you want to associate with a task.",
|
||||
"type": "integer"
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.LabelTaskBulk": {
|
||||
|
@ -8058,9 +7920,7 @@
|
|||
"items": {
|
||||
"$ref": "#/definitions/models.Label"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.LinkSharing": {
|
||||
|
@ -8088,26 +7948,36 @@
|
|||
},
|
||||
"right": {
|
||||
"description": "The right this list is shared with. 0 = Read only, 1 = Read \u0026 Write, 2 = Admin. See the docs for more details.",
|
||||
"type": "integer",
|
||||
"default": 0,
|
||||
"maximum": 2
|
||||
"maximum": 2,
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.Right"
|
||||
}
|
||||
]
|
||||
},
|
||||
"shared_by": {
|
||||
"description": "The user who shared this list",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/user.User"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sharing_type": {
|
||||
"description": "The kind of this link. 0 = undefined, 1 = without password, 2 = with password.",
|
||||
"type": "integer",
|
||||
"default": 0,
|
||||
"maximum": 2
|
||||
"maximum": 2,
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.SharingType"
|
||||
}
|
||||
]
|
||||
},
|
||||
"updated": {
|
||||
"description": "A timestamp when this share was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.List": {
|
||||
|
@ -8156,7 +8026,11 @@
|
|||
},
|
||||
"owner": {
|
||||
"description": "The user who created this list.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/user.User"
|
||||
}
|
||||
]
|
||||
},
|
||||
"position": {
|
||||
"description": "The position this list has when querying all lists. See the tasks.position property on how to use this.",
|
||||
|
@ -8164,7 +8038,11 @@
|
|||
},
|
||||
"subscription": {
|
||||
"description": "The subscription status for the user reading this list. You can only read this property, use the subscription endpoints to modify it.\nWill only returned when retreiving one list.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.Subscription"
|
||||
}
|
||||
]
|
||||
},
|
||||
"title": {
|
||||
"description": "The title of the list. You'll see this in the namespace overview.",
|
||||
|
@ -8175,9 +8053,7 @@
|
|||
"updated": {
|
||||
"description": "A timestamp when this list was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.ListDuplicate": {
|
||||
|
@ -8185,14 +8061,16 @@
|
|||
"properties": {
|
||||
"list": {
|
||||
"description": "The copied list",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.List"
|
||||
}
|
||||
]
|
||||
},
|
||||
"namespace_id": {
|
||||
"description": "The target namespace ID",
|
||||
"type": "integer"
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.ListUser": {
|
||||
|
@ -8208,9 +8086,13 @@
|
|||
},
|
||||
"right": {
|
||||
"description": "The right this user has. 0 = Read only, 1 = Read \u0026 Write, 2 = Admin. See the docs for more details.",
|
||||
"type": "integer",
|
||||
"default": 0,
|
||||
"maximum": 2
|
||||
"maximum": 2,
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.Right"
|
||||
}
|
||||
]
|
||||
},
|
||||
"updated": {
|
||||
"description": "A timestamp when this relation was last updated. You cannot change this value.",
|
||||
|
@ -8219,9 +8101,7 @@
|
|||
"user_id": {
|
||||
"description": "The username.",
|
||||
"type": "string"
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.Message": {
|
||||
|
@ -8259,11 +8139,19 @@
|
|||
},
|
||||
"owner": {
|
||||
"description": "The user who owns this namespace",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/user.User"
|
||||
}
|
||||
]
|
||||
},
|
||||
"subscription": {
|
||||
"description": "The subscription status for the user reading this namespace. You can only read this property, use the subscription endpoints to modify it.\nWill only returned when retreiving one namespace.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.Subscription"
|
||||
}
|
||||
]
|
||||
},
|
||||
"title": {
|
||||
"description": "The name of this namespace.",
|
||||
|
@ -8274,9 +8162,7 @@
|
|||
"updated": {
|
||||
"description": "A timestamp when this namespace was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.NamespaceUser": {
|
||||
|
@ -8292,9 +8178,13 @@
|
|||
},
|
||||
"right": {
|
||||
"description": "The right this user has. 0 = Read only, 1 = Read \u0026 Write, 2 = Admin. See the docs for more details.",
|
||||
"type": "integer",
|
||||
"default": 0,
|
||||
"maximum": 2
|
||||
"maximum": 2,
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.Right"
|
||||
}
|
||||
]
|
||||
},
|
||||
"updated": {
|
||||
"description": "A timestamp when this relation was last updated. You cannot change this value.",
|
||||
|
@ -8303,9 +8193,7 @@
|
|||
"user_id": {
|
||||
"description": "The username.",
|
||||
"type": "string"
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.NamespaceWithLists": {
|
||||
|
@ -8340,11 +8228,19 @@
|
|||
},
|
||||
"owner": {
|
||||
"description": "The user who owns this namespace",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/user.User"
|
||||
}
|
||||
]
|
||||
},
|
||||
"subscription": {
|
||||
"description": "The subscription status for the user reading this namespace. You can only read this property, use the subscription endpoints to modify it.\nWill only returned when retreiving one namespace.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.Subscription"
|
||||
}
|
||||
]
|
||||
},
|
||||
"title": {
|
||||
"description": "The name of this namespace.",
|
||||
|
@ -8355,9 +8251,7 @@
|
|||
"updated": {
|
||||
"description": "A timestamp when this namespace was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.RelatedTaskMap": {
|
||||
|
@ -8369,6 +8263,50 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"models.RelationKind": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"unknown",
|
||||
"subtask",
|
||||
"parenttask",
|
||||
"related",
|
||||
"duplicateof",
|
||||
"duplicates",
|
||||
"blocking",
|
||||
"blocked",
|
||||
"precedes",
|
||||
"follows",
|
||||
"copiedfrom",
|
||||
"copiedto"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"RelationKindUnknown",
|
||||
"RelationKindSubtask",
|
||||
"RelationKindParenttask",
|
||||
"RelationKindRelated",
|
||||
"RelationKindDuplicateOf",
|
||||
"RelationKindDuplicates",
|
||||
"RelationKindBlocking",
|
||||
"RelationKindBlocked",
|
||||
"RelationKindPreceeds",
|
||||
"RelationKindFollows",
|
||||
"RelationKindCopiedFrom",
|
||||
"RelationKindCopiedTo"
|
||||
]
|
||||
},
|
||||
"models.Right": {
|
||||
"type": "integer",
|
||||
"enum": [
|
||||
0,
|
||||
1,
|
||||
2
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"RightRead",
|
||||
"RightWrite",
|
||||
"RightAdmin"
|
||||
]
|
||||
},
|
||||
"models.SavedFilter": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -8382,7 +8320,11 @@
|
|||
},
|
||||
"filters": {
|
||||
"description": "The actual filters this filter contains",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.TaskCollection"
|
||||
}
|
||||
]
|
||||
},
|
||||
"id": {
|
||||
"description": "The unique numeric id of this saved filter",
|
||||
|
@ -8394,7 +8336,11 @@
|
|||
},
|
||||
"owner": {
|
||||
"description": "The user who owns this filter",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/user.User"
|
||||
}
|
||||
]
|
||||
},
|
||||
"title": {
|
||||
"description": "The title of the filter.",
|
||||
|
@ -8405,10 +8351,21 @@
|
|||
"updated": {
|
||||
"description": "A timestamp when this filter was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.SharingType": {
|
||||
"type": "integer",
|
||||
"enum": [
|
||||
0,
|
||||
1,
|
||||
2
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"SharingTypeUnknown",
|
||||
"SharingTypeWithoutPassword",
|
||||
"SharingTypeWithPassword"
|
||||
]
|
||||
},
|
||||
"models.Subscription": {
|
||||
"type": "object",
|
||||
|
@ -8430,10 +8387,12 @@
|
|||
},
|
||||
"user": {
|
||||
"description": "The user who made this subscription",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/user.User"
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.Task": {
|
||||
|
@ -8467,7 +8426,11 @@
|
|||
},
|
||||
"created_by": {
|
||||
"description": "The user who initially created the task.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/user.User"
|
||||
}
|
||||
]
|
||||
},
|
||||
"description": {
|
||||
"description": "The task description.",
|
||||
|
@ -8539,7 +8502,11 @@
|
|||
},
|
||||
"related_tasks": {
|
||||
"description": "All related tasks, grouped by their relation kind",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.RelatedTaskMap"
|
||||
}
|
||||
]
|
||||
},
|
||||
"reminder_dates": {
|
||||
"description": "An array of datetimes when the user wants to be reminded of the task.",
|
||||
|
@ -8554,7 +8521,11 @@
|
|||
},
|
||||
"repeat_mode": {
|
||||
"description": "Can have three possible values which will trigger when the task is marked as done: 0 = repeats after the amount specified in repeat_after, 1 = repeats all dates each months (ignoring repeat_after), 3 = repeats from the current date rather than the last set date.",
|
||||
"type": "integer"
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.TaskRepeatMode"
|
||||
}
|
||||
]
|
||||
},
|
||||
"start_date": {
|
||||
"description": "When this task starts.",
|
||||
|
@ -8562,7 +8533,11 @@
|
|||
},
|
||||
"subscription": {
|
||||
"description": "The subscription status for the user reading this task. You can only read this property, use the subscription endpoints to modify it.\nWill only returned when retreiving one task.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.Subscription"
|
||||
}
|
||||
]
|
||||
},
|
||||
"title": {
|
||||
"description": "The task text. This is what you'll see in the list.",
|
||||
|
@ -8572,9 +8547,7 @@
|
|||
"updated": {
|
||||
"description": "A timestamp when this task was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.TaskAssginee": {
|
||||
|
@ -8585,9 +8558,7 @@
|
|||
},
|
||||
"user_id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.TaskAttachment": {
|
||||
|
@ -8607,9 +8578,7 @@
|
|||
},
|
||||
"task_id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.TaskCollection": {
|
||||
|
@ -8657,9 +8626,7 @@
|
|||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.TaskComment": {
|
||||
|
@ -8679,9 +8646,7 @@
|
|||
},
|
||||
"updated": {
|
||||
"type": "string"
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.TaskRelation": {
|
||||
|
@ -8693,7 +8658,11 @@
|
|||
},
|
||||
"created_by": {
|
||||
"description": "The user who created this relation",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/user.User"
|
||||
}
|
||||
]
|
||||
},
|
||||
"other_task_id": {
|
||||
"description": "The ID of the other task, the task which is being related.",
|
||||
|
@ -8701,15 +8670,30 @@
|
|||
},
|
||||
"relation_kind": {
|
||||
"description": "The kind of the relation.",
|
||||
"type": "string"
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.RelationKind"
|
||||
}
|
||||
]
|
||||
},
|
||||
"task_id": {
|
||||
"description": "The ID of the \"base\" task, the task which has a relation to another.",
|
||||
"type": "integer"
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.TaskRepeatMode": {
|
||||
"type": "integer",
|
||||
"enum": [
|
||||
0,
|
||||
1,
|
||||
2
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"TaskRepeatModeDefault",
|
||||
"TaskRepeatModeMonth",
|
||||
"TaskRepeatModeFromCurrentDate"
|
||||
]
|
||||
},
|
||||
"models.Team": {
|
||||
"type": "object",
|
||||
|
@ -8720,7 +8704,11 @@
|
|||
},
|
||||
"created_by": {
|
||||
"description": "The user who created this team.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/user.User"
|
||||
}
|
||||
]
|
||||
},
|
||||
"description": {
|
||||
"description": "The team's description.",
|
||||
|
@ -8746,9 +8734,7 @@
|
|||
"updated": {
|
||||
"description": "A timestamp when this relation was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.TeamList": {
|
||||
|
@ -8764,9 +8750,13 @@
|
|||
},
|
||||
"right": {
|
||||
"description": "The right this team has. 0 = Read only, 1 = Read \u0026 Write, 2 = Admin. See the docs for more details.",
|
||||
"type": "integer",
|
||||
"default": 0,
|
||||
"maximum": 2
|
||||
"maximum": 2,
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.Right"
|
||||
}
|
||||
]
|
||||
},
|
||||
"team_id": {
|
||||
"description": "The team id.",
|
||||
|
@ -8775,9 +8765,7 @@
|
|||
"updated": {
|
||||
"description": "A timestamp when this relation was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.TeamMember": {
|
||||
|
@ -8798,9 +8786,7 @@
|
|||
"username": {
|
||||
"description": "The username of the member. We use this to prevent automated user id entering.",
|
||||
"type": "string"
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.TeamNamespace": {
|
||||
|
@ -8816,9 +8802,13 @@
|
|||
},
|
||||
"right": {
|
||||
"description": "The right this team has. 0 = Read only, 1 = Read \u0026 Write, 2 = Admin. See the docs for more details.",
|
||||
"type": "integer",
|
||||
"default": 0,
|
||||
"maximum": 2
|
||||
"maximum": 2,
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.Right"
|
||||
}
|
||||
]
|
||||
},
|
||||
"team_id": {
|
||||
"description": "The team id.",
|
||||
|
@ -8827,9 +8817,7 @@
|
|||
"updated": {
|
||||
"description": "A timestamp when this relation was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.TeamUser": {
|
||||
|
@ -8865,8 +8853,7 @@
|
|||
"type": "string",
|
||||
"maxLength": 250,
|
||||
"minLength": 1
|
||||
},
|
||||
"web.Auth": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.TeamWithRight": {
|
||||
|
@ -8878,7 +8865,11 @@
|
|||
},
|
||||
"created_by": {
|
||||
"description": "The user who created this team.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/user.User"
|
||||
}
|
||||
]
|
||||
},
|
||||
"description": {
|
||||
"description": "The team's description.",
|
||||
|
@ -8902,16 +8893,12 @@
|
|||
"minLength": 1
|
||||
},
|
||||
"right": {
|
||||
"description": "The right this team has. 0 = Read only, 1 = Read \u0026 Write, 2 = Admin. See the docs for more details.",
|
||||
"type": "integer",
|
||||
"default": 0
|
||||
"$ref": "#/definitions/models.Right"
|
||||
},
|
||||
"updated": {
|
||||
"description": "A timestamp when this relation was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.UserWithRight": {
|
||||
|
@ -8935,9 +8922,7 @@
|
|||
"type": "string"
|
||||
},
|
||||
"right": {
|
||||
"description": "The right this user has. 0 = Read only, 1 = Read \u0026 Write, 2 = Admin. See the docs for more details.",
|
||||
"type": "integer",
|
||||
"default": 0
|
||||
"$ref": "#/definitions/models.Right"
|
||||
},
|
||||
"updated": {
|
||||
"description": "A timestamp when this task was last updated. You cannot change this value.",
|
||||
|
@ -8948,8 +8933,7 @@
|
|||
"type": "string",
|
||||
"maxLength": 250,
|
||||
"minLength": 1
|
||||
},
|
||||
"web.Auth": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"notifications.DatabaseNotification": {
|
||||
|
@ -9178,8 +9162,7 @@
|
|||
"type": "string",
|
||||
"maxLength": 250,
|
||||
"minLength": 1
|
||||
},
|
||||
"web.Auth": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1.LinkShareAuth": {
|
||||
|
@ -9387,15 +9370,6 @@
|
|||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"wunderlist.Migration": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"description": "Code is the code used to get a user api token",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"securityDefinitions": {
|
||||
|
|
|
@ -58,30 +58,9 @@ definitions:
|
|||
value.
|
||||
type: string
|
||||
created_by:
|
||||
$ref: '#/definitions/user.User'
|
||||
allOf:
|
||||
- $ref: '#/definitions/user.User'
|
||||
description: The user who initially created the bucket.
|
||||
filter_by:
|
||||
description: The field name of the field to filter by
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
filter_comparator:
|
||||
description: The comparator for field and value
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
filter_concat:
|
||||
description: The way all filter conditions are concatenated together, can
|
||||
be either "and" or "or".,
|
||||
type: string
|
||||
filter_include_nulls:
|
||||
description: If set to true, the result will also include null values
|
||||
type: boolean
|
||||
filter_value:
|
||||
description: The value of the field name to filter by
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
id:
|
||||
description: The unique, numeric id of this bucket.
|
||||
type: integer
|
||||
|
@ -97,22 +76,10 @@ definitions:
|
|||
list_id:
|
||||
description: The list this bucket belongs to.
|
||||
type: integer
|
||||
order_by:
|
||||
description: The query parameter to order the items by. This can be either
|
||||
asc or desc, with asc being the default.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
position:
|
||||
description: The position this bucket has when querying all buckets. See the
|
||||
tasks.position property on how to use this.
|
||||
type: number
|
||||
sort_by:
|
||||
description: The query parameter to sort by. This is for ex. done, priority,
|
||||
etc.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
tasks:
|
||||
description: All tasks which belong to this bucket.
|
||||
items:
|
||||
|
@ -126,8 +93,6 @@ definitions:
|
|||
description: A timestamp when this bucket was last updated. You cannot change
|
||||
this value.
|
||||
type: string
|
||||
web.CRUDable: {}
|
||||
web.Rights: {}
|
||||
type: object
|
||||
models.BulkAssignees:
|
||||
properties:
|
||||
|
@ -136,8 +101,6 @@ definitions:
|
|||
items:
|
||||
$ref: '#/definitions/user.User'
|
||||
type: array
|
||||
web.CRUDable: {}
|
||||
web.Rights: {}
|
||||
type: object
|
||||
models.BulkTask:
|
||||
properties:
|
||||
|
@ -163,7 +126,8 @@ definitions:
|
|||
value.
|
||||
type: string
|
||||
created_by:
|
||||
$ref: '#/definitions/user.User'
|
||||
allOf:
|
||||
- $ref: '#/definitions/user.User'
|
||||
description: The user who initially created the task.
|
||||
description:
|
||||
description: The task description.
|
||||
|
@ -228,7 +192,8 @@ definitions:
|
|||
sort by this later.
|
||||
type: integer
|
||||
related_tasks:
|
||||
$ref: '#/definitions/models.RelatedTaskMap'
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.RelatedTaskMap'
|
||||
description: All related tasks, grouped by their relation kind
|
||||
reminder_dates:
|
||||
description: An array of datetimes when the user wants to be reminded of the
|
||||
|
@ -242,16 +207,18 @@ definitions:
|
|||
increase all remindes and the due date by its amount.
|
||||
type: integer
|
||||
repeat_mode:
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.TaskRepeatMode'
|
||||
description: 'Can have three possible values which will trigger when the task
|
||||
is marked as done: 0 = repeats after the amount specified in repeat_after,
|
||||
1 = repeats all dates each months (ignoring repeat_after), 3 = repeats from
|
||||
the current date rather than the last set date.'
|
||||
type: integer
|
||||
start_date:
|
||||
description: When this task starts.
|
||||
type: string
|
||||
subscription:
|
||||
$ref: '#/definitions/models.Subscription'
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.Subscription'
|
||||
description: |-
|
||||
The subscription status for the user reading this task. You can only read this property, use the subscription endpoints to modify it.
|
||||
Will only returned when retreiving one task.
|
||||
|
@ -268,8 +235,6 @@ definitions:
|
|||
description: A timestamp when this task was last updated. You cannot change
|
||||
this value.
|
||||
type: string
|
||||
web.CRUDable: {}
|
||||
web.Rights: {}
|
||||
type: object
|
||||
models.DatabaseNotifications:
|
||||
properties:
|
||||
|
@ -294,8 +259,6 @@ definitions:
|
|||
description: When this notification is marked as read, this will be updated
|
||||
with the current timestamp.
|
||||
type: string
|
||||
web.CRUDable: {}
|
||||
web.Rights: {}
|
||||
type: object
|
||||
models.Label:
|
||||
properties:
|
||||
|
@ -304,7 +267,8 @@ definitions:
|
|||
value.
|
||||
type: string
|
||||
created_by:
|
||||
$ref: '#/definitions/user.User'
|
||||
allOf:
|
||||
- $ref: '#/definitions/user.User'
|
||||
description: The user who created this label
|
||||
description:
|
||||
description: The label description.
|
||||
|
@ -326,8 +290,6 @@ definitions:
|
|||
description: A timestamp when this label was last updated. You cannot change
|
||||
this value.
|
||||
type: string
|
||||
web.CRUDable: {}
|
||||
web.Rights: {}
|
||||
type: object
|
||||
models.LabelTask:
|
||||
properties:
|
||||
|
@ -338,8 +300,6 @@ definitions:
|
|||
label_id:
|
||||
description: The label id you want to associate with a task.
|
||||
type: integer
|
||||
web.CRUDable: {}
|
||||
web.Rights: {}
|
||||
type: object
|
||||
models.LabelTaskBulk:
|
||||
properties:
|
||||
|
@ -348,8 +308,6 @@ definitions:
|
|||
items:
|
||||
$ref: '#/definitions/models.Label'
|
||||
type: array
|
||||
web.CRUDable: {}
|
||||
web.Rights: {}
|
||||
type: object
|
||||
models.LinkSharing:
|
||||
properties:
|
||||
|
@ -372,26 +330,27 @@ definitions:
|
|||
it after the link share has been created.
|
||||
type: string
|
||||
right:
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.Right'
|
||||
default: 0
|
||||
description: The right this list is shared with. 0 = Read only, 1 = Read &
|
||||
Write, 2 = Admin. See the docs for more details.
|
||||
maximum: 2
|
||||
type: integer
|
||||
shared_by:
|
||||
$ref: '#/definitions/user.User'
|
||||
allOf:
|
||||
- $ref: '#/definitions/user.User'
|
||||
description: The user who shared this list
|
||||
sharing_type:
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.SharingType'
|
||||
default: 0
|
||||
description: The kind of this link. 0 = undefined, 1 = without password, 2
|
||||
= with password.
|
||||
maximum: 2
|
||||
type: integer
|
||||
updated:
|
||||
description: A timestamp when this share was last updated. You cannot change
|
||||
this value.
|
||||
type: string
|
||||
web.CRUDable: {}
|
||||
web.Rights: {}
|
||||
type: object
|
||||
models.List:
|
||||
properties:
|
||||
|
@ -433,14 +392,16 @@ definitions:
|
|||
namespace_id:
|
||||
type: integer
|
||||
owner:
|
||||
$ref: '#/definitions/user.User'
|
||||
allOf:
|
||||
- $ref: '#/definitions/user.User'
|
||||
description: The user who created this list.
|
||||
position:
|
||||
description: The position this list has when querying all lists. See the tasks.position
|
||||
property on how to use this.
|
||||
type: number
|
||||
subscription:
|
||||
$ref: '#/definitions/models.Subscription'
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.Subscription'
|
||||
description: |-
|
||||
The subscription status for the user reading this list. You can only read this property, use the subscription endpoints to modify it.
|
||||
Will only returned when retreiving one list.
|
||||
|
@ -453,19 +414,16 @@ definitions:
|
|||
description: A timestamp when this list was last updated. You cannot change
|
||||
this value.
|
||||
type: string
|
||||
web.CRUDable: {}
|
||||
web.Rights: {}
|
||||
type: object
|
||||
models.ListDuplicate:
|
||||
properties:
|
||||
list:
|
||||
$ref: '#/definitions/models.List'
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.List'
|
||||
description: The copied list
|
||||
namespace_id:
|
||||
description: The target namespace ID
|
||||
type: integer
|
||||
web.CRUDable: {}
|
||||
web.Rights: {}
|
||||
type: object
|
||||
models.ListUser:
|
||||
properties:
|
||||
|
@ -477,11 +435,12 @@ definitions:
|
|||
description: The unique, numeric id of this list <-> user relation.
|
||||
type: integer
|
||||
right:
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.Right'
|
||||
default: 0
|
||||
description: The right this user has. 0 = Read only, 1 = Read & Write, 2 =
|
||||
Admin. See the docs for more details.
|
||||
maximum: 2
|
||||
type: integer
|
||||
updated:
|
||||
description: A timestamp when this relation was last updated. You cannot change
|
||||
this value.
|
||||
|
@ -489,8 +448,6 @@ definitions:
|
|||
user_id:
|
||||
description: The username.
|
||||
type: string
|
||||
web.CRUDable: {}
|
||||
web.Rights: {}
|
||||
type: object
|
||||
models.Message:
|
||||
properties:
|
||||
|
@ -518,10 +475,12 @@ definitions:
|
|||
description: Whether or not a namespace is archived.
|
||||
type: boolean
|
||||
owner:
|
||||
$ref: '#/definitions/user.User'
|
||||
allOf:
|
||||
- $ref: '#/definitions/user.User'
|
||||
description: The user who owns this namespace
|
||||
subscription:
|
||||
$ref: '#/definitions/models.Subscription'
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.Subscription'
|
||||
description: |-
|
||||
The subscription status for the user reading this namespace. You can only read this property, use the subscription endpoints to modify it.
|
||||
Will only returned when retreiving one namespace.
|
||||
|
@ -534,8 +493,6 @@ definitions:
|
|||
description: A timestamp when this namespace was last updated. You cannot
|
||||
change this value.
|
||||
type: string
|
||||
web.CRUDable: {}
|
||||
web.Rights: {}
|
||||
type: object
|
||||
models.NamespaceUser:
|
||||
properties:
|
||||
|
@ -547,11 +504,12 @@ definitions:
|
|||
description: The unique, numeric id of this namespace <-> user relation.
|
||||
type: integer
|
||||
right:
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.Right'
|
||||
default: 0
|
||||
description: The right this user has. 0 = Read only, 1 = Read & Write, 2 =
|
||||
Admin. See the docs for more details.
|
||||
maximum: 2
|
||||
type: integer
|
||||
updated:
|
||||
description: A timestamp when this relation was last updated. You cannot change
|
||||
this value.
|
||||
|
@ -559,8 +517,6 @@ definitions:
|
|||
user_id:
|
||||
description: The username.
|
||||
type: string
|
||||
web.CRUDable: {}
|
||||
web.Rights: {}
|
||||
type: object
|
||||
models.NamespaceWithLists:
|
||||
properties:
|
||||
|
@ -586,10 +542,12 @@ definitions:
|
|||
$ref: '#/definitions/models.List'
|
||||
type: array
|
||||
owner:
|
||||
$ref: '#/definitions/user.User'
|
||||
allOf:
|
||||
- $ref: '#/definitions/user.User'
|
||||
description: The user who owns this namespace
|
||||
subscription:
|
||||
$ref: '#/definitions/models.Subscription'
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.Subscription'
|
||||
description: |-
|
||||
The subscription status for the user reading this namespace. You can only read this property, use the subscription endpoints to modify it.
|
||||
Will only returned when retreiving one namespace.
|
||||
|
@ -602,8 +560,6 @@ definitions:
|
|||
description: A timestamp when this namespace was last updated. You cannot
|
||||
change this value.
|
||||
type: string
|
||||
web.CRUDable: {}
|
||||
web.Rights: {}
|
||||
type: object
|
||||
models.RelatedTaskMap:
|
||||
additionalProperties:
|
||||
|
@ -611,6 +567,44 @@ definitions:
|
|||
$ref: '#/definitions/models.Task'
|
||||
type: array
|
||||
type: object
|
||||
models.RelationKind:
|
||||
enum:
|
||||
- unknown
|
||||
- subtask
|
||||
- parenttask
|
||||
- related
|
||||
- duplicateof
|
||||
- duplicates
|
||||
- blocking
|
||||
- blocked
|
||||
- precedes
|
||||
- follows
|
||||
- copiedfrom
|
||||
- copiedto
|
||||
type: string
|
||||
x-enum-varnames:
|
||||
- RelationKindUnknown
|
||||
- RelationKindSubtask
|
||||
- RelationKindParenttask
|
||||
- RelationKindRelated
|
||||
- RelationKindDuplicateOf
|
||||
- RelationKindDuplicates
|
||||
- RelationKindBlocking
|
||||
- RelationKindBlocked
|
||||
- RelationKindPreceeds
|
||||
- RelationKindFollows
|
||||
- RelationKindCopiedFrom
|
||||
- RelationKindCopiedTo
|
||||
models.Right:
|
||||
enum:
|
||||
- 0
|
||||
- 1
|
||||
- 2
|
||||
type: integer
|
||||
x-enum-varnames:
|
||||
- RightRead
|
||||
- RightWrite
|
||||
- RightAdmin
|
||||
models.SavedFilter:
|
||||
properties:
|
||||
created:
|
||||
|
@ -621,7 +615,8 @@ definitions:
|
|||
description: The description of the filter
|
||||
type: string
|
||||
filters:
|
||||
$ref: '#/definitions/models.TaskCollection'
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.TaskCollection'
|
||||
description: The actual filters this filter contains
|
||||
id:
|
||||
description: The unique numeric id of this saved filter
|
||||
|
@ -631,7 +626,8 @@ definitions:
|
|||
a separate namespace together with favorite lists.
|
||||
type: boolean
|
||||
owner:
|
||||
$ref: '#/definitions/user.User'
|
||||
allOf:
|
||||
- $ref: '#/definitions/user.User'
|
||||
description: The user who owns this filter
|
||||
title:
|
||||
description: The title of the filter.
|
||||
|
@ -642,9 +638,17 @@ definitions:
|
|||
description: A timestamp when this filter was last updated. You cannot change
|
||||
this value.
|
||||
type: string
|
||||
web.CRUDable: {}
|
||||
web.Rights: {}
|
||||
type: object
|
||||
models.SharingType:
|
||||
enum:
|
||||
- 0
|
||||
- 1
|
||||
- 2
|
||||
type: integer
|
||||
x-enum-varnames:
|
||||
- SharingTypeUnknown
|
||||
- SharingTypeWithoutPassword
|
||||
- SharingTypeWithPassword
|
||||
models.Subscription:
|
||||
properties:
|
||||
created:
|
||||
|
@ -660,10 +664,9 @@ definitions:
|
|||
description: The numeric ID of the subscription
|
||||
type: integer
|
||||
user:
|
||||
$ref: '#/definitions/user.User'
|
||||
allOf:
|
||||
- $ref: '#/definitions/user.User'
|
||||
description: The user who made this subscription
|
||||
web.CRUDable: {}
|
||||
web.Rights: {}
|
||||
type: object
|
||||
models.Task:
|
||||
properties:
|
||||
|
@ -689,7 +692,8 @@ definitions:
|
|||
value.
|
||||
type: string
|
||||
created_by:
|
||||
$ref: '#/definitions/user.User'
|
||||
allOf:
|
||||
- $ref: '#/definitions/user.User'
|
||||
description: The user who initially created the task.
|
||||
description:
|
||||
description: The task description.
|
||||
|
@ -754,7 +758,8 @@ definitions:
|
|||
sort by this later.
|
||||
type: integer
|
||||
related_tasks:
|
||||
$ref: '#/definitions/models.RelatedTaskMap'
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.RelatedTaskMap'
|
||||
description: All related tasks, grouped by their relation kind
|
||||
reminder_dates:
|
||||
description: An array of datetimes when the user wants to be reminded of the
|
||||
|
@ -768,16 +773,18 @@ definitions:
|
|||
increase all remindes and the due date by its amount.
|
||||
type: integer
|
||||
repeat_mode:
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.TaskRepeatMode'
|
||||
description: 'Can have three possible values which will trigger when the task
|
||||
is marked as done: 0 = repeats after the amount specified in repeat_after,
|
||||
1 = repeats all dates each months (ignoring repeat_after), 3 = repeats from
|
||||
the current date rather than the last set date.'
|
||||
type: integer
|
||||
start_date:
|
||||
description: When this task starts.
|
||||
type: string
|
||||
subscription:
|
||||
$ref: '#/definitions/models.Subscription'
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.Subscription'
|
||||
description: |-
|
||||
The subscription status for the user reading this task. You can only read this property, use the subscription endpoints to modify it.
|
||||
Will only returned when retreiving one task.
|
||||
|
@ -789,8 +796,6 @@ definitions:
|
|||
description: A timestamp when this task was last updated. You cannot change
|
||||
this value.
|
||||
type: string
|
||||
web.CRUDable: {}
|
||||
web.Rights: {}
|
||||
type: object
|
||||
models.TaskAssginee:
|
||||
properties:
|
||||
|
@ -798,8 +803,6 @@ definitions:
|
|||
type: string
|
||||
user_id:
|
||||
type: integer
|
||||
web.CRUDable: {}
|
||||
web.Rights: {}
|
||||
type: object
|
||||
models.TaskAttachment:
|
||||
properties:
|
||||
|
@ -813,8 +816,6 @@ definitions:
|
|||
type: integer
|
||||
task_id:
|
||||
type: integer
|
||||
web.CRUDable: {}
|
||||
web.Rights: {}
|
||||
type: object
|
||||
models.TaskCollection:
|
||||
properties:
|
||||
|
@ -852,8 +853,6 @@ definitions:
|
|||
items:
|
||||
type: string
|
||||
type: array
|
||||
web.CRUDable: {}
|
||||
web.Rights: {}
|
||||
type: object
|
||||
models.TaskComment:
|
||||
properties:
|
||||
|
@ -867,8 +866,6 @@ definitions:
|
|||
type: integer
|
||||
updated:
|
||||
type: string
|
||||
web.CRUDable: {}
|
||||
web.Rights: {}
|
||||
type: object
|
||||
models.TaskRelation:
|
||||
properties:
|
||||
|
@ -877,20 +874,30 @@ definitions:
|
|||
value.
|
||||
type: string
|
||||
created_by:
|
||||
$ref: '#/definitions/user.User'
|
||||
allOf:
|
||||
- $ref: '#/definitions/user.User'
|
||||
description: The user who created this relation
|
||||
other_task_id:
|
||||
description: The ID of the other task, the task which is being related.
|
||||
type: integer
|
||||
relation_kind:
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.RelationKind'
|
||||
description: The kind of the relation.
|
||||
type: string
|
||||
task_id:
|
||||
description: The ID of the "base" task, the task which has a relation to another.
|
||||
type: integer
|
||||
web.CRUDable: {}
|
||||
web.Rights: {}
|
||||
type: object
|
||||
models.TaskRepeatMode:
|
||||
enum:
|
||||
- 0
|
||||
- 1
|
||||
- 2
|
||||
type: integer
|
||||
x-enum-varnames:
|
||||
- TaskRepeatModeDefault
|
||||
- TaskRepeatModeMonth
|
||||
- TaskRepeatModeFromCurrentDate
|
||||
models.Team:
|
||||
properties:
|
||||
created:
|
||||
|
@ -898,7 +905,8 @@ definitions:
|
|||
this value.
|
||||
type: string
|
||||
created_by:
|
||||
$ref: '#/definitions/user.User'
|
||||
allOf:
|
||||
- $ref: '#/definitions/user.User'
|
||||
description: The user who created this team.
|
||||
description:
|
||||
description: The team's description.
|
||||
|
@ -920,8 +928,6 @@ definitions:
|
|||
description: A timestamp when this relation was last updated. You cannot change
|
||||
this value.
|
||||
type: string
|
||||
web.CRUDable: {}
|
||||
web.Rights: {}
|
||||
type: object
|
||||
models.TeamList:
|
||||
properties:
|
||||
|
@ -933,11 +939,12 @@ definitions:
|
|||
description: The unique, numeric id of this list <-> team relation.
|
||||
type: integer
|
||||
right:
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.Right'
|
||||
default: 0
|
||||
description: The right this team has. 0 = Read only, 1 = Read & Write, 2 =
|
||||
Admin. See the docs for more details.
|
||||
maximum: 2
|
||||
type: integer
|
||||
team_id:
|
||||
description: The team id.
|
||||
type: integer
|
||||
|
@ -945,8 +952,6 @@ definitions:
|
|||
description: A timestamp when this relation was last updated. You cannot change
|
||||
this value.
|
||||
type: string
|
||||
web.CRUDable: {}
|
||||
web.Rights: {}
|
||||
type: object
|
||||
models.TeamMember:
|
||||
properties:
|
||||
|
@ -965,8 +970,6 @@ definitions:
|
|||
description: The username of the member. We use this to prevent automated
|
||||
user id entering.
|
||||
type: string
|
||||
web.CRUDable: {}
|
||||
web.Rights: {}
|
||||
type: object
|
||||
models.TeamNamespace:
|
||||
properties:
|
||||
|
@ -978,11 +981,12 @@ definitions:
|
|||
description: The unique, numeric id of this namespace <-> team relation.
|
||||
type: integer
|
||||
right:
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.Right'
|
||||
default: 0
|
||||
description: The right this team has. 0 = Read only, 1 = Read & Write, 2 =
|
||||
Admin. See the docs for more details.
|
||||
maximum: 2
|
||||
type: integer
|
||||
team_id:
|
||||
description: The team id.
|
||||
type: integer
|
||||
|
@ -990,8 +994,6 @@ definitions:
|
|||
description: A timestamp when this relation was last updated. You cannot change
|
||||
this value.
|
||||
type: string
|
||||
web.CRUDable: {}
|
||||
web.Rights: {}
|
||||
type: object
|
||||
models.TeamUser:
|
||||
properties:
|
||||
|
@ -1022,7 +1024,6 @@ definitions:
|
|||
maxLength: 250
|
||||
minLength: 1
|
||||
type: string
|
||||
web.Auth: {}
|
||||
type: object
|
||||
models.TeamWithRight:
|
||||
properties:
|
||||
|
@ -1031,7 +1032,8 @@ definitions:
|
|||
this value.
|
||||
type: string
|
||||
created_by:
|
||||
$ref: '#/definitions/user.User'
|
||||
allOf:
|
||||
- $ref: '#/definitions/user.User'
|
||||
description: The user who created this team.
|
||||
description:
|
||||
description: The team's description.
|
||||
|
@ -1050,16 +1052,11 @@ definitions:
|
|||
minLength: 1
|
||||
type: string
|
||||
right:
|
||||
default: 0
|
||||
description: The right this team has. 0 = Read only, 1 = Read & Write, 2 =
|
||||
Admin. See the docs for more details.
|
||||
type: integer
|
||||
$ref: '#/definitions/models.Right'
|
||||
updated:
|
||||
description: A timestamp when this relation was last updated. You cannot change
|
||||
this value.
|
||||
type: string
|
||||
web.CRUDable: {}
|
||||
web.Rights: {}
|
||||
type: object
|
||||
models.UserWithRight:
|
||||
properties:
|
||||
|
@ -1078,10 +1075,7 @@ definitions:
|
|||
description: The full name of the user.
|
||||
type: string
|
||||
right:
|
||||
default: 0
|
||||
description: The right this user has. 0 = Read only, 1 = Read & Write, 2 =
|
||||
Admin. See the docs for more details.
|
||||
type: integer
|
||||
$ref: '#/definitions/models.Right'
|
||||
updated:
|
||||
description: A timestamp when this task was last updated. You cannot change
|
||||
this value.
|
||||
|
@ -1091,7 +1085,6 @@ definitions:
|
|||
maxLength: 250
|
||||
minLength: 1
|
||||
type: string
|
||||
web.Auth: {}
|
||||
type: object
|
||||
notifications.DatabaseNotification:
|
||||
properties:
|
||||
|
@ -1258,7 +1251,6 @@ definitions:
|
|||
maxLength: 250
|
||||
minLength: 1
|
||||
type: string
|
||||
web.Auth: {}
|
||||
type: object
|
||||
v1.LinkShareAuth:
|
||||
properties:
|
||||
|
@ -1407,12 +1399,6 @@ definitions:
|
|||
message:
|
||||
type: string
|
||||
type: object
|
||||
wunderlist.Migration:
|
||||
properties:
|
||||
code:
|
||||
description: Code is the code used to get a user api token
|
||||
type: string
|
||||
type: object
|
||||
info:
|
||||
contact:
|
||||
email: hello@vikunja.io
|
||||
|
@ -1509,7 +1495,7 @@ paths:
|
|||
parameters:
|
||||
- description: Unsplash Image ID
|
||||
in: path
|
||||
name: thumb
|
||||
name: image
|
||||
required: true
|
||||
type: integer
|
||||
produces:
|
||||
|
@ -1539,7 +1525,7 @@ paths:
|
|||
parameters:
|
||||
- description: Unsplash Image ID
|
||||
in: path
|
||||
name: thumb
|
||||
name: image
|
||||
required: true
|
||||
type: integer
|
||||
produces:
|
||||
|
@ -3490,77 +3476,6 @@ paths:
|
|||
summary: Get migration status
|
||||
tags:
|
||||
- migration
|
||||
/migration/wunderlist/auth:
|
||||
get:
|
||||
description: Returns the auth url where the user needs to get its auth code.
|
||||
This code can then be used to migrate everything from wunderlist to Vikunja.
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: The auth url.
|
||||
schema:
|
||||
$ref: '#/definitions/handler.AuthURL'
|
||||
"500":
|
||||
description: Internal server error
|
||||
schema:
|
||||
$ref: '#/definitions/models.Message'
|
||||
security:
|
||||
- JWTKeyAuth: []
|
||||
summary: Get the auth url from wunderlist
|
||||
tags:
|
||||
- migration
|
||||
/migration/wunderlist/migrate:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Migrates all folders, lists, tasks, notes, reminders, subtasks
|
||||
and files from wunderlist to vikunja.
|
||||
parameters:
|
||||
- description: The auth code previously obtained from the auth url. See the
|
||||
docs for /migration/wunderlist/auth.
|
||||
in: body
|
||||
name: migrationCode
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/wunderlist.Migration'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: A message telling you everything was migrated successfully.
|
||||
schema:
|
||||
$ref: '#/definitions/models.Message'
|
||||
"500":
|
||||
description: Internal server error
|
||||
schema:
|
||||
$ref: '#/definitions/models.Message'
|
||||
security:
|
||||
- JWTKeyAuth: []
|
||||
summary: Migrate all lists, tasks etc. from wunderlist
|
||||
tags:
|
||||
- migration
|
||||
/migration/wunderlist/status:
|
||||
get:
|
||||
description: Returns if the current user already did the migation or not. This
|
||||
is useful to show a confirmation message in the frontend if the user is trying
|
||||
to do the same migration again.
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: The migration status
|
||||
schema:
|
||||
$ref: '#/definitions/migration.Status'
|
||||
"500":
|
||||
description: Internal server error
|
||||
schema:
|
||||
$ref: '#/definitions/models.Message'
|
||||
security:
|
||||
- JWTKeyAuth: []
|
||||
summary: Get migration status
|
||||
tags:
|
||||
- migration
|
||||
/namespace/{id}:
|
||||
post:
|
||||
consumes:
|
||||
|
@ -5247,7 +5162,7 @@ paths:
|
|||
type: string
|
||||
- description: The id of the other task.
|
||||
in: path
|
||||
name: otherTaskID
|
||||
name: otherTaskId
|
||||
required: true
|
||||
type: integer
|
||||
produces:
|
||||
|
|
|
@ -419,7 +419,7 @@ func GetUserFromClaims(claims jwt.MapClaims) (user *User, err error) {
|
|||
}
|
||||
|
||||
// UpdateUser updates a user
|
||||
func UpdateUser(s *xorm.Session, user *User) (updatedUser *User, err error) {
|
||||
func UpdateUser(s *xorm.Session, user *User, forceOverride bool) (updatedUser *User, err error) {
|
||||
|
||||
// Check if it exists
|
||||
theUser, err := GetUserWithEmail(s, &User{ID: user.ID})
|
||||
|
@ -442,7 +442,7 @@ func UpdateUser(s *xorm.Session, user *User) (updatedUser *User, err error) {
|
|||
}
|
||||
|
||||
// Check if we have a name
|
||||
if user.Name == "" {
|
||||
if user.Name == "" && !forceOverride {
|
||||
user.Name = theUser.Name
|
||||
}
|
||||
|
||||
|
|
|
@ -292,7 +292,7 @@ func TestUpdateUser(t *testing.T) {
|
|||
ID: 1,
|
||||
Password: "LoremIpsum",
|
||||
Email: "testing@example.com",
|
||||
})
|
||||
}, false)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", uuser.Password) // Password should not change
|
||||
assert.Equal(t, "user1", uuser.Username) // Username should not change either
|
||||
|
@ -305,7 +305,7 @@ func TestUpdateUser(t *testing.T) {
|
|||
uuser, err := UpdateUser(s, &User{
|
||||
ID: 1,
|
||||
Username: "changedname",
|
||||
})
|
||||
}, false)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "$2a$14$dcadBoMBL9jQoOcZK8Fju.cy0Ptx2oZECkKLnaa8ekRoTFe1w7To.", uuser.Password) // Password should not change
|
||||
assert.Equal(t, "changedname", uuser.Username)
|
||||
|
@ -317,7 +317,7 @@ func TestUpdateUser(t *testing.T) {
|
|||
|
||||
_, err := UpdateUser(s, &User{
|
||||
ID: 99999,
|
||||
})
|
||||
}, false)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, IsErrUserDoesNotExist(err))
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue
Block a user