forked from vikunja/vikunja
Compare commits
1 Commits
main
...
renovate/a
Author | SHA1 | Date |
---|---|---|
renovate | 15bbbf3788 |
102
.drone.yml
102
.drone.yml
|
@ -1,6 +1,5 @@
|
|||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: testing
|
||||
|
||||
workspace:
|
||||
|
@ -112,7 +111,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: always
|
||||
pull: true
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
commands:
|
||||
|
@ -123,7 +122,7 @@ steps:
|
|||
|
||||
- name: build
|
||||
image: vikunja/golang-build:latest
|
||||
pull: always
|
||||
pull: true
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
depends_on: [ mage ]
|
||||
|
@ -134,7 +133,7 @@ steps:
|
|||
|
||||
- name: lint
|
||||
image: golang:1.19-alpine
|
||||
pull: always
|
||||
pull: true
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
depends_on: [ build ]
|
||||
|
@ -148,7 +147,7 @@ steps:
|
|||
|
||||
- name: test-migration-prepare
|
||||
image: kolaente/toolbox:latest
|
||||
pull: always
|
||||
pull: true
|
||||
commands:
|
||||
# Get the latest version
|
||||
- wget https://dl.vikunja.io/api/unstable/vikunja-unstable-linux-amd64-full.zip -q -O vikunja-latest.zip
|
||||
|
@ -156,7 +155,7 @@ steps:
|
|||
|
||||
- name: test-migration-sqlite
|
||||
image: vikunja/golang-build:latest
|
||||
pull: always
|
||||
pull: true
|
||||
depends_on: [ test-migration-prepare, build ]
|
||||
environment:
|
||||
VIKUNJA_DATABASE_TYPE: sqlite
|
||||
|
@ -175,7 +174,7 @@ steps:
|
|||
|
||||
- name: test-migration-mysql
|
||||
image: vikunja/golang-build:latest
|
||||
pull: always
|
||||
pull: true
|
||||
depends_on: [ test-migration-prepare, build ]
|
||||
environment:
|
||||
VIKUNJA_DATABASE_TYPE: mysql
|
||||
|
@ -194,7 +193,7 @@ steps:
|
|||
|
||||
- name: test-migration-psql
|
||||
image: vikunja/golang-build:latest
|
||||
pull: always
|
||||
pull: true
|
||||
depends_on: [ test-migration-prepare, build ]
|
||||
environment:
|
||||
VIKUNJA_DATABASE_TYPE: postgres
|
||||
|
@ -214,7 +213,7 @@ steps:
|
|||
|
||||
- name: test
|
||||
image: vikunja/golang-build:latest
|
||||
pull: always
|
||||
pull: true
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
commands:
|
||||
|
@ -225,7 +224,7 @@ steps:
|
|||
|
||||
- name: test-sqlite
|
||||
image: vikunja/golang-build:latest
|
||||
pull: always
|
||||
pull: true
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
VIKUNJA_TESTS_USE_CONFIG: 1
|
||||
|
@ -242,7 +241,7 @@ steps:
|
|||
|
||||
- name: test-mysql
|
||||
image: vikunja/golang-build:latest
|
||||
pull: always
|
||||
pull: true
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
VIKUNJA_TESTS_USE_CONFIG: 1
|
||||
|
@ -259,7 +258,7 @@ steps:
|
|||
|
||||
- name: test-postgres
|
||||
image: vikunja/golang-build:latest
|
||||
pull: always
|
||||
pull: true
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
VIKUNJA_TESTS_USE_CONFIG: 1
|
||||
|
@ -277,7 +276,7 @@ steps:
|
|||
|
||||
- name: integration-test
|
||||
image: vikunja/golang-build:latest
|
||||
pull: always
|
||||
pull: true
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
commands:
|
||||
|
@ -288,7 +287,7 @@ steps:
|
|||
|
||||
- name: integration-test-sqlite
|
||||
image: vikunja/golang-build:latest
|
||||
pull: always
|
||||
pull: true
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
VIKUNJA_TESTS_USE_CONFIG: 1
|
||||
|
@ -305,7 +304,7 @@ steps:
|
|||
|
||||
- name: integration-test-mysql
|
||||
image: vikunja/golang-build:latest
|
||||
pull: always
|
||||
pull: true
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
VIKUNJA_TESTS_USE_CONFIG: 1
|
||||
|
@ -322,7 +321,7 @@ steps:
|
|||
|
||||
- name: integration-test-postgres
|
||||
image: vikunja/golang-build:latest
|
||||
pull: always
|
||||
pull: true
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
VIKUNJA_TESTS_USE_CONFIG: 1
|
||||
|
@ -344,15 +343,14 @@ steps:
|
|||
########
|
||||
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: release
|
||||
|
||||
depends_on:
|
||||
- testing
|
||||
|
||||
workspace:
|
||||
base: /source
|
||||
path: /
|
||||
base: /go
|
||||
path: src/code.vikunja.io/api
|
||||
|
||||
trigger:
|
||||
ref:
|
||||
|
@ -370,7 +368,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: always
|
||||
pull: true
|
||||
environment:
|
||||
GOPROXY: 'https://goproxy.kolaente.de'
|
||||
commands:
|
||||
|
@ -380,7 +378,7 @@ steps:
|
|||
|
||||
- name: before-static-build
|
||||
image: techknowlogick/xgo:latest
|
||||
pull: always
|
||||
pull: true
|
||||
commands:
|
||||
- export PATH=$PATH:$GOPATH/bin
|
||||
- go install github.com/magefile/mage
|
||||
|
@ -389,7 +387,7 @@ steps:
|
|||
|
||||
- name: static-build-windows
|
||||
image: techknowlogick/xgo:latest
|
||||
pull: always
|
||||
pull: true
|
||||
environment:
|
||||
# This path does not exist. However, when we set the gopath to /go, the build fails. Not sure why.
|
||||
# Leaving this here until we know how to resolve this properly.
|
||||
|
@ -402,7 +400,7 @@ steps:
|
|||
|
||||
- name: static-build-linux
|
||||
image: techknowlogick/xgo:latest
|
||||
pull: always
|
||||
pull: true
|
||||
environment:
|
||||
# This path does not exist. However, when we set the gopath to /go, the build fails. Not sure why.
|
||||
# Leaving this here until we know how to resolve this properly.
|
||||
|
@ -415,7 +413,7 @@ steps:
|
|||
|
||||
- name: static-build-darwin
|
||||
image: techknowlogick/xgo:latest
|
||||
pull: always
|
||||
pull: true
|
||||
environment:
|
||||
# This path does not exist. However, when we set the gopath to /go, the build fails. Not sure why.
|
||||
# Leaving this here until we know how to resolve this properly.
|
||||
|
@ -428,7 +426,7 @@ steps:
|
|||
|
||||
- name: after-build-compress
|
||||
image: kolaente/upx
|
||||
pull: always
|
||||
pull: true
|
||||
depends_on:
|
||||
- static-build-windows
|
||||
- static-build-linux
|
||||
|
@ -438,7 +436,7 @@ steps:
|
|||
|
||||
- name: after-build-static
|
||||
image: techknowlogick/xgo:latest
|
||||
pull: always
|
||||
pull: true
|
||||
depends_on:
|
||||
- after-build-compress
|
||||
commands:
|
||||
|
@ -450,7 +448,7 @@ steps:
|
|||
|
||||
- name: sign-release
|
||||
image: plugins/gpgsign:1
|
||||
pull: always
|
||||
pull: true
|
||||
depends_on: [ after-build-static ]
|
||||
settings:
|
||||
key:
|
||||
|
@ -464,7 +462,7 @@ steps:
|
|||
# Push the releases to our pseudo-s3-bucket
|
||||
- name: release-latest
|
||||
image: plugins/s3
|
||||
pull: always
|
||||
pull: true
|
||||
settings:
|
||||
bucket: vikunja-releases
|
||||
access_key:
|
||||
|
@ -486,7 +484,7 @@ steps:
|
|||
|
||||
- name: release-version
|
||||
image: plugins/s3
|
||||
pull: always
|
||||
pull: true
|
||||
settings:
|
||||
bucket: vikunja-releases
|
||||
access_key:
|
||||
|
@ -506,8 +504,8 @@ steps:
|
|||
|
||||
# Build os packages and push it to our bucket
|
||||
- name: build-os-packages-unstable
|
||||
image: goreleaser/nfpm:v2.24.0
|
||||
pull: always
|
||||
image: goreleaser/nfpm
|
||||
pull: true
|
||||
commands:
|
||||
- apk add git go
|
||||
- ./mage-static release:packages
|
||||
|
@ -519,11 +517,11 @@ steps:
|
|||
- main
|
||||
event:
|
||||
- push
|
||||
depends_on: [ after-build-compress ]
|
||||
depends_on: [ static-build-linux ]
|
||||
|
||||
- name: build-os-packages-version
|
||||
image: goreleaser/nfpm:v2.24.0
|
||||
pull: always
|
||||
image: goreleaser/nfpm
|
||||
pull: true
|
||||
commands:
|
||||
- apk add git go
|
||||
- ./mage-static release:packages
|
||||
|
@ -533,12 +531,12 @@ steps:
|
|||
when:
|
||||
event:
|
||||
- tag
|
||||
depends_on: [ after-build-compress ]
|
||||
depends_on: [ static-build-linux ]
|
||||
|
||||
# Push the os releases to our pseudo-s3-bucket
|
||||
- name: release-os-latest
|
||||
image: plugins/s3
|
||||
pull: always
|
||||
pull: true
|
||||
settings:
|
||||
bucket: vikunja-releases
|
||||
access_key:
|
||||
|
@ -560,7 +558,7 @@ steps:
|
|||
|
||||
- name: release-os-version
|
||||
image: plugins/s3
|
||||
pull: always
|
||||
pull: true
|
||||
settings:
|
||||
bucket: vikunja-releases
|
||||
access_key:
|
||||
|
@ -580,7 +578,6 @@ steps:
|
|||
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: deploy-docs
|
||||
|
||||
workspace:
|
||||
|
@ -599,7 +596,8 @@ trigger:
|
|||
steps:
|
||||
- name: theme
|
||||
image: kolaente/toolbox
|
||||
pull: always
|
||||
pull: true
|
||||
group: build-static
|
||||
commands:
|
||||
- mkdir docs/themes/vikunja -p
|
||||
- cd docs/themes/vikunja
|
||||
|
@ -607,8 +605,8 @@ steps:
|
|||
- tar -xzf vikunja-theme.tar.gz
|
||||
|
||||
- name: build
|
||||
image: klakegg/hugo:0.107.0
|
||||
pull: always
|
||||
image: klakegg/hugo:0.104.2
|
||||
pull: true
|
||||
commands:
|
||||
- cd docs
|
||||
- hugo
|
||||
|
@ -616,7 +614,7 @@ steps:
|
|||
|
||||
- name: docker
|
||||
image: plugins/docker
|
||||
pull: always
|
||||
pull: true
|
||||
settings:
|
||||
username:
|
||||
from_secret: docker_username
|
||||
|
@ -648,7 +646,7 @@ steps:
|
|||
- name: docker-unstable
|
||||
image: thegeeklab/drone-docker-buildx
|
||||
privileged: true
|
||||
pull: always
|
||||
pull: true
|
||||
settings:
|
||||
username:
|
||||
from_secret: docker_username
|
||||
|
@ -667,34 +665,24 @@ steps:
|
|||
ref:
|
||||
- refs/heads/main
|
||||
|
||||
- name: generate-tags
|
||||
image: thegeeklab/docker-autotag
|
||||
environment:
|
||||
DOCKER_AUTOTAG_VERSION: ${DRONE_TAG}
|
||||
DOCKER_AUTOTAG_EXTRA_TAGS: latest
|
||||
DOCKER_AUTOTAG_OUTPUT_FILE: .tags
|
||||
depends_on: [ fetch-tags ]
|
||||
when:
|
||||
ref:
|
||||
- "refs/tags/**"
|
||||
|
||||
- name: docker-release
|
||||
image: thegeeklab/drone-docker-buildx
|
||||
privileged: true
|
||||
pull: always
|
||||
pull: true
|
||||
settings:
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
repo: vikunja/api
|
||||
auto_tag: true
|
||||
platforms:
|
||||
- linux/386
|
||||
- linux/amd64
|
||||
- linux/arm/v6
|
||||
- linux/arm/v7
|
||||
- linux/arm64/v8
|
||||
depends_on: [ generate-tags ]
|
||||
depends_on: [ fetch-tags ]
|
||||
when:
|
||||
ref:
|
||||
- "refs/tags/**"
|
||||
|
@ -731,6 +719,6 @@ steps:
|
|||
- failure
|
||||
---
|
||||
kind: signature
|
||||
hmac: 8255925defaacaa9e67871cf8376628925da0ff0996752b71bb6c3c2c5e9b8eb
|
||||
hmac: 768d54fc8433705fb63f754adf19536a16152a4046a040afdbcbb7df60614056
|
||||
|
||||
...
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
name: Bug Report
|
||||
description: Found something you weren't expecting? Report it here!
|
||||
labels: kind/bug
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
NOTE: If your issue is a security concern, please send an email to security@vikunja.io instead of opening a public issue.
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Please fill out this issue template to report a bug.
|
||||
|
||||
1. If you want to propose a new feature, please open a discussion thread in the forum: https://community.vikunja.io
|
||||
2. Please ask questions or configuration/deploy problems on our [Matrix Room](https://matrix.to/#/#vikunja:matrix.org) or forum (https://community.vikunja.io).
|
||||
3. Make sure you are using the latest release and
|
||||
take a moment to check that your issue hasn't been reported before.
|
||||
4. Please give all relevant information below for bug reports, because
|
||||
incomplete details will be handled as an invalid report and closed.
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
description: |
|
||||
Please provide a description of your issue here, with a URL if you were able to reproduce the issue (see below).
|
||||
- type: input
|
||||
id: frontend-version
|
||||
attributes:
|
||||
label: Vikunja Frontend Version
|
||||
description: Vikunja frontend version (or commit reference) of your instance
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: api-version
|
||||
attributes:
|
||||
label: Vikunja API Version
|
||||
description: Vikunja API version (or commit reference) of your instance
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: browser-version
|
||||
attributes:
|
||||
label: Browser and version
|
||||
description: If your issue is related to a frontend problem, please provide the browser and version you used to reproduce it.
|
||||
- type: dropdown
|
||||
id: can-reproduce
|
||||
attributes:
|
||||
label: Can you reproduce the bug on the Vikunja demo site?
|
||||
options:
|
||||
- "Yes"
|
||||
- "No"
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: screenshots
|
||||
attributes:
|
||||
label: Screenshots
|
||||
description: If this issue involves the Web Interface, please provide one or more screenshots
|
84
CHANGELOG.md
84
CHANGELOG.md
|
@ -7,90 +7,6 @@ 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
|
||||
|
|
56
Dockerfile
56
Dockerfile
|
@ -1,39 +1,51 @@
|
|||
# syntax=docker/dockerfile:1
|
||||
# ┬─┐┬ ┐o┬ ┬─┐
|
||||
# │─││ │││ │ │
|
||||
# ┘─┘┘─┘┘┘─┘┘─┘
|
||||
|
||||
FROM techknowlogick/xgo:go-1.19.2 AS builder
|
||||
##############
|
||||
# Build stage
|
||||
FROM --platform=$BUILDPLATFORM techknowlogick/xgo:latest AS build-env
|
||||
|
||||
RUN go install github.com/magefile/mage@latest && \
|
||||
mv /go/bin/mage /usr/local/go/bin
|
||||
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
|
||||
COPY . ./
|
||||
|
||||
ARG TARGETOS TARGETARCH TARGETVARIANT
|
||||
# Checkout version if set
|
||||
RUN if [ -n "${VIKUNJA_VERSION}" ]; then git checkout "${VIKUNJA_VERSION}"; fi && \
|
||||
mage build:clean && \
|
||||
mage release:xgo $TARGETOS/$TARGETARCH/$TARGETVARIANT
|
||||
|
||||
RUN mage build:clean && \
|
||||
mage release:xgo "${TARGETOS}/${TARGETARCH}/${TARGETVARIANT}"
|
||||
|
||||
# ┬─┐┬ ┐┌┐┐┌┐┐┬─┐┬─┐
|
||||
# │┬┘│ │││││││├─ │┬┘
|
||||
# ┘└┘┘─┘┘└┘┘└┘┴─┘┘└┘
|
||||
|
||||
###################
|
||||
# The actual image
|
||||
# Note: I wanted to use the scratch image here, but unfortunatly the go-sqlite bindings require cgo and
|
||||
# because of this, the container would not start when I compiled the image without cgo.
|
||||
FROM alpine:3.16 AS runner
|
||||
FROM alpine:3.17
|
||||
LABEL maintainer="maintainers@vikunja.io"
|
||||
WORKDIR /app/vikunja
|
||||
ENTRYPOINT [ "/sbin/tini", "-g", "--", "/entrypoint.sh" ]
|
||||
|
||||
WORKDIR /app/vikunja/
|
||||
COPY --from=build-env /build/vikunja-* vikunja
|
||||
ENV VIKUNJA_SERVICE_ROOTPATH=/app/vikunja/
|
||||
|
||||
# Dynamic permission changing stuff
|
||||
ENV PUID 1000
|
||||
ENV PGID 1000
|
||||
RUN apk --no-cache add shadow && \
|
||||
addgroup -g ${PGID} vikunja && \
|
||||
adduser -s /bin/sh -D -G vikunja -u ${PUID} vikunja -h /app/vikunja -H && \
|
||||
chown vikunja -R /app/vikunja
|
||||
COPY run.sh /run.sh
|
||||
|
||||
RUN apk --update --no-cache add tzdata tini
|
||||
COPY docker/entrypoint.sh /entrypoint.sh
|
||||
RUN chmod 0755 /entrypoint.sh && mkdir files
|
||||
# Add time zone data
|
||||
RUN apk --no-cache add tzdata
|
||||
|
||||
COPY --from=builder /build/vikunja-* vikunja
|
||||
# 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.2-brightgreen.svg)](https://dl.vikunja.io)
|
||||
[![Download](https://img.shields.io/badge/download-v0.20.1-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,6 +191,21 @@ 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
|
||||
|
@ -296,9 +311,6 @@ auth:
|
|||
- name:
|
||||
# The auth url to send users to if they want to authenticate using OpenID Connect.
|
||||
authurl:
|
||||
# The oidc logouturl that users will be redirected to on logout.
|
||||
# Leave empty or delete key, if you do not want to be redirected.
|
||||
logouturl:
|
||||
# The client ID used to authenticate Vikunja at the OpenID Connect provider.
|
||||
clientid:
|
||||
# The client secret used to authenticate Vikunja at the OpenID Connect provider.
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
#!/usr/bin/env sh
|
||||
set -e
|
||||
|
||||
if [ -n "$PUID" ] && [ "$PUID" -ne 0 ] && \
|
||||
[ -n "$PGID" ] && [ "$PGID" -ne 0 ] ; then
|
||||
echo "info: creating the new user vikunja with $PUID:$PGID"
|
||||
addgroup -g "$PGID" vikunja
|
||||
adduser -s /bin/sh -D -G vikunja -u "$PUID" vikunja -h /app/vikunja -H
|
||||
chown -R vikunja:vikunja ./
|
||||
su -pc /app/vikunja/vikunja - vikunja "$@"
|
||||
else
|
||||
echo "info: creation of non-root user is skipped"
|
||||
exec /app/vikunja/vikunja "$@"
|
||||
fi
|
||||
|
|
@ -969,6 +969,17 @@ 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 database.
|
||||
This docker compose configuration will run Vikunja with backend and frontend with a mariadb as 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.
|
||||
|
||||
Note that you need to change the `VIKUNJA_API_URL` environment variable to the ip (the docker host you're running this on)
|
||||
Not 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 assumptions here which you'll most likely need to adjust for your traefik setup:
|
||||
We also make a few assumtions here which you'll most likely need to adjust for your traefik setup:
|
||||
|
||||
* Your domain is `vikunja.example.com`
|
||||
* The entrypoint you want to make vikunja available from is called `https`
|
||||
|
@ -398,7 +398,7 @@ docker main folders:
|
|||
* vikunja
|
||||
* mariadb
|
||||
|
||||
Synology has its own GUI for managing Docker containers... But it's easier via docker compose.
|
||||
Synology has it's 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 "Deploy Stack" button:
|
||||
3. Deploy the Stack with the "Delpoy Stack" button:
|
||||
|
||||
![Portainer Stack deploy](/docs/synology-proxy-2.png)
|
||||
|
||||
|
|
|
@ -47,7 +47,10 @@ which will run the docker image and expose port 80 on the host.
|
|||
|
||||
See [full docker example]({{< ref "full-docker-example.md">}}) for more varations of this config.
|
||||
|
||||
The docker container runs as an unprivileged user and does not mount anything.
|
||||
### Setting user and group id of the user running vikunja
|
||||
|
||||
You can set the user and group id of the user running vikunja with the `PUID` and `PGID` evironment variables.
|
||||
This follows the pattern used by [the linuxserver.io](https://docs.linuxserver.io/general/understanding-puid-and-pgid) docker images.
|
||||
|
||||
### API URL configuration in docker
|
||||
|
||||
|
|
|
@ -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/charts/stable/vikunja/)
|
||||
* [Truecharts](https://truecharts.org/docs/charts/stable/vikunja/)
|
||||
* [k8s at Home](https://github.com/k8s-at-home/charts)
|
||||
|
||||
|
|
49
go.mod
49
go.mod
|
@ -21,58 +21,56 @@ 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-20221122102835-109346913e54
|
||||
github.com/arran4/golang-ical v0.0.0-20220517104411-fd89fefb0182
|
||||
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef
|
||||
github.com/bbrks/go-blurhash v1.1.1
|
||||
github.com/c2h5oh/datasize v0.0.0-20220606134207-859f65c6625b
|
||||
github.com/coreos/go-oidc/v3 v3.5.0
|
||||
github.com/coreos/go-oidc/v3 v3.4.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.17.0
|
||||
github.com/getsentry/sentry-go v0.15.0
|
||||
github.com/go-redis/redis/v8 v8.11.5
|
||||
github.com/go-sql-driver/mysql v1.7.0
|
||||
github.com/go-sql-driver/mysql v1.6.0
|
||||
github.com/go-testfixtures/testfixtures/v3 v3.8.1
|
||||
github.com/gocarina/gocsv v0.0.0-20230123225133-763e25b40669
|
||||
github.com/golang-jwt/jwt/v4 v4.4.3
|
||||
github.com/golang-jwt/jwt/v4 v4.4.2
|
||||
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-jwt/v4 v4.0.1
|
||||
github.com/labstack/echo/v4 v4.10.0
|
||||
github.com/labstack/echo/v4 v4.9.1
|
||||
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.4.0
|
||||
github.com/pquerna/otp v1.3.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.3
|
||||
github.com/spf13/cobra v1.6.1
|
||||
github.com/spf13/viper v1.15.0
|
||||
github.com/spf13/viper v1.14.0
|
||||
github.com/stretchr/testify v1.8.1
|
||||
github.com/swaggo/swag v1.8.10
|
||||
github.com/swaggo/swag v1.8.7
|
||||
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.8
|
||||
github.com/wneessen/go-mail v0.3.4
|
||||
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.4.0
|
||||
golang.org/x/crypto v0.2.0
|
||||
golang.org/x/image v0.1.0
|
||||
golang.org/x/oauth2 v0.1.0
|
||||
golang.org/x/sync v0.1.0
|
||||
golang.org/x/sys v0.4.0
|
||||
golang.org/x/term v0.4.0
|
||||
golang.org/x/sys v0.2.0
|
||||
golang.org/x/term v0.2.0
|
||||
gopkg.in/d4l3k/messagediff.v1 v1.2.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
src.techknowlogick.com/xgo v1.7.1-0.20230117190652-94aee174ab86
|
||||
src.techknowlogick.com/xgo v1.5.1-0.20220906164532-735bfdfb90d9
|
||||
src.techknowlogick.com/xormigrate v1.5.0
|
||||
xorm.io/builder v0.3.12
|
||||
xorm.io/xorm v1.3.2
|
||||
|
@ -94,7 +92,6 @@ 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
|
||||
|
@ -111,7 +108,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.7 // indirect
|
||||
github.com/magiconair/properties v1.8.6 // 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
|
||||
|
@ -122,7 +119,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.6 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.5 // 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
|
||||
|
@ -132,15 +129,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.2 // indirect
|
||||
github.com/subosito/gotenv v1.4.1 // indirect
|
||||
github.com/syndtr/goleveldb v1.0.0 // indirect
|
||||
github.com/urfave/cli/v2 v2.3.0 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||
github.com/valyala/fasttemplate v1.2.1 // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // 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/net v0.2.0 // indirect
|
||||
golang.org/x/text v0.4.0 // indirect
|
||||
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // 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
|
||||
|
|
148
go.sum
148
go.sum
|
@ -43,7 +43,6 @@ 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=
|
||||
|
@ -100,8 +99,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-20221122102835-109346913e54 h1:HfAA5Vxbo64UTckj+EW/hfBjvvcUcbcwWCASvypy8JU=
|
||||
github.com/arran4/golang-ical v0.0.0-20221122102835-109346913e54/go.mod h1:BSTTrYHuM12oAL8jDdcmPdw02SBThKYWNFHQlvEG6b0=
|
||||
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/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=
|
||||
|
@ -149,8 +148,6 @@ 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=
|
||||
|
@ -201,16 +198,18 @@ 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.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/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/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=
|
||||
|
@ -219,8 +218,6 @@ 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=
|
||||
|
@ -245,16 +242,11 @@ 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/gocarina/gocsv v0.0.0-20230123225133-763e25b40669 h1:MvZzCA/mduVWoBSVKJeMdv+AqXQmZZ8i6p8889ejt/Y=
|
||||
github.com/gocarina/gocsv v0.0.0-20230123225133-763e25b40669/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=
|
||||
|
@ -266,8 +258,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.3 h1:Hxl6lhQFj4AnOX6MLrsCb/+7tCj7DxP7VA+2rDIq5AU=
|
||||
github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
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-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=
|
||||
|
@ -509,13 +501,9 @@ 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.10.0 h1:5CiyngihEO4HXsz3vVsJn7f8xAlWwRr3aY6Ih280ZKA=
|
||||
github.com/labstack/echo/v4 v4.10.0/go.mod h1:S/T/5fy/GigaXnHTkh0ZGe4LpkkQysvRjFMSUTkDRNQ=
|
||||
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/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=
|
||||
|
@ -538,8 +526,6 @@ 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=
|
||||
|
@ -572,6 +558,8 @@ 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=
|
||||
|
@ -634,8 +622,6 @@ 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=
|
||||
|
@ -649,8 +635,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.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg=
|
||||
github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
||||
github.com/pquerna/otp v1.3.0 h1:oJV/SkzR33anKXwQU3Of42rL4wbrffP4uvUf1SvS5Xs=
|
||||
github.com/pquerna/otp v1.3.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=
|
||||
|
@ -658,6 +644,10 @@ 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=
|
||||
|
@ -665,6 +655,7 @@ 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=
|
||||
|
@ -718,11 +709,15 @@ 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=
|
||||
|
@ -730,10 +725,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=
|
||||
|
@ -749,17 +744,14 @@ 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/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/swaggo/swag v1.8.10 h1:eExW4bFa52WOjqRzRD58bgWsWfdFJso50lpbeTcmTfo=
|
||||
github.com/swaggo/swag v1.8.10/go.mod h1:ezQVUUhly8dludpVk+/PuwJWvLLanB13ygV5Pr9enSk=
|
||||
github.com/swaggo/swag v1.8.7 h1:2K9ivTD3teEO+2fXV6zrZKDqk5IuU2aJtBDo8U7omWU=
|
||||
github.com/swaggo/swag v1.8.7/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=
|
||||
|
@ -776,17 +768,18 @@ 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.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/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/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=
|
||||
|
@ -794,6 +787,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=
|
||||
|
@ -842,10 +837,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.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/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
|
||||
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
||||
golang.org/x/crypto v0.2.0 h1:BRXPfhNivWL5Yq0BGQ39a2sW6t44aODpfxkWjYdzewE=
|
||||
golang.org/x/crypto v0.2.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||
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=
|
||||
|
@ -859,10 +854,8 @@ 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.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/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/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=
|
||||
|
@ -944,11 +937,10 @@ 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.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/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.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU=
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
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=
|
||||
|
@ -970,10 +962,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.3.0 h1:6l90koy8/LaBLmLu8jpHeHexzMwEita0zFfYlggy2F8=
|
||||
golang.org/x/oauth2 v0.3.0/go.mod h1:rQrIauxkUhJ6CuwEXwymO2/eh4xz2ZWF1nBkcxS+tGk=
|
||||
golang.org/x/oauth2 v0.4.0 h1:NF0gk8LVPg1Ml7SSbGyySuoxdsXitj7TvgvuRxIMc/M=
|
||||
golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec=
|
||||
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/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=
|
||||
|
@ -1082,17 +1072,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.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/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/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.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/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/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=
|
||||
|
@ -1102,17 +1092,15 @@ 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.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/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
|
||||
golang.org/x/text v0.4.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.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
golang.org/x/time v0.3.0/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/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=
|
||||
|
@ -1530,10 +1518,6 @@ 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=
|
||||
|
|
|
@ -703,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 = "/usr/bin/nfpm"
|
||||
binpath = "/nfpm"
|
||||
err = exec.Command(binpath).Run()
|
||||
}
|
||||
if err != nil && strings.Contains(err.Error(), "executable file not found") {
|
||||
|
@ -712,7 +712,7 @@ func (Release) Packages() error {
|
|||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Because nfpm does not support templating, we replace the values in the config file and restore it after running
|
||||
// Because nfpm does not support templating, we replace the values in the config file and restore it after running
|
||||
nfpmConfigPath := RootPath + "/nfpm.yaml"
|
||||
nfpmconfig, err := os.ReadFile(nfpmConfigPath)
|
||||
if err != nil {
|
||||
|
|
|
@ -22,8 +22,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"code.vikunja.io/api/pkg/utils"
|
||||
)
|
||||
|
@ -59,12 +58,10 @@ type Todo struct {
|
|||
RelatedToUID string
|
||||
Color string
|
||||
|
||||
Start time.Time
|
||||
End time.Time
|
||||
DueDate time.Time
|
||||
Duration time.Duration
|
||||
RepeatAfter int64
|
||||
RepeatMode models.TaskRepeatMode
|
||||
Start time.Time
|
||||
End time.Time
|
||||
DueDate time.Time
|
||||
Duration time.Duration
|
||||
|
||||
Created time.Time
|
||||
Updated time.Time // last-mod
|
||||
|
@ -229,16 +226,6 @@ 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)
|
||||
|
||||
|
@ -253,7 +240,7 @@ END:VCALENDAR` // Need a line break
|
|||
}
|
||||
|
||||
func makeCalDavTimeFromTimeStamp(ts time.Time) (caldavtime string) {
|
||||
return ts.In(time.UTC).Format(DateFormat) + "Z"
|
||||
return ts.In(config.GetTimeZone()).Format(DateFormat)
|
||||
}
|
||||
|
||||
func calcAlarmDateFromReminder(eventStart, reminder time.Time) (alarmTime string) {
|
||||
|
|
|
@ -20,8 +20,6 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
@ -86,25 +84,25 @@ X-APPLE-CALENDAR-COLOR:#affffeFF
|
|||
X-OUTLOOK-COLOR:#affffeFF
|
||||
X-FUNAMBOL-COLOR:#affffeFF
|
||||
DESCRIPTION:Lorem Ipsum
|
||||
DTSTAMP:20181201T011204Z
|
||||
DTSTART:20181201T011204Z
|
||||
DTEND:20181201T013024Z
|
||||
DTSTAMP:20181201T011204
|
||||
DTSTART:20181201T011204
|
||||
DTEND:20181201T013024
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:randommduidd
|
||||
SUMMARY:Event #2
|
||||
DESCRIPTION:
|
||||
DTSTAMP:20181202T045844Z
|
||||
DTSTART:20181202T045844Z
|
||||
DTEND:20181202T081844Z
|
||||
DTSTAMP:20181202T045844
|
||||
DTSTART:20181202T045844
|
||||
DTEND:20181202T081844
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:20181202T0600242aaef4a81d770c1e775e26bc5abebc87f1d3d7bffaa83
|
||||
SUMMARY:Event #3 with empty uid
|
||||
DESCRIPTION:
|
||||
DTSTAMP:20181202T050024Z
|
||||
DTSTART:20181202T050024Z
|
||||
DTEND:20181202T050320Z
|
||||
DTSTAMP:20181202T050024
|
||||
DTSTART:20181202T050024
|
||||
DTEND:20181202T050320
|
||||
END:VEVENT
|
||||
END:VCALENDAR`,
|
||||
},
|
||||
|
@ -171,9 +169,9 @@ BEGIN:VEVENT
|
|||
UID:randommduid
|
||||
SUMMARY:Event #1
|
||||
DESCRIPTION:Lorem Ipsum
|
||||
DTSTAMP:20181201T011204Z
|
||||
DTSTART:20181201T011204Z
|
||||
DTEND:20181201T013024Z
|
||||
DTSTAMP:20181201T011204
|
||||
DTSTART:20181201T011204
|
||||
DTEND:20181201T013024
|
||||
BEGIN:VALARM
|
||||
TRIGGER:-PT3M20S
|
||||
ACTION:DISPLAY
|
||||
|
@ -194,9 +192,9 @@ BEGIN:VEVENT
|
|||
UID:randommduidd
|
||||
SUMMARY:Event #2
|
||||
DESCRIPTION:
|
||||
DTSTAMP:20181202T045844Z
|
||||
DTSTART:20181202T045844Z
|
||||
DTEND:20181202T081844Z
|
||||
DTSTAMP:20181202T045844
|
||||
DTSTART:20181202T045844
|
||||
DTEND:20181202T081844
|
||||
BEGIN:VALARM
|
||||
TRIGGER:-PT27H50M0S
|
||||
ACTION:DISPLAY
|
||||
|
@ -214,12 +212,12 @@ DESCRIPTION:Event #2
|
|||
END:VALARM
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:20181202T050024Z2aaef4a81d770c1e775e26bc5abebc87f1d3d7bffaa83
|
||||
UID:20181202T0500242aaef4a81d770c1e775e26bc5abebc87f1d3d7bffaa83
|
||||
SUMMARY:Event #3 with empty uid
|
||||
DESCRIPTION:
|
||||
DTSTAMP:20181202T050024Z
|
||||
DTSTART:20181202T050024Z
|
||||
DTEND:20181202T050320Z
|
||||
DTSTAMP:20181202T050024
|
||||
DTSTART:20181202T050024
|
||||
DTEND:20181202T050320
|
||||
BEGIN:VALARM
|
||||
TRIGGER:-PT27H51M40S
|
||||
ACTION:DISPLAY
|
||||
|
@ -242,12 +240,12 @@ DESCRIPTION:Event #3 with empty uid
|
|||
END:VALARM
|
||||
END:VEVENT
|
||||
BEGIN:VEVENT
|
||||
UID:20181202T050024Zae7548ce9556df85038abe90dc674d4741a61ce74d1cf
|
||||
UID:20181202T050024ae7548ce9556df85038abe90dc674d4741a61ce74d1cf
|
||||
SUMMARY:Event #4 without any
|
||||
DESCRIPTION:
|
||||
DTSTAMP:20181202T050024Z
|
||||
DTSTART:20181202T050024Z
|
||||
DTEND:20181202T050320Z
|
||||
DTSTAMP:20181202T050024
|
||||
DTSTART:20181202T050024
|
||||
DTEND:20181202T050320
|
||||
END:VEVENT
|
||||
END:VCALENDAR`,
|
||||
},
|
||||
|
@ -280,9 +278,9 @@ BEGIN:VEVENT
|
|||
UID:randommduid
|
||||
SUMMARY:Event #1
|
||||
DESCRIPTION:Lorem Ipsum\nDolor sit amet
|
||||
DTSTAMP:20181201T011204Z
|
||||
DTSTART:20181201T011204Z
|
||||
DTEND:20181201T013024Z
|
||||
DTSTAMP:20181201T011204
|
||||
DTSTART:20181201T011204
|
||||
DTEND:20181201T013024
|
||||
END:VEVENT
|
||||
END:VCALENDAR`,
|
||||
},
|
||||
|
@ -335,13 +333,13 @@ X-OUTLOOK-COLOR:#ffffffFF
|
|||
X-FUNAMBOL-COLOR:#ffffffFF
|
||||
BEGIN:VTODO
|
||||
UID:randommduid
|
||||
DTSTAMP:20181201T011204Z
|
||||
DTSTAMP:20181201T011204
|
||||
SUMMARY:Todo #1
|
||||
X-APPLE-CALENDAR-COLOR:#affffeFF
|
||||
X-OUTLOOK-COLOR:#affffeFF
|
||||
X-FUNAMBOL-COLOR:#affffeFF
|
||||
DESCRIPTION:Lorem Ipsum\nDolor sit amet
|
||||
LAST-MODIFIED:00010101T000000Z
|
||||
LAST-MODIFIED:00010101T000000
|
||||
END:VTODO
|
||||
END:VCALENDAR`,
|
||||
},
|
||||
|
@ -370,12 +368,12 @@ X-WR-CALNAME:test
|
|||
PRODID:-//RandomProdID which is not random//EN
|
||||
BEGIN:VTODO
|
||||
UID:randommduid
|
||||
DTSTAMP:20181201T011204Z
|
||||
DTSTAMP:20181201T011204
|
||||
SUMMARY:Todo #1
|
||||
DESCRIPTION:Lorem Ipsum
|
||||
COMPLETED:20181201T013024Z
|
||||
COMPLETED:20181201T013024
|
||||
STATUS:COMPLETED
|
||||
LAST-MODIFIED:00010101T000000Z
|
||||
LAST-MODIFIED:00010101T000000
|
||||
END:VTODO
|
||||
END:VCALENDAR`,
|
||||
},
|
||||
|
@ -404,82 +402,11 @@ X-WR-CALNAME:test
|
|||
PRODID:-//RandomProdID which is not random//EN
|
||||
BEGIN:VTODO
|
||||
UID:randommduid
|
||||
DTSTAMP:20181201T011204Z
|
||||
DTSTAMP:20181201T011204
|
||||
SUMMARY:Todo #1
|
||||
DESCRIPTION:Lorem Ipsum
|
||||
PRIORITY:9
|
||||
LAST-MODIFIED:00010101T000000Z
|
||||
END:VTODO
|
||||
END:VCALENDAR`,
|
||||
},
|
||||
{
|
||||
name: "with repeating monthly",
|
||||
args: args{
|
||||
config: &Config{
|
||||
Name: "test",
|
||||
ProdID: "RandomProdID which is not random",
|
||||
},
|
||||
todos: []*Todo{
|
||||
{
|
||||
Summary: "Todo #1",
|
||||
Description: "Lorem Ipsum",
|
||||
UID: "randommduid",
|
||||
Timestamp: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||
RepeatMode: models.TaskRepeatModeMonth,
|
||||
DueDate: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantCaldavtasks: `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
X-PUBLISHED-TTL:PT4H
|
||||
X-WR-CALNAME:test
|
||||
PRODID:-//RandomProdID which is not random//EN
|
||||
BEGIN:VTODO
|
||||
UID:randommduid
|
||||
DTSTAMP:20181201T011204Z
|
||||
SUMMARY:Todo #1
|
||||
DESCRIPTION:Lorem Ipsum
|
||||
DUE:20181201T011204Z
|
||||
RRULE:FREQ=MONTHLY;BYMONTHDAY=01
|
||||
LAST-MODIFIED:00010101T000000Z
|
||||
END:VTODO
|
||||
END:VCALENDAR`,
|
||||
},
|
||||
{
|
||||
name: "with repeat mode default",
|
||||
args: args{
|
||||
config: &Config{
|
||||
Name: "test",
|
||||
ProdID: "RandomProdID which is not random",
|
||||
},
|
||||
todos: []*Todo{
|
||||
{
|
||||
Summary: "Todo #1",
|
||||
Description: "Lorem Ipsum",
|
||||
UID: "randommduid",
|
||||
Timestamp: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||
RepeatMode: models.TaskRepeatModeDefault,
|
||||
DueDate: time.Unix(1543626724, 0).In(config.GetTimeZone()),
|
||||
RepeatAfter: 435,
|
||||
},
|
||||
},
|
||||
},
|
||||
wantCaldavtasks: `BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
METHOD:PUBLISH
|
||||
X-PUBLISHED-TTL:PT4H
|
||||
X-WR-CALNAME:test
|
||||
PRODID:-//RandomProdID which is not random//EN
|
||||
BEGIN:VTODO
|
||||
UID:randommduid
|
||||
DTSTAMP:20181201T011204Z
|
||||
SUMMARY:Todo #1
|
||||
DESCRIPTION:Lorem Ipsum
|
||||
DUE:20181201T011204Z
|
||||
RRULE:FREQ=SECONDLY;INTERVAL=435
|
||||
LAST-MODIFIED:00010101T000000Z
|
||||
LAST-MODIFIED:00010101T000000
|
||||
END:VTODO
|
||||
END:VCALENDAR`,
|
||||
},
|
||||
|
|
|
@ -42,15 +42,13 @@ func GetCaldavTodosForTasks(list *models.ListWithTasksAndBuckets, listTasks []*m
|
|||
Description: t.Description,
|
||||
Completed: t.DoneAt,
|
||||
// Organizer: &t.CreatedBy, // Disabled until we figure out how this works
|
||||
Priority: t.Priority,
|
||||
Start: t.StartDate,
|
||||
End: t.EndDate,
|
||||
Created: t.Created,
|
||||
Updated: t.Updated,
|
||||
DueDate: t.DueDate,
|
||||
Duration: duration,
|
||||
RepeatAfter: t.RepeatAfter,
|
||||
RepeatMode: t.RepeatMode,
|
||||
Priority: t.Priority,
|
||||
Start: t.StartDate,
|
||||
End: t.EndDate,
|
||||
Created: t.Created,
|
||||
Updated: t.Updated,
|
||||
DueDate: t.DueDate,
|
||||
Duration: duration,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -225,7 +225,7 @@ var userUpdateCmd = &cobra.Command{
|
|||
u.AvatarProvider = userFlagAvatar
|
||||
}
|
||||
|
||||
_, err := user.UpdateUser(s, u, false)
|
||||
_, err := user.UpdateUser(s, u)
|
||||
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, false)
|
||||
_, err := user.UpdateUser(s, u)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
log.Fatalf("Could not enable the user")
|
||||
|
|
|
@ -128,6 +128,10 @@ 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`
|
||||
|
@ -365,6 +369,7 @@ func InitDefaultConfig() {
|
|||
CorsOrigins.setDefault([]string{"*"})
|
||||
CorsMaxAge.setDefault(0)
|
||||
// Migration
|
||||
MigrationWunderlistEnable.setDefault(false)
|
||||
MigrationTodoistEnable.setDefault(false)
|
||||
MigrationTrelloEnable.setDefault(false)
|
||||
MigrationMicrosoftTodoEnable.setDefault(false)
|
||||
|
|
|
@ -18,7 +18,6 @@ package db
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
|
||||
|
@ -54,35 +53,7 @@ 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,7 +337,6 @@
|
|||
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
|
||||
|
|
|
@ -25,7 +25,6 @@ import (
|
|||
"code.vikunja.io/api/pkg/log"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"xorm.io/builder"
|
||||
"xorm.io/xorm"
|
||||
"xorm.io/xorm/names"
|
||||
)
|
||||
|
@ -103,10 +102,3 @@ func AssertMissing(t *testing.T, table string, values map[string]interface{}) {
|
|||
assert.NoError(t, err, fmt.Sprintf("Failed to assert entries don't exist in db, error was: %s", err))
|
||||
assert.False(t, exists, fmt.Sprintf("Entries %v exist in table %s", values, table))
|
||||
}
|
||||
|
||||
// AssertCount checks if a number of entries exists in the database
|
||||
func AssertCount(t *testing.T, table string, where builder.Cond, count int64) {
|
||||
dbCount, err := x.Table(table).Where(where).Count()
|
||||
assert.NoError(t, err, fmt.Sprintf("Failed to assert count in db, error was: %s", err))
|
||||
assert.Equal(t, count, dbCount, fmt.Sprintf("Found %d entries instead of expected %d in table %s", dbCount, count, table))
|
||||
}
|
||||
|
|
|
@ -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, true)
|
||||
file, err = CreateWithMimeAndSession(s, f, realname, realsize, a, mime)
|
||||
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, checkFileSizeLimit bool) (file *File, err error) {
|
||||
func CreateWithMimeAndSession(s *xorm.Session, f io.Reader, realname string, realsize uint64, a web.Auth, mime string) (file *File, err error) {
|
||||
// Get and parse the configured file size
|
||||
var maxSize datasize.ByteSize
|
||||
err = maxSize.UnmarshalText([]byte(config.FilesMaxSize.GetString()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if realsize > maxSize.Bytes() && checkFileSizeLimit {
|
||||
if realsize > maxSize.Bytes() {
|
||||
return nil, ErrFileIsTooLarge{Size: realsize}
|
||||
}
|
||||
|
||||
|
|
|
@ -58,10 +58,6 @@ 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.SetGenHeader(h.Field, h.Content)
|
||||
m.SetHeader(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", false)
|
||||
exportFile, err := files.CreateWithMimeAndSession(s, exported, tmpFilename, uint64(stat.Size()), u, "application/zip")
|
||||
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 OR n.is_archived IS NULL").
|
||||
Where("n.is_archived = false").
|
||||
Where("namespace_id = ?", nID).
|
||||
Find(&lists)
|
||||
}
|
||||
|
|
|
@ -37,9 +37,7 @@ 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 < ? 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").
|
||||
Where("due_date is not null and due_date < ?", nextMinute.Add(time.Hour*14).Format(dbTimeFormat)).
|
||||
And("done = false").
|
||||
Find(&tasks)
|
||||
if err != nil {
|
||||
|
@ -140,13 +138,9 @@ 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: t,
|
||||
}
|
||||
n = &UndoneTaskOverdueNotification{
|
||||
User: ut.user,
|
||||
Task: ut.tasks[0],
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -817,28 +817,23 @@ 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) (targetBucket *Bucket, err error) {
|
||||
func setTaskBucket(s *xorm.Session, task *Task, originalTask *Task, doCheckBucketLimit bool) (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 nil, err
|
||||
return 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
|
||||
return err
|
||||
}
|
||||
task.BucketID = bucket.ID
|
||||
}
|
||||
|
@ -846,7 +841,7 @@ func setTaskBucket(s *xorm.Session, task *Task, originalTask *Task, doCheckBucke
|
|||
if bucket == nil {
|
||||
bucket, err = getBucketByID(s, task.BucketID)
|
||||
if err != nil {
|
||||
return
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -860,7 +855,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 nil, err
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -868,7 +863,7 @@ func setTaskBucket(s *xorm.Session, task *Task, originalTask *Task, doCheckBucke
|
|||
task.Done = true
|
||||
}
|
||||
|
||||
return bucket, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func calculateDefaultPosition(entityID int64, position float64) float64 {
|
||||
|
@ -937,7 +932,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
|
||||
}
|
||||
|
@ -1028,21 +1023,13 @@ func (t *Task) Update(s *xorm.Session, a web.Auth) (err error) {
|
|||
ot.Reminders[i] = r.Reminder
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
if err := setTaskBucket(s, t, &ot, t.BucketID != ot.BucketID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Update the assignees
|
||||
if err := ot.updateTaskAssignees(s, t.Assignees, a); err != nil {
|
||||
return err
|
||||
|
@ -1382,7 +1369,7 @@ func setTaskDatesFromCurrentDateRepeat(oldTask, newTask *Task) {
|
|||
|
||||
// This helper function updates the reminders, doneAt, start and end dates of the *old* task
|
||||
// and saves the new values in the newTask object.
|
||||
// We make a few assumptions here:
|
||||
// We make a few assumtions here:
|
||||
// 1. Everything in oldTask is the truth - we figure out if we update anything at all if oldTask.RepeatAfter has a value > 0
|
||||
// 2. Because of 1., this functions should not be used to update values other than Done in the same go
|
||||
func updateDone(oldTask *Task, newTask *Task) {
|
||||
|
@ -1419,9 +1406,9 @@ func (t *Task) updateReminders(s *xorm.Session, reminders []time.Time) (err erro
|
|||
}
|
||||
|
||||
// Resolve duplicates and sort them
|
||||
reminderMap := make(map[int64]time.Time, len(reminders))
|
||||
reminderMap := make(map[string]time.Time, len(reminders))
|
||||
for _, reminder := range reminders {
|
||||
reminderMap[reminder.UTC().Unix()] = reminder
|
||||
reminderMap[reminder.UTC().String()] = reminder
|
||||
}
|
||||
|
||||
// Loop through all reminders and add them
|
||||
|
|
|
@ -25,7 +25,6 @@ import (
|
|||
"code.vikunja.io/api/pkg/user"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
func TestTask_Create(t *testing.T) {
|
||||
|
@ -244,33 +243,6 @@ 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()
|
||||
|
@ -336,9 +308,8 @@ func TestTask_Update(t *testing.T) {
|
|||
defer s.Close()
|
||||
|
||||
task := &Task{
|
||||
ID: 28,
|
||||
Done: true,
|
||||
RepeatAfter: 3600,
|
||||
ID: 28,
|
||||
Done: true,
|
||||
}
|
||||
err := task.Update(s, u)
|
||||
assert.NoError(t, err)
|
||||
|
@ -368,27 +339,6 @@ func TestTask_Update(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(3), task.Index)
|
||||
})
|
||||
t.Run("the same date multiple times should be saved once", func(t *testing.T) {
|
||||
db.LoadAndAssertFixtures(t)
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
task := &Task{
|
||||
ID: 1,
|
||||
Title: "test",
|
||||
Reminders: []time.Time{
|
||||
time.Unix(1674745156, 0),
|
||||
time.Unix(1674745156, 223),
|
||||
},
|
||||
ListID: 1,
|
||||
}
|
||||
err := task.Update(s, u)
|
||||
assert.NoError(t, err)
|
||||
err = s.Commit()
|
||||
assert.NoError(t, err)
|
||||
|
||||
db.AssertCount(t, "task_reminders", builder.Eq{"task_id": 1}, 1)
|
||||
})
|
||||
}
|
||||
|
||||
func TestTask_Delete(t *testing.T) {
|
||||
|
|
|
@ -51,7 +51,6 @@ type Provider struct {
|
|||
Key string `json:"key"`
|
||||
OriginalAuthURL string `json:"-"`
|
||||
AuthURL string `json:"auth_url"`
|
||||
LogoutURL string `json:"logout_url"`
|
||||
ClientID string `json:"client_id"`
|
||||
ClientSecret string `json:"-"`
|
||||
openIDProvider *oidc.Provider
|
||||
|
@ -267,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
|
||||
}
|
||||
|
|
|
@ -60,7 +60,6 @@ func GetAllProviders() (providers []*Provider, err error) {
|
|||
}
|
||||
|
||||
provider, err := getProviderFromMap(pi)
|
||||
|
||||
if err != nil {
|
||||
if provider != nil {
|
||||
log.Errorf("Error while getting openid provider %s: %s", provider.Name, err)
|
||||
|
@ -120,18 +119,12 @@ func getProviderFromMap(pi map[string]interface{}) (provider *Provider, err erro
|
|||
|
||||
k := getKeyFromName(name)
|
||||
|
||||
logoutURL, ok := pi["logouturl"].(string)
|
||||
if !ok {
|
||||
logoutURL = ""
|
||||
}
|
||||
|
||||
provider = &Provider{
|
||||
Name: pi["name"].(string),
|
||||
Key: k,
|
||||
AuthURL: pi["authurl"].(string),
|
||||
OriginalAuthURL: pi["authurl"].(string),
|
||||
ClientSecret: pi["clientsecret"].(string),
|
||||
LogoutURL: logoutURL,
|
||||
}
|
||||
|
||||
cl, is := pi["clientid"].(int)
|
||||
|
@ -150,6 +143,7 @@ func getProviderFromMap(pi map[string]interface{}) (provider *Provider, err erro
|
|||
ClientID: provider.ClientID,
|
||||
ClientSecret: provider.ClientSecret,
|
||||
RedirectURL: config.AuthOpenIDRedirectURL.GetString() + k,
|
||||
|
||||
// Discovery returns the OAuth2 endpoints.
|
||||
Endpoint: provider.openIDProvider.Endpoint(),
|
||||
|
||||
|
|
|
@ -295,11 +295,6 @@ func GetListBackground(c echo.Context) error {
|
|||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err, c)
|
||||
}
|
||||
stat, err := bgFile.File.Stat()
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err, c)
|
||||
}
|
||||
|
||||
// Unsplash requires pingbacks as per their api usage guidelines.
|
||||
// To do this in a privacy-preserving manner, we do the ping from inside of Vikunja to not expose any user details.
|
||||
|
@ -311,11 +306,6 @@ func GetListBackground(c echo.Context) error {
|
|||
return handler.HandleHTTPError(err, c)
|
||||
}
|
||||
|
||||
// Set Last-Modified header if we have the file stat, so clients can decide whether to use cached files
|
||||
if stat != nil {
|
||||
c.Response().Header().Set(echo.HeaderLastModified, stat.ModTime().UTC().Format(http.TimeFormat))
|
||||
}
|
||||
|
||||
// Serve the file
|
||||
return c.Stream(http.StatusOK, "image/jpg", bgFile.File)
|
||||
}
|
||||
|
|
|
@ -22,7 +22,6 @@ import (
|
|||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
@ -145,16 +144,11 @@ 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 {
|
||||
var decoded []byte
|
||||
decoded, err = base64.StdEncoding.DecodeString(content[i]["notification"].(string))
|
||||
if err != nil && !errors.Is(err, base64.CorruptInputError(0)) {
|
||||
decoded, err := base64.StdEncoding.DecodeString(content[i]["notification"].(string))
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -226,9 +226,6 @@ func insertFromStructure(s *xorm.Session, str []*models.NamespaceWithListsAndTas
|
|||
// If not, create one and save it for later
|
||||
var lb *models.Label
|
||||
var exists bool
|
||||
if label == nil {
|
||||
continue
|
||||
}
|
||||
lb, exists = labels[label.Title+label.HexColor]
|
||||
if !exists {
|
||||
err = label.Create(s, user)
|
||||
|
|
|
@ -56,22 +56,12 @@ 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)
|
||||
}
|
||||
|
|
|
@ -27,11 +27,10 @@ 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"
|
||||
|
@ -40,39 +39,23 @@ type Migrator struct {
|
|||
}
|
||||
|
||||
type tickTickTask struct {
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
// Copied from https://stackoverflow.com/a/57617885
|
||||
|
@ -136,22 +119,19 @@ func convertTickTickToVikunja(tasks []*tickTickTask) (result []*models.Namespace
|
|||
ID: t.TaskID,
|
||||
Title: t.Title,
|
||||
Description: t.Content,
|
||||
StartDate: t.StartDate.Time,
|
||||
EndDate: t.DueDate.Time,
|
||||
DueDate: t.DueDate.Time,
|
||||
Done: t.Status == "1",
|
||||
DoneAt: t.CompletedTime.Time,
|
||||
Position: t.Order,
|
||||
Labels: labels,
|
||||
StartDate: t.StartDate,
|
||||
EndDate: t.DueDate,
|
||||
DueDate: t.DueDate,
|
||||
Reminders: []time.Time{
|
||||
t.DueDate.Add(t.Reminder * -1),
|
||||
},
|
||||
Done: t.Status == "1",
|
||||
DoneAt: t.CompletedTime,
|
||||
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}},
|
||||
|
@ -185,22 +165,6 @@ 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.
|
||||
|
@ -214,26 +178,85 @@ func newLineSkipDecoder(r io.Reader, linesToSkip int) gocsv.SimpleDecoder {
|
|||
// @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{}
|
||||
decode := newLineSkipDecoder(fr, 3)
|
||||
err := gocsv.UnmarshalDecoder(decode, &allTasks)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
line := 0
|
||||
for {
|
||||
|
||||
for _, task := range allTasks {
|
||||
if task.IsChecklistString == "Y" {
|
||||
task.IsChecklist = true
|
||||
record, err := r.Read()
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
log.Debugf("[TickTick Migration] CSV parse error: %s", err)
|
||||
}
|
||||
|
||||
reminder := parseDuration(task.ReminderDuration)
|
||||
if reminder > 0 {
|
||||
task.Reminder = reminder
|
||||
line++
|
||||
if line <= 4 {
|
||||
continue
|
||||
}
|
||||
|
||||
task.Tags = strings.Split(task.TagsList, ", ")
|
||||
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)
|
||||
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,
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
allTasks = append(allTasks, t)
|
||||
}
|
||||
|
||||
vikunjaTasks := convertTickTickToVikunja(allTasks)
|
||||
|
|
|
@ -26,15 +26,12 @@ import (
|
|||
)
|
||||
|
||||
func TestConvertTicktickTasksToVikunja(t *testing.T) {
|
||||
t1, err := time.Parse(time.RFC3339Nano, "2022-11-18T03:00:00.4770000Z")
|
||||
time1, err := time.Parse(time.RFC3339Nano, "2022-11-18T03:00:00.4770000Z")
|
||||
require.NoError(t, err)
|
||||
time1 := tickTickTime{Time: t1}
|
||||
t2, err := time.Parse(time.RFC3339Nano, "2022-12-18T03:00:00.4770000Z")
|
||||
time2, err := time.Parse(time.RFC3339Nano, "2022-12-18T03:00:00.4770000Z")
|
||||
require.NoError(t, err)
|
||||
time2 := tickTickTime{Time: t2}
|
||||
t3, err := time.Parse(time.RFC3339Nano, "2022-12-10T03:00:00.4770000Z")
|
||||
time3, 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)
|
||||
|
||||
|
@ -94,9 +91,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.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].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].Labels, []*models.Label{
|
||||
{Title: "label1"},
|
||||
{Title: "label2"},
|
||||
|
@ -108,7 +105,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.Time)
|
||||
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[1].DoneAt, tickTickTasks[1].CompletedTime)
|
||||
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[1].RelatedTasks, models.RelatedTaskMap{
|
||||
models.RelationKindParenttask: []*models.Task{
|
||||
{
|
||||
|
@ -119,9 +116,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.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].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].Labels, []*models.Label{
|
||||
{Title: "label1"},
|
||||
{Title: "label2"},
|
||||
|
|
|
@ -20,7 +20,6 @@ import (
|
|||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
@ -35,8 +34,6 @@ import (
|
|||
"code.vikunja.io/api/pkg/utils"
|
||||
)
|
||||
|
||||
const paginationLimit = 200
|
||||
|
||||
// Migration is the todoist migration struct
|
||||
type Migration struct {
|
||||
Code string `json:"code"`
|
||||
|
@ -48,25 +45,28 @@ type apiTokenResponse struct {
|
|||
}
|
||||
|
||||
type label struct {
|
||||
ID string `json:"id"`
|
||||
ID int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Color string `json:"color"`
|
||||
Color int64 `json:"color"`
|
||||
ItemOrder int64 `json:"item_order"`
|
||||
IsDeleted bool `json:"is_deleted"`
|
||||
IsFavorite bool `json:"is_favorite"`
|
||||
IsDeleted int64 `json:"is_deleted"`
|
||||
IsFavorite int64 `json:"is_favorite"`
|
||||
}
|
||||
|
||||
type project struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Color string `json:"color"`
|
||||
ParentID string `json:"parent_id"`
|
||||
ChildOrder int64 `json:"child_order"`
|
||||
Collapsed bool `json:"collapsed"`
|
||||
Shared bool `json:"shared"`
|
||||
IsDeleted bool `json:"is_deleted"`
|
||||
IsArchived bool `json:"is_archived"`
|
||||
IsFavorite bool `json:"is_favorite"`
|
||||
ID int64 `json:"id"`
|
||||
LegacyID int64 `json:"legacy_id"`
|
||||
Name string `json:"name"`
|
||||
Color int64 `json:"color"`
|
||||
ParentID int64 `json:"parent_id"`
|
||||
ChildOrder int64 `json:"child_order"`
|
||||
Collapsed int64 `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"`
|
||||
}
|
||||
|
||||
type dueDate struct {
|
||||
|
@ -78,25 +78,31 @@ type dueDate struct {
|
|||
}
|
||||
|
||||
type item struct {
|
||||
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 string `json:"parent_id"`
|
||||
ChildOrder int64 `json:"child_order"`
|
||||
SectionID string `json:"section_id"`
|
||||
Children interface{} `json:"children"`
|
||||
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:"completed_at"`
|
||||
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"`
|
||||
Content string `json:"content"`
|
||||
Priority int64 `json:"priority"`
|
||||
Due *dueDate `json:"due"`
|
||||
ParentID int64 `json:"parent_id"`
|
||||
LegacyParentID int64 `json:"legacy_parent_id"`
|
||||
ChildOrder int64 `json:"child_order"`
|
||||
SectionID int64 `json:"section_id"`
|
||||
DayOrder int64 `json:"day_order"`
|
||||
Collapsed int64 `json:"collapsed"`
|
||||
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"`
|
||||
HasMoreNotes bool `json:"has_more_notes"`
|
||||
DateCompleted time.Time `json:"date_completed"`
|
||||
}
|
||||
|
||||
type itemWrapper struct {
|
||||
|
@ -104,11 +110,12 @@ type itemWrapper struct {
|
|||
}
|
||||
|
||||
type doneItem struct {
|
||||
CompletedDate time.Time `json:"completed_at"`
|
||||
CompletedDate time.Time `json:"completed_date"`
|
||||
Content string `json:"content"`
|
||||
ID string `json:"id"`
|
||||
ProjectID string `json:"project_id"`
|
||||
TaskID string `json:"task_id"`
|
||||
ID int64 `json:"id"`
|
||||
ProjectID int64 `json:"project_id"`
|
||||
TaskID int64 `json:"task_id"`
|
||||
UserID int `json:"user_id"`
|
||||
}
|
||||
|
||||
type doneItemSync struct {
|
||||
|
@ -125,13 +132,18 @@ type fileAttachment struct {
|
|||
}
|
||||
|
||||
type note struct {
|
||||
ID string `json:"id"`
|
||||
ProjectID string `json:"project_id"`
|
||||
ItemID string `json:"item_id"`
|
||||
Content string `json:"content"`
|
||||
FileAttachment *fileAttachment `json:"file_attachment"`
|
||||
IsDeleted bool `json:"is_deleted"`
|
||||
Posted time.Time `json:"posted_at"`
|
||||
ID int64 `json:"id"`
|
||||
LegacyID int64 `json:"legacy_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"`
|
||||
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"`
|
||||
}
|
||||
|
||||
type projectNote struct {
|
||||
|
@ -140,13 +152,16 @@ type projectNote struct {
|
|||
ID int64 `json:"id"`
|
||||
IsDeleted int64 `json:"is_deleted"`
|
||||
Posted time.Time `json:"posted"`
|
||||
ProjectID string `json:"project_id"`
|
||||
PostedUID int64 `json:"posted_uid"`
|
||||
ProjectID int64 `json:"project_id"`
|
||||
UidsToNotify []int64 `json:"uids_to_notify"`
|
||||
}
|
||||
|
||||
type reminder struct {
|
||||
ID string `json:"id"`
|
||||
ItemID string `json:"item_id"`
|
||||
ID int64 `json:"id"`
|
||||
NotifyUID int64 `json:"notify_uid"`
|
||||
ItemID int64 `json:"item_id"`
|
||||
Service string `json:"service"`
|
||||
Type string `json:"type"`
|
||||
Due *dueDate `json:"due"`
|
||||
MmOffset int64 `json:"mm_offset"`
|
||||
|
@ -154,11 +169,11 @@ type reminder struct {
|
|||
}
|
||||
|
||||
type section struct {
|
||||
ID string `json:"id"`
|
||||
DateAdded time.Time `json:"added_at"`
|
||||
ID int64 `json:"id"`
|
||||
DateAdded time.Time `json:"date_added"`
|
||||
IsDeleted bool `json:"is_deleted"`
|
||||
Name string `json:"name"`
|
||||
ProjectID string `json:"project_id"`
|
||||
ProjectID int64 `json:"project_id"`
|
||||
SectionOrder int64 `json:"section_order"`
|
||||
}
|
||||
|
||||
|
@ -172,32 +187,32 @@ type sync struct {
|
|||
Sections []*section `json:"sections"`
|
||||
}
|
||||
|
||||
var todoistColors = map[string]string{}
|
||||
var todoistColors = map[int64]string{}
|
||||
|
||||
func init() {
|
||||
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",
|
||||
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",
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -251,7 +266,7 @@ func parseDate(dateString string) (date time.Time, err error) {
|
|||
return date, err
|
||||
}
|
||||
|
||||
func convertTodoistToVikunja(sync *sync, doneItems map[string]*doneItem) (fullVikunjaHierachie []*models.NamespaceWithListsAndTasks, err error) {
|
||||
func convertTodoistToVikunja(sync *sync, doneItems map[int64]*doneItem) (fullVikunjaHierachie []*models.NamespaceWithListsAndTasks, err error) {
|
||||
|
||||
newNamespace := &models.NamespaceWithListsAndTasks{
|
||||
Namespace: models.Namespace{
|
||||
|
@ -260,22 +275,20 @@ func convertTodoistToVikunja(sync *sync, doneItems map[string]*doneItem) (fullVi
|
|||
}
|
||||
|
||||
// A map for all vikunja lists with the project id they're coming from as key
|
||||
lists := make(map[string]*models.ListWithTasksAndBuckets, len(sync.Projects))
|
||||
lists := make(map[int64]*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[string]*models.TaskWithComments, len(sync.Items))
|
||||
tasks := make(map[int64]*models.TaskWithComments, len(sync.Items))
|
||||
|
||||
// A map for all vikunja labels with the todoist id as key to find them easier
|
||||
labels := make(map[string]*models.Label, len(sync.Labels))
|
||||
|
||||
sections := make(map[string]int64)
|
||||
labels := make(map[int64]*models.Label, len(sync.Labels))
|
||||
|
||||
for _, p := range sync.Projects {
|
||||
list := &models.ListWithTasksAndBuckets{
|
||||
List: models.List{
|
||||
Title: p.Name,
|
||||
HexColor: todoistColors[p.Color],
|
||||
IsArchived: p.IsArchived,
|
||||
IsArchived: p.IsArchived == 1,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -288,40 +301,32 @@ func convertTodoistToVikunja(sync *sync, doneItems map[string]*doneItem) (fullVi
|
|||
return sync.Sections[i].SectionOrder < sync.Sections[j].SectionOrder
|
||||
})
|
||||
|
||||
var fabricatedSectionID int64 = 1
|
||||
for _, section := range sync.Sections {
|
||||
if section.IsDeleted || section.ProjectID == "" {
|
||||
if section.IsDeleted || section.ProjectID == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
lists[section.ProjectID].Buckets = append(lists[section.ProjectID].Buckets, &models.Bucket{
|
||||
ID: fabricatedSectionID,
|
||||
ID: section.ID,
|
||||
Title: section.Name,
|
||||
Created: section.DateAdded,
|
||||
})
|
||||
sections[section.ID] = fabricatedSectionID
|
||||
}
|
||||
|
||||
for _, label := range sync.Labels {
|
||||
labels[label.Name] = &models.Label{
|
||||
labels[label.ID] = &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,
|
||||
BucketID: sections[i.SectionID],
|
||||
Done: i.Checked == 1,
|
||||
BucketID: i.SectionID,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -352,31 +357,29 @@ func convertTodoistToVikunja(sync *sync, doneItems map[string]*doneItem) (fullVi
|
|||
}
|
||||
|
||||
// Put all labels together from earlier
|
||||
for _, lName := range i.Labels {
|
||||
task.Labels = append(task.Labels, labels[lName])
|
||||
for _, lID := range i.Labels {
|
||||
task.Labels = append(task.Labels, labels[lID])
|
||||
}
|
||||
|
||||
tasks[i.ID] = task
|
||||
|
||||
if _, exists := lists[i.ProjectID]; !exists {
|
||||
log.Debugf("[Todoist Migration] Tried to put item %s in project %s but the project does not exist", i.ID, i.ProjectID)
|
||||
log.Debugf("[Todoist Migration] Tried to put item %d in project %d 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 == "" {
|
||||
if i.ParentID == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, exists := tasks[i.ParentID]; !exists {
|
||||
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)
|
||||
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)
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -404,7 +407,7 @@ func convertTodoistToVikunja(sync *sync, doneItems map[string]*doneItem) (fullVi
|
|||
// FIXME: Should be comments
|
||||
for _, n := range sync.Notes {
|
||||
if _, exists := tasks[n.ItemID]; !exists {
|
||||
log.Debugf("[Todoist Migration] Could not find task %s for note %s", n.ItemID, n.ID)
|
||||
log.Debugf("[Todoist Migration] Could not find task %d for note %d", n.ItemID, n.ID)
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -457,7 +460,7 @@ func convertTodoistToVikunja(sync *sync, doneItems map[string]*doneItem) (fullVi
|
|||
}
|
||||
|
||||
if _, exists := tasks[r.ItemID]; !exists {
|
||||
log.Debugf("Could not find task %s for reminder %s while trying to resolve reminders", r.ItemID, r.ID)
|
||||
log.Debugf("Could not find task %d for reminder %d while trying to resolve reminders", r.ItemID, r.ID)
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -530,14 +533,11 @@ 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\"]"},
|
||||
}
|
||||
bearerHeader := map[string]string{
|
||||
"Authorization": "Bearer " + token,
|
||||
}
|
||||
|
||||
resp, err := migration.DoPostWithHeaders("https://api.todoist.com/sync/v9/sync", form, bearerHeader)
|
||||
resp, err := migration.DoPost("https://api.todoist.com/sync/v8/sync", form)
|
||||
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[string]*doneItem)
|
||||
doneItems := make(map[int64]*doneItem)
|
||||
|
||||
for {
|
||||
resp, err = migration.DoPostWithHeaders("https://api.todoist.com/sync/v9/completed/get_all?limit="+strconv.Itoa(paginationLimit)+"&offset="+strconv.Itoa(offset*paginationLimit), form, bearerHeader)
|
||||
resp, err = migration.DoPost("https://api.todoist.com/sync/v8/completed/get_all?limit=200&offset="+strconv.Itoa(offset), form)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -580,26 +580,21 @@ func (m *Migration) Migrate(u *user.User) (err error) {
|
|||
doneItems[i.TaskID] = i
|
||||
|
||||
// need to get done item data
|
||||
resp, err = migration.DoPostWithHeaders("https://api.todoist.com/sync/v9/items/get", url.Values{
|
||||
"item_id": []string{i.TaskID},
|
||||
}, bearerHeader)
|
||||
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)},
|
||||
})
|
||||
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 %s", i.ID)
|
||||
log.Debugf("[Todoist Migration] Retrieved full task data for done task %d", i.TaskID)
|
||||
syncResponse.Items = append(syncResponse.Items, doneI.Item)
|
||||
}
|
||||
|
||||
|
@ -614,7 +609,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.DoPostWithHeaders("https://api.todoist.com/sync/v9/projects/get_archived", form, bearerHeader)
|
||||
resp, err = migration.DoPost("https://api.todoist.com/sync/v8/projects/get_archived", form)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
@ -630,9 +625,10 @@ 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
|
||||
// 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
|
||||
for _, p := range archivedProjects {
|
||||
resp, err = migration.DoPostWithHeaders("https://api.todoist.com/sync/v9/projects/get_data?project_id="+p.ID, form, bearerHeader)
|
||||
resp, err = migration.DoPost("https://api.todoist.com/sync/v8/projects/get_data?project_id="+strconv.FormatInt(p.ID, 10), form)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ package todoist
|
|||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
@ -46,32 +47,33 @@ 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/testimage.jpg")
|
||||
exampleFile, err := os.ReadFile(config.ServiceRootpath.GetString() + "/pkg/modules/migration/wunderlist/testimage.jpg")
|
||||
assert.NoError(t, err)
|
||||
|
||||
makeTestItem := func(id, projectId string, hasDueDate, hasLabels, done bool) *item {
|
||||
makeTestItem := func(id, projectId int64, hasDueDate, hasLabels, done bool) *item {
|
||||
item := &item{
|
||||
ID: id,
|
||||
UserID: "1855589",
|
||||
UserID: 1855589,
|
||||
ProjectID: projectId,
|
||||
Content: "Task" + id,
|
||||
Content: "Task" + strconv.FormatInt(id, 10),
|
||||
Priority: 1,
|
||||
ParentID: 0,
|
||||
ChildOrder: 1,
|
||||
DateAdded: time1,
|
||||
DateCompleted: nilTime,
|
||||
}
|
||||
|
||||
if done {
|
||||
item.Checked = true
|
||||
item.Checked = 1
|
||||
item.DateCompleted = time3
|
||||
}
|
||||
|
||||
if hasLabels {
|
||||
item.Labels = []string{
|
||||
"Label1",
|
||||
"Label2",
|
||||
"Label3",
|
||||
"Label4",
|
||||
item.Labels = []int64{
|
||||
80000,
|
||||
80001,
|
||||
80002,
|
||||
80003,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -89,159 +91,164 @@ func TestConvertTodoistToVikunja(t *testing.T) {
|
|||
testSync := &sync{
|
||||
Projects: []*project{
|
||||
{
|
||||
ID: "396936926",
|
||||
ID: 396936926,
|
||||
Name: "Project1",
|
||||
Color: "berry_red",
|
||||
Color: 30,
|
||||
ChildOrder: 1,
|
||||
Collapsed: false,
|
||||
Collapsed: 0,
|
||||
Shared: false,
|
||||
IsDeleted: false,
|
||||
IsArchived: false,
|
||||
IsFavorite: false,
|
||||
IsDeleted: 0,
|
||||
IsArchived: 0,
|
||||
IsFavorite: 0,
|
||||
},
|
||||
{
|
||||
ID: "396936927",
|
||||
ID: 396936927,
|
||||
Name: "Project2",
|
||||
Color: "mint_green",
|
||||
Color: 37,
|
||||
ChildOrder: 1,
|
||||
Collapsed: false,
|
||||
Collapsed: 0,
|
||||
Shared: false,
|
||||
IsDeleted: false,
|
||||
IsArchived: false,
|
||||
IsFavorite: false,
|
||||
IsDeleted: 0,
|
||||
IsArchived: 0,
|
||||
IsFavorite: 0,
|
||||
},
|
||||
{
|
||||
ID: "396936928",
|
||||
ID: 396936928,
|
||||
Name: "Project3 - Archived",
|
||||
Color: "mint_green",
|
||||
Color: 37,
|
||||
ChildOrder: 1,
|
||||
Collapsed: false,
|
||||
Collapsed: 0,
|
||||
Shared: false,
|
||||
IsDeleted: false,
|
||||
IsArchived: true,
|
||||
IsFavorite: false,
|
||||
IsDeleted: 0,
|
||||
IsArchived: 1,
|
||||
IsFavorite: 0,
|
||||
},
|
||||
},
|
||||
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: false,
|
||||
Checked: 0,
|
||||
DateAdded: time1,
|
||||
},
|
||||
{
|
||||
ID: "400000106",
|
||||
UserID: "1855589",
|
||||
ProjectID: "396936926",
|
||||
ID: 400000106,
|
||||
UserID: 1855589,
|
||||
ProjectID: 396936926,
|
||||
Content: "Task400000106",
|
||||
Priority: 1,
|
||||
ParentID: "",
|
||||
ParentID: 0,
|
||||
ChildOrder: 1,
|
||||
DateAdded: time1,
|
||||
Checked: true,
|
||||
Checked: 1,
|
||||
DateCompleted: time3,
|
||||
Due: &dueDate{
|
||||
Date: "2021-01-31T19:00:00Z",
|
||||
Timezone: nil,
|
||||
IsRecurring: false,
|
||||
},
|
||||
Labels: []string{
|
||||
"Label1",
|
||||
"Label2",
|
||||
"Label3",
|
||||
"Label4",
|
||||
Labels: []int64{
|
||||
80000,
|
||||
80001,
|
||||
80002,
|
||||
80003,
|
||||
},
|
||||
},
|
||||
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: true,
|
||||
Checked: 1,
|
||||
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: "berry_red",
|
||||
Color: 30,
|
||||
},
|
||||
{
|
||||
ID: "80001",
|
||||
ID: 80001,
|
||||
Name: "Label2",
|
||||
Color: "red",
|
||||
Color: 31,
|
||||
},
|
||||
{
|
||||
ID: "80002",
|
||||
ID: 80002,
|
||||
Name: "Label3",
|
||||
Color: "orange",
|
||||
Color: 32,
|
||||
},
|
||||
{
|
||||
ID: "80003",
|
||||
ID: 80003,
|
||||
Name: "Label4",
|
||||
Color: "yellow",
|
||||
Color: 33,
|
||||
},
|
||||
},
|
||||
Notes: []*note{
|
||||
{
|
||||
ID: "101476",
|
||||
ItemID: "400000000",
|
||||
Content: "Lorem Ipsum dolor sit amet",
|
||||
Posted: time1,
|
||||
ID: 101476,
|
||||
PostedUID: 1855589,
|
||||
ItemID: 400000000,
|
||||
Content: "Lorem Ipsum dolor sit amet",
|
||||
Posted: time1,
|
||||
},
|
||||
{
|
||||
ID: "101477",
|
||||
ItemID: "400000001",
|
||||
Content: "Lorem Ipsum dolor sit amet",
|
||||
Posted: time1,
|
||||
ID: 101477,
|
||||
PostedUID: 1855589,
|
||||
ItemID: 400000001,
|
||||
Content: "Lorem Ipsum dolor sit amet",
|
||||
Posted: time1,
|
||||
},
|
||||
{
|
||||
ID: "101478",
|
||||
ItemID: "400000003",
|
||||
Content: "Lorem Ipsum dolor sit amet",
|
||||
Posted: time1,
|
||||
ID: 101478,
|
||||
PostedUID: 1855589,
|
||||
ItemID: 400000003,
|
||||
Content: "Lorem Ipsum dolor sit amet",
|
||||
Posted: time1,
|
||||
},
|
||||
{
|
||||
ID: "101479",
|
||||
ItemID: "400000010",
|
||||
Content: "Lorem Ipsum dolor sit amet",
|
||||
Posted: time1,
|
||||
ID: 101479,
|
||||
PostedUID: 1855589,
|
||||
ItemID: 400000010,
|
||||
Content: "Lorem Ipsum dolor sit amet",
|
||||
Posted: time1,
|
||||
},
|
||||
{
|
||||
ID: "101480",
|
||||
ItemID: "400000101",
|
||||
Content: "Lorem Ipsum dolor sit amet",
|
||||
ID: 101480,
|
||||
PostedUID: 1855589,
|
||||
ItemID: 400000101,
|
||||
Content: "Lorem Ipsum dolor sit amet",
|
||||
FileAttachment: &fileAttachment{
|
||||
FileName: "file.md",
|
||||
FileType: "text/plain",
|
||||
|
@ -256,38 +263,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,
|
||||
|
@ -295,40 +307,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,
|
||||
|
@ -337,9 +349,9 @@ func TestConvertTodoistToVikunja(t *testing.T) {
|
|||
},
|
||||
Sections: []*section{
|
||||
{
|
||||
ID: "1234",
|
||||
ID: 1234,
|
||||
Name: "Some Bucket",
|
||||
ProjectID: "396936926",
|
||||
ProjectID: 396936926,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -347,19 +359,19 @@ func TestConvertTodoistToVikunja(t *testing.T) {
|
|||
vikunjaLabels := []*models.Label{
|
||||
{
|
||||
Title: "Label1",
|
||||
HexColor: todoistColors["berry_red"],
|
||||
HexColor: todoistColors[30],
|
||||
},
|
||||
{
|
||||
Title: "Label2",
|
||||
HexColor: todoistColors["red"],
|
||||
HexColor: todoistColors[31],
|
||||
},
|
||||
{
|
||||
Title: "Label3",
|
||||
HexColor: todoistColors["orange"],
|
||||
HexColor: todoistColors[32],
|
||||
},
|
||||
{
|
||||
Title: "Label4",
|
||||
HexColor: todoistColors["yellow"],
|
||||
HexColor: todoistColors[33],
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -373,11 +385,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["berry_red"],
|
||||
HexColor: todoistColors[30],
|
||||
},
|
||||
Buckets: []*models.Bucket{
|
||||
{
|
||||
ID: 1,
|
||||
ID: 1234,
|
||||
Title: "Some Bucket",
|
||||
},
|
||||
},
|
||||
|
@ -498,7 +510,7 @@ func TestConvertTodoistToVikunja(t *testing.T) {
|
|||
Done: true,
|
||||
Created: time1,
|
||||
DoneAt: time3,
|
||||
BucketID: 1,
|
||||
BucketID: 1234,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -507,7 +519,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["mint_green"],
|
||||
HexColor: todoistColors[37],
|
||||
},
|
||||
Tasks: []*models.TaskWithComments{
|
||||
{
|
||||
|
@ -604,7 +616,7 @@ func TestConvertTodoistToVikunja(t *testing.T) {
|
|||
{
|
||||
List: models.List{
|
||||
Title: "Project3 - Archived",
|
||||
HexColor: todoistColors["mint_green"],
|
||||
HexColor: todoistColors[37],
|
||||
IsArchived: true,
|
||||
},
|
||||
Tasks: []*models.TaskWithComments{
|
||||
|
@ -622,7 +634,7 @@ func TestConvertTodoistToVikunja(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
doneItems := make(map[string]*doneItem)
|
||||
doneItems := make(map[int64]*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/testimage.jpg")
|
||||
exampleFile, err := os.ReadFile(config.ServiceRootpath.GetString() + "/pkg/modules/migration/wunderlist/testimage.jpg")
|
||||
assert.NoError(t, err)
|
||||
|
||||
trelloData := []*trello.Board{
|
||||
|
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
@ -0,0 +1,512 @@
|
|||
// 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"
|
||||
}
|
|
@ -0,0 +1,386 @@
|
|||
// 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, false); err != nil {
|
||||
if _, err := user.UpdateUser(s, u); err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err, c)
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ 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"
|
||||
|
@ -120,6 +121,10 @@ 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, false)
|
||||
_, err = user2.UpdateUser(s, user)
|
||||
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, true)
|
||||
_, err = user2.UpdateUser(s, user)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err, c)
|
||||
|
|
|
@ -48,6 +48,7 @@ package routes
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -69,6 +70,7 @@ 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
|
||||
|
@ -78,7 +80,7 @@ import (
|
|||
|
||||
"github.com/getsentry/sentry-go"
|
||||
sentryecho "github.com/getsentry/sentry-go/echo"
|
||||
echojwt "github.com/labstack/echo-jwt/v4"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/labstack/echo/v4/middleware"
|
||||
elog "github.com/labstack/gommon/log"
|
||||
|
@ -271,8 +273,29 @@ func registerAPIRoutes(a *echo.Group) {
|
|||
ur.POST("/shares/:share/auth", apiv1.AuthenticateLinkShare)
|
||||
}
|
||||
|
||||
// ===== Routes with Authentication =====
|
||||
a.Use(echojwt.JWT([]byte(config.ServiceJWTSecret.GetString())))
|
||||
// ===== 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
|
||||
},
|
||||
}))
|
||||
|
||||
// Rate limit
|
||||
setupRateLimit(a, config.RateLimitKind.GetString())
|
||||
|
@ -589,6 +612,16 @@ 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{
|
||||
|
|
|
@ -3061,6 +3061,113 @@ 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": [
|
||||
|
@ -7620,11 +7727,36 @@ const docTemplate = `{
|
|||
},
|
||||
"created_by": {
|
||||
"description": "The user who initially created the bucket.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/user.User"
|
||||
}
|
||||
]
|
||||
"$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.",
|
||||
|
@ -7643,10 +7775,24 @@ 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",
|
||||
|
@ -7662,7 +7808,9 @@ 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": {
|
||||
|
@ -7674,7 +7822,9 @@ const docTemplate = `{
|
|||
"items": {
|
||||
"$ref": "#/definitions/user.User"
|
||||
}
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.BulkTask": {
|
||||
|
@ -7708,11 +7858,7 @@ const docTemplate = `{
|
|||
},
|
||||
"created_by": {
|
||||
"description": "The user who initially created the task.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/user.User"
|
||||
}
|
||||
]
|
||||
"$ref": "#/definitions/user.User"
|
||||
},
|
||||
"description": {
|
||||
"description": "The task description.",
|
||||
|
@ -7784,11 +7930,7 @@ const docTemplate = `{
|
|||
},
|
||||
"related_tasks": {
|
||||
"description": "All related tasks, grouped by their relation kind",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.RelatedTaskMap"
|
||||
}
|
||||
]
|
||||
"$ref": "#/definitions/models.RelatedTaskMap"
|
||||
},
|
||||
"reminder_dates": {
|
||||
"description": "An array of datetimes when the user wants to be reminded of the task.",
|
||||
|
@ -7803,11 +7945,7 @@ 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.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.TaskRepeatMode"
|
||||
}
|
||||
]
|
||||
"type": "integer"
|
||||
},
|
||||
"start_date": {
|
||||
"description": "When this task starts.",
|
||||
|
@ -7815,11 +7953,7 @@ 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"
|
||||
}
|
||||
]
|
||||
"$ref": "#/definitions/models.Subscription"
|
||||
},
|
||||
"task_ids": {
|
||||
"description": "A list of task ids to update",
|
||||
|
@ -7836,7 +7970,9 @@ 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": {
|
||||
|
@ -7864,7 +8000,9 @@ 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": {
|
||||
|
@ -7876,11 +8014,7 @@ const docTemplate = `{
|
|||
},
|
||||
"created_by": {
|
||||
"description": "The user who created this label",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/user.User"
|
||||
}
|
||||
]
|
||||
"$ref": "#/definitions/user.User"
|
||||
},
|
||||
"description": {
|
||||
"description": "The label description.",
|
||||
|
@ -7904,7 +8038,9 @@ 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": {
|
||||
|
@ -7917,7 +8053,9 @@ const docTemplate = `{
|
|||
"label_id": {
|
||||
"description": "The label id you want to associate with a task.",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.LabelTaskBulk": {
|
||||
|
@ -7929,7 +8067,9 @@ const docTemplate = `{
|
|||
"items": {
|
||||
"$ref": "#/definitions/models.Label"
|
||||
}
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.LinkSharing": {
|
||||
|
@ -7957,36 +8097,26 @@ 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,
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.Right"
|
||||
}
|
||||
]
|
||||
"maximum": 2
|
||||
},
|
||||
"shared_by": {
|
||||
"description": "The user who shared this list",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/user.User"
|
||||
}
|
||||
]
|
||||
"$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,
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.SharingType"
|
||||
}
|
||||
]
|
||||
"maximum": 2
|
||||
},
|
||||
"updated": {
|
||||
"description": "A timestamp when this share was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.List": {
|
||||
|
@ -8035,11 +8165,7 @@ const docTemplate = `{
|
|||
},
|
||||
"owner": {
|
||||
"description": "The user who created this list.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/user.User"
|
||||
}
|
||||
]
|
||||
"$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.",
|
||||
|
@ -8047,11 +8173,7 @@ 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"
|
||||
}
|
||||
]
|
||||
"$ref": "#/definitions/models.Subscription"
|
||||
},
|
||||
"title": {
|
||||
"description": "The title of the list. You'll see this in the namespace overview.",
|
||||
|
@ -8062,7 +8184,9 @@ 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": {
|
||||
|
@ -8070,16 +8194,14 @@ const docTemplate = `{
|
|||
"properties": {
|
||||
"list": {
|
||||
"description": "The copied list",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.List"
|
||||
}
|
||||
]
|
||||
"$ref": "#/definitions/models.List"
|
||||
},
|
||||
"namespace_id": {
|
||||
"description": "The target namespace ID",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.ListUser": {
|
||||
|
@ -8095,13 +8217,9 @@ 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,
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.Right"
|
||||
}
|
||||
]
|
||||
"maximum": 2
|
||||
},
|
||||
"updated": {
|
||||
"description": "A timestamp when this relation was last updated. You cannot change this value.",
|
||||
|
@ -8110,7 +8228,9 @@ const docTemplate = `{
|
|||
"user_id": {
|
||||
"description": "The username.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.Message": {
|
||||
|
@ -8148,19 +8268,11 @@ const docTemplate = `{
|
|||
},
|
||||
"owner": {
|
||||
"description": "The user who owns this namespace",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/user.User"
|
||||
}
|
||||
]
|
||||
"$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"
|
||||
}
|
||||
]
|
||||
"$ref": "#/definitions/models.Subscription"
|
||||
},
|
||||
"title": {
|
||||
"description": "The name of this namespace.",
|
||||
|
@ -8171,7 +8283,9 @@ 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": {
|
||||
|
@ -8187,13 +8301,9 @@ 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,
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.Right"
|
||||
}
|
||||
]
|
||||
"maximum": 2
|
||||
},
|
||||
"updated": {
|
||||
"description": "A timestamp when this relation was last updated. You cannot change this value.",
|
||||
|
@ -8202,7 +8312,9 @@ const docTemplate = `{
|
|||
"user_id": {
|
||||
"description": "The username.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.NamespaceWithLists": {
|
||||
|
@ -8237,19 +8349,11 @@ const docTemplate = `{
|
|||
},
|
||||
"owner": {
|
||||
"description": "The user who owns this namespace",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/user.User"
|
||||
}
|
||||
]
|
||||
"$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"
|
||||
}
|
||||
]
|
||||
"$ref": "#/definitions/models.Subscription"
|
||||
},
|
||||
"title": {
|
||||
"description": "The name of this namespace.",
|
||||
|
@ -8260,7 +8364,9 @@ 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": {
|
||||
|
@ -8272,50 +8378,6 @@ 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": {
|
||||
|
@ -8329,11 +8391,7 @@ const docTemplate = `{
|
|||
},
|
||||
"filters": {
|
||||
"description": "The actual filters this filter contains",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.TaskCollection"
|
||||
}
|
||||
]
|
||||
"$ref": "#/definitions/models.TaskCollection"
|
||||
},
|
||||
"id": {
|
||||
"description": "The unique numeric id of this saved filter",
|
||||
|
@ -8345,11 +8403,7 @@ const docTemplate = `{
|
|||
},
|
||||
"owner": {
|
||||
"description": "The user who owns this filter",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/user.User"
|
||||
}
|
||||
]
|
||||
"$ref": "#/definitions/user.User"
|
||||
},
|
||||
"title": {
|
||||
"description": "The title of the filter.",
|
||||
|
@ -8360,22 +8414,11 @@ 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",
|
||||
"properties": {
|
||||
|
@ -8396,12 +8439,10 @@ const docTemplate = `{
|
|||
},
|
||||
"user": {
|
||||
"description": "The user who made this subscription",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/user.User"
|
||||
}
|
||||
]
|
||||
}
|
||||
"$ref": "#/definitions/user.User"
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.Task": {
|
||||
|
@ -8435,11 +8476,7 @@ const docTemplate = `{
|
|||
},
|
||||
"created_by": {
|
||||
"description": "The user who initially created the task.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/user.User"
|
||||
}
|
||||
]
|
||||
"$ref": "#/definitions/user.User"
|
||||
},
|
||||
"description": {
|
||||
"description": "The task description.",
|
||||
|
@ -8511,11 +8548,7 @@ const docTemplate = `{
|
|||
},
|
||||
"related_tasks": {
|
||||
"description": "All related tasks, grouped by their relation kind",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.RelatedTaskMap"
|
||||
}
|
||||
]
|
||||
"$ref": "#/definitions/models.RelatedTaskMap"
|
||||
},
|
||||
"reminder_dates": {
|
||||
"description": "An array of datetimes when the user wants to be reminded of the task.",
|
||||
|
@ -8530,11 +8563,7 @@ 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.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.TaskRepeatMode"
|
||||
}
|
||||
]
|
||||
"type": "integer"
|
||||
},
|
||||
"start_date": {
|
||||
"description": "When this task starts.",
|
||||
|
@ -8542,11 +8571,7 @@ 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"
|
||||
}
|
||||
]
|
||||
"$ref": "#/definitions/models.Subscription"
|
||||
},
|
||||
"title": {
|
||||
"description": "The task text. This is what you'll see in the list.",
|
||||
|
@ -8556,7 +8581,9 @@ 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": {
|
||||
|
@ -8567,7 +8594,9 @@ const docTemplate = `{
|
|||
},
|
||||
"user_id": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.TaskAttachment": {
|
||||
|
@ -8587,7 +8616,9 @@ const docTemplate = `{
|
|||
},
|
||||
"task_id": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.TaskCollection": {
|
||||
|
@ -8635,7 +8666,9 @@ const docTemplate = `{
|
|||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.TaskComment": {
|
||||
|
@ -8655,7 +8688,9 @@ const docTemplate = `{
|
|||
},
|
||||
"updated": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.TaskRelation": {
|
||||
|
@ -8667,11 +8702,7 @@ const docTemplate = `{
|
|||
},
|
||||
"created_by": {
|
||||
"description": "The user who created this relation",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/user.User"
|
||||
}
|
||||
]
|
||||
"$ref": "#/definitions/user.User"
|
||||
},
|
||||
"other_task_id": {
|
||||
"description": "The ID of the other task, the task which is being related.",
|
||||
|
@ -8679,31 +8710,16 @@ const docTemplate = `{
|
|||
},
|
||||
"relation_kind": {
|
||||
"description": "The kind of the relation.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.RelationKind"
|
||||
}
|
||||
]
|
||||
"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": {}
|
||||
}
|
||||
},
|
||||
"models.TaskRepeatMode": {
|
||||
"type": "integer",
|
||||
"enum": [
|
||||
0,
|
||||
1,
|
||||
2
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"TaskRepeatModeDefault",
|
||||
"TaskRepeatModeMonth",
|
||||
"TaskRepeatModeFromCurrentDate"
|
||||
]
|
||||
},
|
||||
"models.Team": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -8713,11 +8729,7 @@ const docTemplate = `{
|
|||
},
|
||||
"created_by": {
|
||||
"description": "The user who created this team.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/user.User"
|
||||
}
|
||||
]
|
||||
"$ref": "#/definitions/user.User"
|
||||
},
|
||||
"description": {
|
||||
"description": "The team's description.",
|
||||
|
@ -8743,7 +8755,9 @@ 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": {
|
||||
|
@ -8759,13 +8773,9 @@ 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,
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.Right"
|
||||
}
|
||||
]
|
||||
"maximum": 2
|
||||
},
|
||||
"team_id": {
|
||||
"description": "The team id.",
|
||||
|
@ -8774,7 +8784,9 @@ 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": {
|
||||
|
@ -8795,7 +8807,9 @@ 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": {
|
||||
|
@ -8811,13 +8825,9 @@ 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,
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.Right"
|
||||
}
|
||||
]
|
||||
"maximum": 2
|
||||
},
|
||||
"team_id": {
|
||||
"description": "The team id.",
|
||||
|
@ -8826,7 +8836,9 @@ 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": {
|
||||
|
@ -8862,7 +8874,8 @@ const docTemplate = `{
|
|||
"type": "string",
|
||||
"maxLength": 250,
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"web.Auth": {}
|
||||
}
|
||||
},
|
||||
"models.TeamWithRight": {
|
||||
|
@ -8874,11 +8887,7 @@ const docTemplate = `{
|
|||
},
|
||||
"created_by": {
|
||||
"description": "The user who created this team.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/user.User"
|
||||
}
|
||||
]
|
||||
"$ref": "#/definitions/user.User"
|
||||
},
|
||||
"description": {
|
||||
"description": "The team's description.",
|
||||
|
@ -8902,12 +8911,16 @@ const docTemplate = `{
|
|||
"minLength": 1
|
||||
},
|
||||
"right": {
|
||||
"$ref": "#/definitions/models.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
|
||||
},
|
||||
"updated": {
|
||||
"description": "A timestamp when this relation was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.UserWithRight": {
|
||||
|
@ -8931,7 +8944,9 @@ const docTemplate = `{
|
|||
"type": "string"
|
||||
},
|
||||
"right": {
|
||||
"$ref": "#/definitions/models.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
|
||||
},
|
||||
"updated": {
|
||||
"description": "A timestamp when this task was last updated. You cannot change this value.",
|
||||
|
@ -8942,7 +8957,8 @@ const docTemplate = `{
|
|||
"type": "string",
|
||||
"maxLength": 250,
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"web.Auth": {}
|
||||
}
|
||||
},
|
||||
"notifications.DatabaseNotification": {
|
||||
|
@ -8992,9 +9008,6 @@ const docTemplate = `{
|
|||
"key": {
|
||||
"type": "string"
|
||||
},
|
||||
"logout_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
|
@ -9174,7 +9187,8 @@ const docTemplate = `{
|
|||
"type": "string",
|
||||
"maxLength": 250,
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"web.Auth": {}
|
||||
}
|
||||
},
|
||||
"v1.LinkShareAuth": {
|
||||
|
@ -9382,6 +9396,15 @@ 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": {
|
||||
|
|
|
@ -3052,6 +3052,113 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/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": [
|
||||
|
@ -7611,11 +7718,36 @@
|
|||
},
|
||||
"created_by": {
|
||||
"description": "The user who initially created the bucket.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/user.User"
|
||||
}
|
||||
]
|
||||
"$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.",
|
||||
|
@ -7634,10 +7766,24 @@
|
|||
"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",
|
||||
|
@ -7653,7 +7799,9 @@
|
|||
"updated": {
|
||||
"description": "A timestamp when this bucket was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.BulkAssignees": {
|
||||
|
@ -7665,7 +7813,9 @@
|
|||
"items": {
|
||||
"$ref": "#/definitions/user.User"
|
||||
}
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.BulkTask": {
|
||||
|
@ -7699,11 +7849,7 @@
|
|||
},
|
||||
"created_by": {
|
||||
"description": "The user who initially created the task.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/user.User"
|
||||
}
|
||||
]
|
||||
"$ref": "#/definitions/user.User"
|
||||
},
|
||||
"description": {
|
||||
"description": "The task description.",
|
||||
|
@ -7775,11 +7921,7 @@
|
|||
},
|
||||
"related_tasks": {
|
||||
"description": "All related tasks, grouped by their relation kind",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.RelatedTaskMap"
|
||||
}
|
||||
]
|
||||
"$ref": "#/definitions/models.RelatedTaskMap"
|
||||
},
|
||||
"reminder_dates": {
|
||||
"description": "An array of datetimes when the user wants to be reminded of the task.",
|
||||
|
@ -7794,11 +7936,7 @@
|
|||
},
|
||||
"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.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.TaskRepeatMode"
|
||||
}
|
||||
]
|
||||
"type": "integer"
|
||||
},
|
||||
"start_date": {
|
||||
"description": "When this task starts.",
|
||||
|
@ -7806,11 +7944,7 @@
|
|||
},
|
||||
"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"
|
||||
}
|
||||
]
|
||||
"$ref": "#/definitions/models.Subscription"
|
||||
},
|
||||
"task_ids": {
|
||||
"description": "A list of task ids to update",
|
||||
|
@ -7827,7 +7961,9 @@
|
|||
"updated": {
|
||||
"description": "A timestamp when this task was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.DatabaseNotifications": {
|
||||
|
@ -7855,7 +7991,9 @@
|
|||
"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": {
|
||||
|
@ -7867,11 +8005,7 @@
|
|||
},
|
||||
"created_by": {
|
||||
"description": "The user who created this label",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/user.User"
|
||||
}
|
||||
]
|
||||
"$ref": "#/definitions/user.User"
|
||||
},
|
||||
"description": {
|
||||
"description": "The label description.",
|
||||
|
@ -7895,7 +8029,9 @@
|
|||
"updated": {
|
||||
"description": "A timestamp when this label was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.LabelTask": {
|
||||
|
@ -7908,7 +8044,9 @@
|
|||
"label_id": {
|
||||
"description": "The label id you want to associate with a task.",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.LabelTaskBulk": {
|
||||
|
@ -7920,7 +8058,9 @@
|
|||
"items": {
|
||||
"$ref": "#/definitions/models.Label"
|
||||
}
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.LinkSharing": {
|
||||
|
@ -7948,36 +8088,26 @@
|
|||
},
|
||||
"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,
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.Right"
|
||||
}
|
||||
]
|
||||
"maximum": 2
|
||||
},
|
||||
"shared_by": {
|
||||
"description": "The user who shared this list",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/user.User"
|
||||
}
|
||||
]
|
||||
"$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,
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.SharingType"
|
||||
}
|
||||
]
|
||||
"maximum": 2
|
||||
},
|
||||
"updated": {
|
||||
"description": "A timestamp when this share was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.List": {
|
||||
|
@ -8026,11 +8156,7 @@
|
|||
},
|
||||
"owner": {
|
||||
"description": "The user who created this list.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/user.User"
|
||||
}
|
||||
]
|
||||
"$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.",
|
||||
|
@ -8038,11 +8164,7 @@
|
|||
},
|
||||
"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"
|
||||
}
|
||||
]
|
||||
"$ref": "#/definitions/models.Subscription"
|
||||
},
|
||||
"title": {
|
||||
"description": "The title of the list. You'll see this in the namespace overview.",
|
||||
|
@ -8053,7 +8175,9 @@
|
|||
"updated": {
|
||||
"description": "A timestamp when this list was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.ListDuplicate": {
|
||||
|
@ -8061,16 +8185,14 @@
|
|||
"properties": {
|
||||
"list": {
|
||||
"description": "The copied list",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.List"
|
||||
}
|
||||
]
|
||||
"$ref": "#/definitions/models.List"
|
||||
},
|
||||
"namespace_id": {
|
||||
"description": "The target namespace ID",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.ListUser": {
|
||||
|
@ -8086,13 +8208,9 @@
|
|||
},
|
||||
"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,
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.Right"
|
||||
}
|
||||
]
|
||||
"maximum": 2
|
||||
},
|
||||
"updated": {
|
||||
"description": "A timestamp when this relation was last updated. You cannot change this value.",
|
||||
|
@ -8101,7 +8219,9 @@
|
|||
"user_id": {
|
||||
"description": "The username.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.Message": {
|
||||
|
@ -8139,19 +8259,11 @@
|
|||
},
|
||||
"owner": {
|
||||
"description": "The user who owns this namespace",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/user.User"
|
||||
}
|
||||
]
|
||||
"$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"
|
||||
}
|
||||
]
|
||||
"$ref": "#/definitions/models.Subscription"
|
||||
},
|
||||
"title": {
|
||||
"description": "The name of this namespace.",
|
||||
|
@ -8162,7 +8274,9 @@
|
|||
"updated": {
|
||||
"description": "A timestamp when this namespace was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.NamespaceUser": {
|
||||
|
@ -8178,13 +8292,9 @@
|
|||
},
|
||||
"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,
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.Right"
|
||||
}
|
||||
]
|
||||
"maximum": 2
|
||||
},
|
||||
"updated": {
|
||||
"description": "A timestamp when this relation was last updated. You cannot change this value.",
|
||||
|
@ -8193,7 +8303,9 @@
|
|||
"user_id": {
|
||||
"description": "The username.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.NamespaceWithLists": {
|
||||
|
@ -8228,19 +8340,11 @@
|
|||
},
|
||||
"owner": {
|
||||
"description": "The user who owns this namespace",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/user.User"
|
||||
}
|
||||
]
|
||||
"$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"
|
||||
}
|
||||
]
|
||||
"$ref": "#/definitions/models.Subscription"
|
||||
},
|
||||
"title": {
|
||||
"description": "The name of this namespace.",
|
||||
|
@ -8251,7 +8355,9 @@
|
|||
"updated": {
|
||||
"description": "A timestamp when this namespace was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.RelatedTaskMap": {
|
||||
|
@ -8263,50 +8369,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
|
@ -8320,11 +8382,7 @@
|
|||
},
|
||||
"filters": {
|
||||
"description": "The actual filters this filter contains",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.TaskCollection"
|
||||
}
|
||||
]
|
||||
"$ref": "#/definitions/models.TaskCollection"
|
||||
},
|
||||
"id": {
|
||||
"description": "The unique numeric id of this saved filter",
|
||||
|
@ -8336,11 +8394,7 @@
|
|||
},
|
||||
"owner": {
|
||||
"description": "The user who owns this filter",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/user.User"
|
||||
}
|
||||
]
|
||||
"$ref": "#/definitions/user.User"
|
||||
},
|
||||
"title": {
|
||||
"description": "The title of the filter.",
|
||||
|
@ -8351,22 +8405,11 @@
|
|||
"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",
|
||||
"properties": {
|
||||
|
@ -8387,12 +8430,10 @@
|
|||
},
|
||||
"user": {
|
||||
"description": "The user who made this subscription",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/user.User"
|
||||
}
|
||||
]
|
||||
}
|
||||
"$ref": "#/definitions/user.User"
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.Task": {
|
||||
|
@ -8426,11 +8467,7 @@
|
|||
},
|
||||
"created_by": {
|
||||
"description": "The user who initially created the task.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/user.User"
|
||||
}
|
||||
]
|
||||
"$ref": "#/definitions/user.User"
|
||||
},
|
||||
"description": {
|
||||
"description": "The task description.",
|
||||
|
@ -8502,11 +8539,7 @@
|
|||
},
|
||||
"related_tasks": {
|
||||
"description": "All related tasks, grouped by their relation kind",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.RelatedTaskMap"
|
||||
}
|
||||
]
|
||||
"$ref": "#/definitions/models.RelatedTaskMap"
|
||||
},
|
||||
"reminder_dates": {
|
||||
"description": "An array of datetimes when the user wants to be reminded of the task.",
|
||||
|
@ -8521,11 +8554,7 @@
|
|||
},
|
||||
"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.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.TaskRepeatMode"
|
||||
}
|
||||
]
|
||||
"type": "integer"
|
||||
},
|
||||
"start_date": {
|
||||
"description": "When this task starts.",
|
||||
|
@ -8533,11 +8562,7 @@
|
|||
},
|
||||
"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"
|
||||
}
|
||||
]
|
||||
"$ref": "#/definitions/models.Subscription"
|
||||
},
|
||||
"title": {
|
||||
"description": "The task text. This is what you'll see in the list.",
|
||||
|
@ -8547,7 +8572,9 @@
|
|||
"updated": {
|
||||
"description": "A timestamp when this task was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.TaskAssginee": {
|
||||
|
@ -8558,7 +8585,9 @@
|
|||
},
|
||||
"user_id": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.TaskAttachment": {
|
||||
|
@ -8578,7 +8607,9 @@
|
|||
},
|
||||
"task_id": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.TaskCollection": {
|
||||
|
@ -8626,7 +8657,9 @@
|
|||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.TaskComment": {
|
||||
|
@ -8646,7 +8679,9 @@
|
|||
},
|
||||
"updated": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.TaskRelation": {
|
||||
|
@ -8658,11 +8693,7 @@
|
|||
},
|
||||
"created_by": {
|
||||
"description": "The user who created this relation",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/user.User"
|
||||
}
|
||||
]
|
||||
"$ref": "#/definitions/user.User"
|
||||
},
|
||||
"other_task_id": {
|
||||
"description": "The ID of the other task, the task which is being related.",
|
||||
|
@ -8670,31 +8701,16 @@
|
|||
},
|
||||
"relation_kind": {
|
||||
"description": "The kind of the relation.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.RelationKind"
|
||||
}
|
||||
]
|
||||
"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": {}
|
||||
}
|
||||
},
|
||||
"models.TaskRepeatMode": {
|
||||
"type": "integer",
|
||||
"enum": [
|
||||
0,
|
||||
1,
|
||||
2
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"TaskRepeatModeDefault",
|
||||
"TaskRepeatModeMonth",
|
||||
"TaskRepeatModeFromCurrentDate"
|
||||
]
|
||||
},
|
||||
"models.Team": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -8704,11 +8720,7 @@
|
|||
},
|
||||
"created_by": {
|
||||
"description": "The user who created this team.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/user.User"
|
||||
}
|
||||
]
|
||||
"$ref": "#/definitions/user.User"
|
||||
},
|
||||
"description": {
|
||||
"description": "The team's description.",
|
||||
|
@ -8734,7 +8746,9 @@
|
|||
"updated": {
|
||||
"description": "A timestamp when this relation was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.TeamList": {
|
||||
|
@ -8750,13 +8764,9 @@
|
|||
},
|
||||
"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,
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.Right"
|
||||
}
|
||||
]
|
||||
"maximum": 2
|
||||
},
|
||||
"team_id": {
|
||||
"description": "The team id.",
|
||||
|
@ -8765,7 +8775,9 @@
|
|||
"updated": {
|
||||
"description": "A timestamp when this relation was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.TeamMember": {
|
||||
|
@ -8786,7 +8798,9 @@
|
|||
"username": {
|
||||
"description": "The username of the member. We use this to prevent automated user id entering.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.TeamNamespace": {
|
||||
|
@ -8802,13 +8816,9 @@
|
|||
},
|
||||
"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,
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.Right"
|
||||
}
|
||||
]
|
||||
"maximum": 2
|
||||
},
|
||||
"team_id": {
|
||||
"description": "The team id.",
|
||||
|
@ -8817,7 +8827,9 @@
|
|||
"updated": {
|
||||
"description": "A timestamp when this relation was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.TeamUser": {
|
||||
|
@ -8853,7 +8865,8 @@
|
|||
"type": "string",
|
||||
"maxLength": 250,
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"web.Auth": {}
|
||||
}
|
||||
},
|
||||
"models.TeamWithRight": {
|
||||
|
@ -8865,11 +8878,7 @@
|
|||
},
|
||||
"created_by": {
|
||||
"description": "The user who created this team.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/user.User"
|
||||
}
|
||||
]
|
||||
"$ref": "#/definitions/user.User"
|
||||
},
|
||||
"description": {
|
||||
"description": "The team's description.",
|
||||
|
@ -8893,12 +8902,16 @@
|
|||
"minLength": 1
|
||||
},
|
||||
"right": {
|
||||
"$ref": "#/definitions/models.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
|
||||
},
|
||||
"updated": {
|
||||
"description": "A timestamp when this relation was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.UserWithRight": {
|
||||
|
@ -8922,7 +8935,9 @@
|
|||
"type": "string"
|
||||
},
|
||||
"right": {
|
||||
"$ref": "#/definitions/models.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
|
||||
},
|
||||
"updated": {
|
||||
"description": "A timestamp when this task was last updated. You cannot change this value.",
|
||||
|
@ -8933,7 +8948,8 @@
|
|||
"type": "string",
|
||||
"maxLength": 250,
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"web.Auth": {}
|
||||
}
|
||||
},
|
||||
"notifications.DatabaseNotification": {
|
||||
|
@ -8983,9 +8999,6 @@
|
|||
"key": {
|
||||
"type": "string"
|
||||
},
|
||||
"logout_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
|
@ -9165,7 +9178,8 @@
|
|||
"type": "string",
|
||||
"maxLength": 250,
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"web.Auth": {}
|
||||
}
|
||||
},
|
||||
"v1.LinkShareAuth": {
|
||||
|
@ -9373,6 +9387,15 @@
|
|||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"wunderlist.Migration": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"code": {
|
||||
"description": "Code is the code used to get a user api token",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"securityDefinitions": {
|
||||
|
|
|
@ -58,9 +58,30 @@ definitions:
|
|||
value.
|
||||
type: string
|
||||
created_by:
|
||||
allOf:
|
||||
- $ref: '#/definitions/user.User'
|
||||
$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
|
||||
|
@ -76,10 +97,22 @@ 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:
|
||||
|
@ -93,6 +126,8 @@ 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:
|
||||
|
@ -101,6 +136,8 @@ definitions:
|
|||
items:
|
||||
$ref: '#/definitions/user.User'
|
||||
type: array
|
||||
web.CRUDable: {}
|
||||
web.Rights: {}
|
||||
type: object
|
||||
models.BulkTask:
|
||||
properties:
|
||||
|
@ -126,8 +163,7 @@ definitions:
|
|||
value.
|
||||
type: string
|
||||
created_by:
|
||||
allOf:
|
||||
- $ref: '#/definitions/user.User'
|
||||
$ref: '#/definitions/user.User'
|
||||
description: The user who initially created the task.
|
||||
description:
|
||||
description: The task description.
|
||||
|
@ -192,8 +228,7 @@ definitions:
|
|||
sort by this later.
|
||||
type: integer
|
||||
related_tasks:
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.RelatedTaskMap'
|
||||
$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
|
||||
|
@ -207,18 +242,16 @@ 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:
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.Subscription'
|
||||
$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.
|
||||
|
@ -235,6 +268,8 @@ 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:
|
||||
|
@ -259,6 +294,8 @@ 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:
|
||||
|
@ -267,8 +304,7 @@ definitions:
|
|||
value.
|
||||
type: string
|
||||
created_by:
|
||||
allOf:
|
||||
- $ref: '#/definitions/user.User'
|
||||
$ref: '#/definitions/user.User'
|
||||
description: The user who created this label
|
||||
description:
|
||||
description: The label description.
|
||||
|
@ -290,6 +326,8 @@ 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:
|
||||
|
@ -300,6 +338,8 @@ 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:
|
||||
|
@ -308,6 +348,8 @@ definitions:
|
|||
items:
|
||||
$ref: '#/definitions/models.Label'
|
||||
type: array
|
||||
web.CRUDable: {}
|
||||
web.Rights: {}
|
||||
type: object
|
||||
models.LinkSharing:
|
||||
properties:
|
||||
|
@ -330,27 +372,26 @@ 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:
|
||||
allOf:
|
||||
- $ref: '#/definitions/user.User'
|
||||
$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:
|
||||
|
@ -392,16 +433,14 @@ definitions:
|
|||
namespace_id:
|
||||
type: integer
|
||||
owner:
|
||||
allOf:
|
||||
- $ref: '#/definitions/user.User'
|
||||
$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:
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.Subscription'
|
||||
$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.
|
||||
|
@ -414,16 +453,19 @@ 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:
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.List'
|
||||
$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:
|
||||
|
@ -435,12 +477,11 @@ 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.
|
||||
|
@ -448,6 +489,8 @@ definitions:
|
|||
user_id:
|
||||
description: The username.
|
||||
type: string
|
||||
web.CRUDable: {}
|
||||
web.Rights: {}
|
||||
type: object
|
||||
models.Message:
|
||||
properties:
|
||||
|
@ -475,12 +518,10 @@ definitions:
|
|||
description: Whether or not a namespace is archived.
|
||||
type: boolean
|
||||
owner:
|
||||
allOf:
|
||||
- $ref: '#/definitions/user.User'
|
||||
$ref: '#/definitions/user.User'
|
||||
description: The user who owns this namespace
|
||||
subscription:
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.Subscription'
|
||||
$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.
|
||||
|
@ -493,6 +534,8 @@ 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:
|
||||
|
@ -504,12 +547,11 @@ 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.
|
||||
|
@ -517,6 +559,8 @@ definitions:
|
|||
user_id:
|
||||
description: The username.
|
||||
type: string
|
||||
web.CRUDable: {}
|
||||
web.Rights: {}
|
||||
type: object
|
||||
models.NamespaceWithLists:
|
||||
properties:
|
||||
|
@ -542,12 +586,10 @@ definitions:
|
|||
$ref: '#/definitions/models.List'
|
||||
type: array
|
||||
owner:
|
||||
allOf:
|
||||
- $ref: '#/definitions/user.User'
|
||||
$ref: '#/definitions/user.User'
|
||||
description: The user who owns this namespace
|
||||
subscription:
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.Subscription'
|
||||
$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.
|
||||
|
@ -560,6 +602,8 @@ 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:
|
||||
|
@ -567,44 +611,6 @@ 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:
|
||||
|
@ -615,8 +621,7 @@ definitions:
|
|||
description: The description of the filter
|
||||
type: string
|
||||
filters:
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.TaskCollection'
|
||||
$ref: '#/definitions/models.TaskCollection'
|
||||
description: The actual filters this filter contains
|
||||
id:
|
||||
description: The unique numeric id of this saved filter
|
||||
|
@ -626,8 +631,7 @@ definitions:
|
|||
a separate namespace together with favorite lists.
|
||||
type: boolean
|
||||
owner:
|
||||
allOf:
|
||||
- $ref: '#/definitions/user.User'
|
||||
$ref: '#/definitions/user.User'
|
||||
description: The user who owns this filter
|
||||
title:
|
||||
description: The title of the filter.
|
||||
|
@ -638,17 +642,9 @@ 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:
|
||||
|
@ -664,9 +660,10 @@ definitions:
|
|||
description: The numeric ID of the subscription
|
||||
type: integer
|
||||
user:
|
||||
allOf:
|
||||
- $ref: '#/definitions/user.User'
|
||||
$ref: '#/definitions/user.User'
|
||||
description: The user who made this subscription
|
||||
web.CRUDable: {}
|
||||
web.Rights: {}
|
||||
type: object
|
||||
models.Task:
|
||||
properties:
|
||||
|
@ -692,8 +689,7 @@ definitions:
|
|||
value.
|
||||
type: string
|
||||
created_by:
|
||||
allOf:
|
||||
- $ref: '#/definitions/user.User'
|
||||
$ref: '#/definitions/user.User'
|
||||
description: The user who initially created the task.
|
||||
description:
|
||||
description: The task description.
|
||||
|
@ -758,8 +754,7 @@ definitions:
|
|||
sort by this later.
|
||||
type: integer
|
||||
related_tasks:
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.RelatedTaskMap'
|
||||
$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
|
||||
|
@ -773,18 +768,16 @@ 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:
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.Subscription'
|
||||
$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.
|
||||
|
@ -796,6 +789,8 @@ 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:
|
||||
|
@ -803,6 +798,8 @@ definitions:
|
|||
type: string
|
||||
user_id:
|
||||
type: integer
|
||||
web.CRUDable: {}
|
||||
web.Rights: {}
|
||||
type: object
|
||||
models.TaskAttachment:
|
||||
properties:
|
||||
|
@ -816,6 +813,8 @@ definitions:
|
|||
type: integer
|
||||
task_id:
|
||||
type: integer
|
||||
web.CRUDable: {}
|
||||
web.Rights: {}
|
||||
type: object
|
||||
models.TaskCollection:
|
||||
properties:
|
||||
|
@ -853,6 +852,8 @@ definitions:
|
|||
items:
|
||||
type: string
|
||||
type: array
|
||||
web.CRUDable: {}
|
||||
web.Rights: {}
|
||||
type: object
|
||||
models.TaskComment:
|
||||
properties:
|
||||
|
@ -866,6 +867,8 @@ definitions:
|
|||
type: integer
|
||||
updated:
|
||||
type: string
|
||||
web.CRUDable: {}
|
||||
web.Rights: {}
|
||||
type: object
|
||||
models.TaskRelation:
|
||||
properties:
|
||||
|
@ -874,30 +877,20 @@ definitions:
|
|||
value.
|
||||
type: string
|
||||
created_by:
|
||||
allOf:
|
||||
- $ref: '#/definitions/user.User'
|
||||
$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:
|
||||
|
@ -905,8 +898,7 @@ definitions:
|
|||
this value.
|
||||
type: string
|
||||
created_by:
|
||||
allOf:
|
||||
- $ref: '#/definitions/user.User'
|
||||
$ref: '#/definitions/user.User'
|
||||
description: The user who created this team.
|
||||
description:
|
||||
description: The team's description.
|
||||
|
@ -928,6 +920,8 @@ 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:
|
||||
|
@ -939,12 +933,11 @@ 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
|
||||
|
@ -952,6 +945,8 @@ 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:
|
||||
|
@ -970,6 +965,8 @@ 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:
|
||||
|
@ -981,12 +978,11 @@ 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
|
||||
|
@ -994,6 +990,8 @@ 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:
|
||||
|
@ -1024,6 +1022,7 @@ definitions:
|
|||
maxLength: 250
|
||||
minLength: 1
|
||||
type: string
|
||||
web.Auth: {}
|
||||
type: object
|
||||
models.TeamWithRight:
|
||||
properties:
|
||||
|
@ -1032,8 +1031,7 @@ definitions:
|
|||
this value.
|
||||
type: string
|
||||
created_by:
|
||||
allOf:
|
||||
- $ref: '#/definitions/user.User'
|
||||
$ref: '#/definitions/user.User'
|
||||
description: The user who created this team.
|
||||
description:
|
||||
description: The team's description.
|
||||
|
@ -1052,11 +1050,16 @@ definitions:
|
|||
minLength: 1
|
||||
type: string
|
||||
right:
|
||||
$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.
|
||||
type: integer
|
||||
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:
|
||||
|
@ -1075,7 +1078,10 @@ definitions:
|
|||
description: The full name of the user.
|
||||
type: string
|
||||
right:
|
||||
$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.
|
||||
type: integer
|
||||
updated:
|
||||
description: A timestamp when this task was last updated. You cannot change
|
||||
this value.
|
||||
|
@ -1085,6 +1091,7 @@ definitions:
|
|||
maxLength: 250
|
||||
minLength: 1
|
||||
type: string
|
||||
web.Auth: {}
|
||||
type: object
|
||||
notifications.DatabaseNotification:
|
||||
properties:
|
||||
|
@ -1120,8 +1127,6 @@ definitions:
|
|||
type: string
|
||||
key:
|
||||
type: string
|
||||
logout_url:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
type: object
|
||||
|
@ -1253,6 +1258,7 @@ definitions:
|
|||
maxLength: 250
|
||||
minLength: 1
|
||||
type: string
|
||||
web.Auth: {}
|
||||
type: object
|
||||
v1.LinkShareAuth:
|
||||
properties:
|
||||
|
@ -1401,6 +1407,12 @@ 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
|
||||
|
@ -3478,6 +3490,77 @@ 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:
|
||||
|
|
|
@ -419,7 +419,7 @@ func GetUserFromClaims(claims jwt.MapClaims) (user *User, err error) {
|
|||
}
|
||||
|
||||
// UpdateUser updates a user
|
||||
func UpdateUser(s *xorm.Session, user *User, forceOverride bool) (updatedUser *User, err error) {
|
||||
func UpdateUser(s *xorm.Session, user *User) (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, forceOverride bool) (updatedUser *U
|
|||
}
|
||||
|
||||
// Check if we have a name
|
||||
if user.Name == "" && !forceOverride {
|
||||
if user.Name == "" {
|
||||
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