Compare commits

..

21 Commits

Author SHA1 Message Date
renovate 369319c709 fix(deps): update module golang.org/x/oauth2 to v0.4.0
continuous-integration/drone/pr Build is passing Details
2023-01-24 16:01:23 +00:00
renovate fecce19f06 fix(deps): update module github.com/labstack/echo-jwt/v4 to v4.0.1 (#1369)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#1369
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-01-24 15:34:54 +00:00
kolaente 1971df7b84
fix(migration): use the proper authorization method for Todoist's api, fix issues with importing deleted items
continuous-integration/drone/push Build is passing Details
2023-01-24 15:45:56 +01:00
kooshi 31a1452839 fix(migration): import TickTick data by column name instead of index (#1356)
continuous-integration/drone/push Build is passing Details
Resolves: https://github.com/go-vikunja/api/issues/61
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#1356
Co-authored-by: kooshi <kolaente.dev@pat.de.com>
Co-committed-by: kooshi <kolaente.dev@pat.de.com>
2023-01-24 13:58:18 +00:00
kolaente 530bb0a63c
fix(user): make reset the user's name to empty actually work
continuous-integration/drone/push Build is failing Details
2023-01-23 18:30:01 +01:00
kolaente 7bf7a13bb9
fix(reminders): prevent duplicate reminders when updating task details
continuous-integration/drone/push Build is passing Details
2023-01-23 18:14:15 +01:00
renovate 10e6843b11 fix(deps): update module github.com/spf13/viper to v1.15.0 (#1365)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#1365
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-01-19 16:34:04 +00:00
renovate 78f43829cf fix(deps): update module src.techknowlogick.com/xgo to v1.7.0+1.19.5 (#1364)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#1364
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-01-17 21:05:33 +00:00
renovate acd31c4ff7 fix(deps): update module github.com/getsentry/sentry-go to v0.17.0 (#1361)
continuous-integration/drone/push Build is failing Details
Reviewed-on: vikunja/api#1361
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-01-12 13:25:10 +00:00
renovate 1c02114cf5 chore(deps): update klakegg/hugo docker tag to v0.107.0 (#1272)
continuous-integration/drone/push Build is passing Details
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#1272
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-01-11 19:48:22 +00:00
kolaente 82f4a5ad50
fix(export): ignore file size for export files
continuous-integration/drone/push Build is passing Details
2023-01-11 18:56:30 +01:00
kolaente 04614614fe
fix(tasks): don't set a repeating task done when moving it do the done bucket
continuous-integration/drone/push Build is failing Details
2023-01-11 18:46:24 +01:00
kolaente 608bde9806
fix(ci): tagging logic for release docker images
continuous-integration/drone/push Build is passing Details
2023-01-11 18:13:11 +01:00
renovate 568cc16797 fix(deps): update module src.techknowlogick.com/xgo to v1.6.0+1.19.5 (#1358)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#1358
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-01-10 19:21:26 +00:00
Rein 4b5e65d4c2
fix(mailer): forcessl config (#60)
continuous-integration/drone/push Build is passing Details
Co-authored-by: Rein-R3 <rein@reinsan.top>
Reviewed-At: https://github.com/go-vikunja/api/pull/60
2023-01-09 12:39:43 +01:00
renovate 3329d83363 fix(deps): update module github.com/wneessen/go-mail to v0.3.8 (#1357)
continuous-integration/drone/push Build is passing Details
Reviewed-on: vikunja/api#1357
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-01-07 12:52:06 +00:00
renovate c4e5e722e4 chore(deps): update goreleaser/nfpm docker tag to v2.23.0 (#1347)
continuous-integration/drone/push Build is failing Details
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#1347
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-01-06 11:05:47 +00:00
Dominik Pschenitschni 508a3157e2 fix(drone): add type, fix pull, remove group (#1355)
continuous-integration/drone/push Build is failing Details
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/api#1355
Co-authored-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-committed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
2023-01-06 09:46:52 +00:00
TheDubliner 321a8f7e2b
fix(docs): fix a few minor typos (#59)
continuous-integration/drone/push Build is passing Details
2023-01-06 10:33:43 +01:00
renovate dd7dcdd0cc fix(deps): update module golang.org/x/crypto to v0.5.0 (#1353)
continuous-integration/drone/push Build is failing Details
Reviewed-on: vikunja/api#1353
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-01-04 18:37:35 +00:00
renovate cb6368036c fix(deps): update module golang.org/x/term to v0.4.0 (#1352)
continuous-integration/drone/push Build encountered an error Details
Reviewed-on: vikunja/api#1352
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2023-01-04 18:23:30 +00:00
19 changed files with 284 additions and 206 deletions

View File

@ -1,5 +1,6 @@
---
kind: pipeline
type: docker
name: testing
workspace:
@ -111,7 +112,7 @@ steps:
# compiling the same magefile at the same time. It's also faster if each step does not need to compile it first.
- name: mage
image: vikunja/golang-build:latest
pull: true
pull: always
environment:
GOPROXY: 'https://goproxy.kolaente.de'
commands:
@ -122,7 +123,7 @@ steps:
- name: build
image: vikunja/golang-build:latest
pull: true
pull: always
environment:
GOPROXY: 'https://goproxy.kolaente.de'
depends_on: [ mage ]
@ -133,7 +134,7 @@ steps:
- name: lint
image: golang:1.19-alpine
pull: true
pull: always
environment:
GOPROXY: 'https://goproxy.kolaente.de'
depends_on: [ build ]
@ -147,7 +148,7 @@ steps:
- name: test-migration-prepare
image: kolaente/toolbox:latest
pull: true
pull: always
commands:
# Get the latest version
- wget https://dl.vikunja.io/api/unstable/vikunja-unstable-linux-amd64-full.zip -q -O vikunja-latest.zip
@ -155,7 +156,7 @@ steps:
- name: test-migration-sqlite
image: vikunja/golang-build:latest
pull: true
pull: always
depends_on: [ test-migration-prepare, build ]
environment:
VIKUNJA_DATABASE_TYPE: sqlite
@ -174,7 +175,7 @@ steps:
- name: test-migration-mysql
image: vikunja/golang-build:latest
pull: true
pull: always
depends_on: [ test-migration-prepare, build ]
environment:
VIKUNJA_DATABASE_TYPE: mysql
@ -193,7 +194,7 @@ steps:
- name: test-migration-psql
image: vikunja/golang-build:latest
pull: true
pull: always
depends_on: [ test-migration-prepare, build ]
environment:
VIKUNJA_DATABASE_TYPE: postgres
@ -213,7 +214,7 @@ steps:
- name: test
image: vikunja/golang-build:latest
pull: true
pull: always
environment:
GOPROXY: 'https://goproxy.kolaente.de'
commands:
@ -224,7 +225,7 @@ steps:
- name: test-sqlite
image: vikunja/golang-build:latest
pull: true
pull: always
environment:
GOPROXY: 'https://goproxy.kolaente.de'
VIKUNJA_TESTS_USE_CONFIG: 1
@ -241,7 +242,7 @@ steps:
- name: test-mysql
image: vikunja/golang-build:latest
pull: true
pull: always
environment:
GOPROXY: 'https://goproxy.kolaente.de'
VIKUNJA_TESTS_USE_CONFIG: 1
@ -258,7 +259,7 @@ steps:
- name: test-postgres
image: vikunja/golang-build:latest
pull: true
pull: always
environment:
GOPROXY: 'https://goproxy.kolaente.de'
VIKUNJA_TESTS_USE_CONFIG: 1
@ -276,7 +277,7 @@ steps:
- name: integration-test
image: vikunja/golang-build:latest
pull: true
pull: always
environment:
GOPROXY: 'https://goproxy.kolaente.de'
commands:
@ -287,7 +288,7 @@ steps:
- name: integration-test-sqlite
image: vikunja/golang-build:latest
pull: true
pull: always
environment:
GOPROXY: 'https://goproxy.kolaente.de'
VIKUNJA_TESTS_USE_CONFIG: 1
@ -304,7 +305,7 @@ steps:
- name: integration-test-mysql
image: vikunja/golang-build:latest
pull: true
pull: always
environment:
GOPROXY: 'https://goproxy.kolaente.de'
VIKUNJA_TESTS_USE_CONFIG: 1
@ -321,7 +322,7 @@ steps:
- name: integration-test-postgres
image: vikunja/golang-build:latest
pull: true
pull: always
environment:
GOPROXY: 'https://goproxy.kolaente.de'
VIKUNJA_TESTS_USE_CONFIG: 1
@ -343,6 +344,7 @@ steps:
########
kind: pipeline
type: docker
name: release
depends_on:
@ -368,7 +370,7 @@ steps:
# compiling the same magefile at the same time. It's also faster if each step does not need to compile it first.
- name: mage
image: vikunja/golang-build:latest
pull: true
pull: always
environment:
GOPROXY: 'https://goproxy.kolaente.de'
commands:
@ -378,7 +380,7 @@ steps:
- name: before-static-build
image: techknowlogick/xgo:latest
pull: true
pull: always
commands:
- export PATH=$PATH:$GOPATH/bin
- go install github.com/magefile/mage
@ -387,7 +389,7 @@ steps:
- name: static-build-windows
image: techknowlogick/xgo:latest
pull: true
pull: always
environment:
# This path does not exist. However, when we set the gopath to /go, the build fails. Not sure why.
# Leaving this here until we know how to resolve this properly.
@ -400,7 +402,7 @@ steps:
- name: static-build-linux
image: techknowlogick/xgo:latest
pull: true
pull: always
environment:
# This path does not exist. However, when we set the gopath to /go, the build fails. Not sure why.
# Leaving this here until we know how to resolve this properly.
@ -413,7 +415,7 @@ steps:
- name: static-build-darwin
image: techknowlogick/xgo:latest
pull: true
pull: always
environment:
# This path does not exist. However, when we set the gopath to /go, the build fails. Not sure why.
# Leaving this here until we know how to resolve this properly.
@ -426,7 +428,7 @@ steps:
- name: after-build-compress
image: kolaente/upx
pull: true
pull: always
depends_on:
- static-build-windows
- static-build-linux
@ -436,7 +438,7 @@ steps:
- name: after-build-static
image: techknowlogick/xgo:latest
pull: true
pull: always
depends_on:
- after-build-compress
commands:
@ -448,7 +450,7 @@ steps:
- name: sign-release
image: plugins/gpgsign:1
pull: true
pull: always
depends_on: [ after-build-static ]
settings:
key:
@ -462,7 +464,7 @@ steps:
# Push the releases to our pseudo-s3-bucket
- name: release-latest
image: plugins/s3
pull: true
pull: always
settings:
bucket: vikunja-releases
access_key:
@ -484,7 +486,7 @@ steps:
- name: release-version
image: plugins/s3
pull: true
pull: always
settings:
bucket: vikunja-releases
access_key:
@ -504,8 +506,8 @@ steps:
# Build os packages and push it to our bucket
- name: build-os-packages-unstable
image: goreleaser/nfpm:v2.22.2
pull: true
image: goreleaser/nfpm:v2.23.0
pull: always
commands:
- apk add git go
- ./mage-static release:packages
@ -520,8 +522,8 @@ steps:
depends_on: [ after-build-compress ]
- name: build-os-packages-version
image: goreleaser/nfpm:v2.22.2
pull: true
image: goreleaser/nfpm:v2.23.0
pull: always
commands:
- apk add git go
- ./mage-static release:packages
@ -536,7 +538,7 @@ steps:
# Push the os releases to our pseudo-s3-bucket
- name: release-os-latest
image: plugins/s3
pull: true
pull: always
settings:
bucket: vikunja-releases
access_key:
@ -558,7 +560,7 @@ steps:
- name: release-os-version
image: plugins/s3
pull: true
pull: always
settings:
bucket: vikunja-releases
access_key:
@ -578,6 +580,7 @@ steps:
---
kind: pipeline
type: docker
name: deploy-docs
workspace:
@ -596,8 +599,7 @@ trigger:
steps:
- name: theme
image: kolaente/toolbox
pull: true
group: build-static
pull: always
commands:
- mkdir docs/themes/vikunja -p
- cd docs/themes/vikunja
@ -605,8 +607,8 @@ steps:
- tar -xzf vikunja-theme.tar.gz
- name: build
image: klakegg/hugo:0.104.2
pull: true
image: klakegg/hugo:0.107.0
pull: always
commands:
- cd docs
- hugo
@ -614,7 +616,7 @@ steps:
- name: docker
image: plugins/docker
pull: true
pull: always
settings:
username:
from_secret: docker_username
@ -646,7 +648,7 @@ steps:
- name: docker-unstable
image: thegeeklab/drone-docker-buildx
privileged: true
pull: true
pull: always
settings:
username:
from_secret: docker_username
@ -665,24 +667,33 @@ steps:
ref:
- refs/heads/main
- name: generate-tags
image: thegeeklab/docker-autotag
environment:
DOCKER_AUTOTAG_VERSION: ${DRONE_TAG}
DOCKER_AUTOTAG_EXTRA_TAGS: latest
depends_on: [ fetch-tags ]
when:
ref:
- "refs/tags/**"
- name: docker-release
image: thegeeklab/drone-docker-buildx
privileged: true
pull: true
pull: always
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: [ fetch-tags ]
depends_on: [ generate-tags ]
when:
ref:
- "refs/tags/**"
@ -719,6 +730,6 @@ steps:
- failure
---
kind: signature
hmac: f8ce17f7158088a124039f579ba10364788d306feac3feeb51689dce440d6213
hmac: ede99d3c09466ea04c070a3cf75b454a232c42e2a46a5c5835135267d50a48e7
...

View File

@ -10,7 +10,7 @@ menu:
# Full docker example
This docker compose configuration will run Vikunja with backend and frontend with a mariadb as database.
This docker compose configuration will run Vikunja with backend and frontend with a mariadb database.
It uses an nginx container or traefik on the host to proxy backend and frontend into a single port.
For all available configuration options, see [configuration]({{< ref "config.md">}}).
@ -76,7 +76,7 @@ This example lets you host Vikunja without any reverse proxy in front of it. Thi
you need to get something up and running. If you want to host Vikunja on one single port instead of two different ones
or need tls termination, check out one of the other examples.
Not that you need to change the `VIKUNJA_API_URL` environment variable to the ip (the docker host you're running this on)
Note that you need to change the `VIKUNJA_API_URL` environment variable to the ip (the docker host you're running this on)
is reachable at. Because the browser you'll use to access the Vikunja frontend uses that url to make the requests, it
has to be able to reach that ip + port from the outside. Putting everything in a private network won't work.
@ -125,7 +125,7 @@ services:
This example assumes [traefik](https://traefik.io) version 2 installed and configured to [use docker as a configuration provider](https://docs.traefik.io/providers/docker/).
We also make a few assumtions here which you'll most likely need to adjust for your traefik setup:
We also make a few assumptions here which you'll most likely need to adjust for your traefik setup:
* Your domain is `vikunja.example.com`
* The entrypoint you want to make vikunja available from is called `https`
@ -398,7 +398,7 @@ docker main folders:
* vikunja
* mariadb
Synology has it's own GUI for managing Docker containers... But it's easier via docker compose.
Synology has its own GUI for managing Docker containers... But it's easier via docker compose.
To do that, you can
@ -407,7 +407,7 @@ To do that, you can
* without activating SSH, by using Portainer (you have to install first, check out [this tutorial](https://www.portainer.io/blog/how-to-install-portainer-on-a-synology-nas) for exmple):
1. Go to **Dashboard / Stacks** click the button **"Add Stack"**
2. Give it the name Vikunja and paste the adapted docker compose file
3. Deploy the Stack with the "Delpoy Stack" button:
3. Deploy the Stack with the "Deploy Stack" button:
![Portainer Stack deploy](/docs/synology-proxy-2.png)

19
go.mod
View File

@ -31,17 +31,18 @@ require (
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.16.0
github.com/getsentry/sentry-go v0.17.0
github.com/go-redis/redis/v8 v8.11.5
github.com/go-sql-driver/mysql v1.7.0
github.com/go-testfixtures/testfixtures/v3 v3.8.1
github.com/gocarina/gocsv v0.0.0-20221216233619-1fea7ae8d380
github.com/golang-jwt/jwt/v4 v4.4.3
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
github.com/google/uuid v1.3.0
github.com/iancoleman/strcase v0.2.0
github.com/imdario/mergo v0.3.13
github.com/jinzhu/copier v0.3.5
github.com/labstack/echo-jwt/v4 v4.0.0
github.com/labstack/echo-jwt/v4 v4.0.1
github.com/labstack/echo/v4 v4.10.0
github.com/labstack/gommon v0.4.0
github.com/lib/pq v1.10.7
@ -55,15 +56,15 @@ require (
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.14.0
github.com/spf13/viper v1.15.0
github.com/stretchr/testify v1.8.1
github.com/swaggo/swag v1.8.9
github.com/tkuchiki/go-timezone v0.2.2
github.com/ulule/limiter/v3 v3.10.0
github.com/vectordotdev/go-datemath v0.1.1-0.20211214182920-0a4ac8742b93
github.com/wneessen/go-mail v0.3.7
github.com/wneessen/go-mail v0.3.8
github.com/yuin/goldmark v1.5.3
golang.org/x/crypto v0.4.0
golang.org/x/crypto v0.5.0
golang.org/x/image v0.3.0
golang.org/x/oauth2 v0.4.0
golang.org/x/sync v0.1.0
@ -71,7 +72,7 @@ require (
golang.org/x/term v0.4.0
gopkg.in/d4l3k/messagediff.v1 v1.2.1
gopkg.in/yaml.v3 v3.0.1
src.techknowlogick.com/xgo v1.5.1-0.20220906164532-735bfdfb90d9
src.techknowlogick.com/xgo v1.7.1-0.20230117190652-94aee174ab86
src.techknowlogick.com/xormigrate v1.5.0
xorm.io/builder v0.3.12
xorm.io/xorm v1.3.2
@ -110,7 +111,7 @@ require (
github.com/json-iterator/go v1.1.12 // indirect
github.com/laurent22/ical-go v0.1.1-0.20181107184520-7e5d6ade8eef // indirect
github.com/lithammer/shortuuid/v3 v3.0.4 // indirect
github.com/magiconair/properties v1.8.6 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
@ -121,7 +122,7 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/oklog/ulid v1.3.1 // indirect
github.com/pelletier/go-toml v1.9.5 // indirect
github.com/pelletier/go-toml/v2 v2.0.5 // indirect
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.3.0 // indirect
@ -131,7 +132,7 @@ require (
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.4.1 // indirect
github.com/subosito/gotenv v1.4.2 // indirect
github.com/syndtr/goleveldb v1.0.0 // indirect
github.com/urfave/cli/v2 v2.3.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect

22
go.sum
View File

@ -209,6 +209,8 @@ 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/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=
@ -249,6 +251,8 @@ github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-testfixtures/testfixtures/v3 v3.8.1 h1:uonwvepqRvSgddcrReZQhojTlWlmOlHkYAb9ZaOMWgU=
github.com/go-testfixtures/testfixtures/v3 v3.8.1/go.mod h1:Kdu7YeMC0KRXVHdaQ91Vmx3pcjoTF63h4f1qTJDdXLA=
github.com/gocarina/gocsv v0.0.0-20221216233619-1fea7ae8d380 h1:JJq8YZiS07gFIMYZxkbbiMrXIglG3k5JPPtdvckcnfQ=
github.com/gocarina/gocsv v0.0.0-20221216233619-1fea7ae8d380/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI=
github.com/goccy/go-json v0.8.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk=
github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
@ -505,6 +509,8 @@ 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=
@ -530,6 +536,8 @@ github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
@ -624,6 +632,8 @@ github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3v
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg=
github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas=
github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
@ -720,6 +730,8 @@ 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.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=
@ -740,6 +752,8 @@ github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKs
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/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
@ -767,6 +781,8 @@ github.com/wneessen/go-mail v0.3.6 h1:hT8PMIBdcTkoiDwoUGJssPYOe1Gg1/cUcp2o9+ls63
github.com/wneessen/go-mail v0.3.6/go.mod h1:m25lkU2GYQnlVr6tdwK533/UXxo57V0kLOjaFYmub0E=
github.com/wneessen/go-mail v0.3.7 h1:loEAGLvsDZLSiE6c+keBfg0gpias/R3ocFU8Eoh3Pq4=
github.com/wneessen/go-mail v0.3.7/go.mod h1:m25lkU2GYQnlVr6tdwK533/UXxo57V0kLOjaFYmub0E=
github.com/wneessen/go-mail v0.3.8 h1:ja5D/o/RVwrtRIYFlrO7GmtcjDNeMakGQuwQRZYv0JM=
github.com/wneessen/go-mail v0.3.8/go.mod h1:m25lkU2GYQnlVr6tdwK533/UXxo57V0kLOjaFYmub0E=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@ -824,6 +840,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
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/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=
@ -1508,6 +1526,10 @@ sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
src.techknowlogick.com/xgo v1.5.1-0.20220906164532-735bfdfb90d9 h1:7u6pOCURebyXlDy0OhWdnsEBf/KgVKnExA9/w/yCT0A=
src.techknowlogick.com/xgo v1.5.1-0.20220906164532-735bfdfb90d9/go.mod h1:31CE1YKtDOrKTk9PSnjTpe6YbO6W/0LTYZ1VskL09oU=
src.techknowlogick.com/xgo v1.6.1-0.20230110184414-1fd3d5b59de3 h1:y5MVUua3J82o91nQk1gBGrJA1FJn1X6sLXar4Ec08W8=
src.techknowlogick.com/xgo v1.6.1-0.20230110184414-1fd3d5b59de3/go.mod h1:31CE1YKtDOrKTk9PSnjTpe6YbO6W/0LTYZ1VskL09oU=
src.techknowlogick.com/xgo v1.7.1-0.20230117190652-94aee174ab86 h1:VybPMHRdCLbdCttI8fMXOaGpoJGSG9+W/5cfRgr1Xjc=
src.techknowlogick.com/xgo v1.7.1-0.20230117190652-94aee174ab86/go.mod h1:31CE1YKtDOrKTk9PSnjTpe6YbO6W/0LTYZ1VskL09oU=
src.techknowlogick.com/xormigrate v1.5.0 h1:6mWTh8d0sWjMTLUgJqiLe0e0Teu+1j+RgI7ErAeOEV0=
src.techknowlogick.com/xormigrate v1.5.0/go.mod h1:QOCnBeWralVncPn9eZlM4w/rglFK8o1vYpemzPenkBM=
xorm.io/builder v0.3.7/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=

View File

@ -225,7 +225,7 @@ var userUpdateCmd = &cobra.Command{
u.AvatarProvider = userFlagAvatar
}
_, err := user.UpdateUser(s, u)
_, err := user.UpdateUser(s, u, false)
if err != nil {
_ = s.Rollback()
log.Fatalf("Error updating the user: %s", err)
@ -299,7 +299,7 @@ var userChangeEnabledCmd = &cobra.Command{
u.Status = user.StatusActive
}
}
_, err := user.UpdateUser(s, u)
_, err := user.UpdateUser(s, u, false)
if err != nil {
_ = s.Rollback()
log.Fatalf("Could not enable the user")

View File

@ -83,7 +83,7 @@ func CreateWithMime(f io.Reader, realname string, realsize uint64, a web.Auth, m
s := db.NewSession()
defer s.Close()
file, err = CreateWithMimeAndSession(s, f, realname, realsize, a, mime)
file, err = CreateWithMimeAndSession(s, f, realname, realsize, a, mime, true)
if err != nil {
_ = s.Rollback()
return
@ -91,14 +91,14 @@ func CreateWithMime(f io.Reader, realname string, realsize uint64, a web.Auth, m
return
}
func CreateWithMimeAndSession(s *xorm.Session, f io.Reader, realname string, realsize uint64, a web.Auth, mime string) (file *File, err error) {
func CreateWithMimeAndSession(s *xorm.Session, f io.Reader, realname string, realsize uint64, a web.Auth, mime string, checkFileSizeLimit bool) (file *File, err error) {
// Get and parse the configured file size
var maxSize datasize.ByteSize
err = maxSize.UnmarshalText([]byte(config.FilesMaxSize.GetString()))
if err != nil {
return nil, err
}
if realsize > maxSize.Bytes() {
if realsize > maxSize.Bytes() && checkFileSizeLimit {
return nil, ErrFileIsTooLarge{Size: realsize}
}

View File

@ -58,6 +58,10 @@ func getClient() (*mail.Client, error) {
mail.WithTimeout((config.MailerQueueTimeout.GetDuration() + 3) * time.Second), // 3s more for us to close before mail server timeout
}
if config.MailerForceSSL.GetBool() {
opts = append(opts, mail.WithSSL())
}
if config.MailerUsername.GetString() != "" && config.MailerPassword.GetString() != "" {
opts = append(opts, mail.WithSMTPAuth(authType))
}

View File

@ -97,7 +97,7 @@ func ExportUserData(s *xorm.Session, u *user.User) (err error) {
return err
}
exportFile, err := files.CreateWithMimeAndSession(s, exported, tmpFilename, uint64(stat.Size()), u, "application/zip")
exportFile, err := files.CreateWithMimeAndSession(s, exported, tmpFilename, uint64(stat.Size()), u, "application/zip", false)
if err != nil {
return err
}

View File

@ -817,13 +817,13 @@ func checkBucketLimit(s *xorm.Session, t *Task, bucket *Bucket) (err error) {
}
// Contains all the task logic to figure out what bucket to use for this task.
func setTaskBucket(s *xorm.Session, task *Task, originalTask *Task, doCheckBucketLimit bool) (err error) {
func setTaskBucket(s *xorm.Session, task *Task, originalTask *Task, doCheckBucketLimit bool) (targetBucket *Bucket, err error) {
// Make sure we have a bucket
var bucket *Bucket
if task.Done && originalTask != nil && !originalTask.Done {
bucket, err := getDoneBucketForList(s, task.ListID)
if err != nil {
return err
return nil, err
}
if bucket != nil {
task.BucketID = bucket.ID
@ -838,7 +838,7 @@ func setTaskBucket(s *xorm.Session, task *Task, originalTask *Task, doCheckBucke
if task.BucketID == 0 || (originalTask != nil && task.ListID != 0 && originalTask.ListID != task.ListID) {
bucket, err = getDefaultBucket(s, task.ListID)
if err != nil {
return err
return
}
task.BucketID = bucket.ID
}
@ -846,7 +846,7 @@ func setTaskBucket(s *xorm.Session, task *Task, originalTask *Task, doCheckBucke
if bucket == nil {
bucket, err = getBucketByID(s, task.BucketID)
if err != nil {
return err
return
}
}
@ -860,7 +860,7 @@ func setTaskBucket(s *xorm.Session, task *Task, originalTask *Task, doCheckBucke
// Only check the bucket limit if the task is being moved between buckets, allow reordering the task within a bucket
if doCheckBucketLimit {
if err := checkBucketLimit(s, task, bucket); err != nil {
return err
return nil, err
}
}
@ -868,7 +868,7 @@ func setTaskBucket(s *xorm.Session, task *Task, originalTask *Task, doCheckBucke
task.Done = true
}
return nil
return bucket, nil
}
func calculateDefaultPosition(entityID int64, position float64) float64 {
@ -937,7 +937,7 @@ func createTask(s *xorm.Session, t *Task, a web.Auth, updateAssignees bool) (err
}
// Get the default bucket and move the task there
err = setTaskBucket(s, t, nil, true)
_, err = setTaskBucket(s, t, nil, true)
if err != nil {
return
}
@ -1028,13 +1028,21 @@ func (t *Task) Update(s *xorm.Session, a web.Auth) (err error) {
ot.Reminders[i] = r.Reminder
}
// When a repeating task is marked as done, we update all deadlines and reminders and set it as undone
updateDone(&ot, t)
if err := setTaskBucket(s, t, &ot, t.BucketID != 0 && t.BucketID != ot.BucketID); err != nil {
targetBucket, err := setTaskBucket(s, t, &ot, t.BucketID != 0 && t.BucketID != ot.BucketID)
if err != nil {
return err
}
// If the task was moved into the done bucket and the task has a repeating cycle we should not update
// the bucket.
if targetBucket.IsDoneBucket && t.RepeatAfter > 0 {
t.Done = true // This will trigger the correct re-scheduling of the task (happening in updateDone later)
t.BucketID = ot.BucketID
}
// When a repeating task is marked as done, we update all deadlines and reminders and set it as undone
updateDone(&ot, t)
// Update the assignees
if err := ot.updateTaskAssignees(s, t.Assignees, a); err != nil {
return err
@ -1411,9 +1419,9 @@ func (t *Task) updateReminders(s *xorm.Session, reminders []time.Time) (err erro
}
// Resolve duplicates and sort them
reminderMap := make(map[string]time.Time, len(reminders))
reminderMap := make(map[int64]time.Time, len(reminders))
for _, reminder := range reminders {
reminderMap[reminder.UTC().String()] = reminder
reminderMap[reminder.UTC().Unix()] = reminder
}
// Loop through all reminders and add them

View File

@ -243,6 +243,33 @@ func TestTask_Update(t *testing.T) {
"bucket_id": 3,
}, false)
})
t.Run("moving a repeating task to the done bucket", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
s := db.NewSession()
defer s.Close()
task := &Task{
ID: 28,
Title: "test updated",
ListID: 1,
BucketID: 3, // Bucket 3 is the done bucket
RepeatAfter: 3600,
}
err := task.Update(s, u)
assert.NoError(t, err)
err = s.Commit()
assert.NoError(t, err)
assert.False(t, task.Done)
assert.Equal(t, int64(1), task.BucketID) // Bucket should not be updated
db.AssertExists(t, "tasks", map[string]interface{}{
"id": 28,
"done": false,
"title": "test updated",
"list_id": 1,
"bucket_id": 1,
}, false)
})
t.Run("default bucket when moving a task between lists", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
s := db.NewSession()
@ -308,8 +335,9 @@ func TestTask_Update(t *testing.T) {
defer s.Close()
task := &Task{
ID: 28,
Done: true,
ID: 28,
Done: true,
RepeatAfter: 3600,
}
err := task.Update(s, u)
assert.NoError(t, err)

View File

@ -267,7 +267,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
}

View File

@ -56,12 +56,22 @@ func DownloadFileWithHeaders(url string, headers http.Header) (buf *bytes.Buffer
// DoPost makes a form encoded post request
func DoPost(url string, form url.Values) (resp *http.Response, err error) {
return DoPostWithHeaders(url, form, map[string]string{})
}
// DoPostWithHeaders does an api request and allows to pass in arbitrary headers
func DoPostWithHeaders(url string, form url.Values, headers map[string]string) (resp *http.Response, err error) {
req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, url, strings.NewReader(form.Encode()))
if err != nil {
return
}
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
for key, value := range headers {
req.Header.Add(key, value)
}
hc := http.Client{}
return hc.Do(req)
}

View File

@ -27,10 +27,11 @@ import (
"time"
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/modules/migration"
"code.vikunja.io/api/pkg/user"
"github.com/gocarina/gocsv"
)
const timeISO = "2006-01-02T15:04:05-0700"
@ -39,23 +40,39 @@ type Migrator struct {
}
type tickTickTask struct {
FolderName string
ListName string
Title string
Tags []string
Content string
IsChecklist bool
StartDate time.Time
DueDate time.Time
Reminder time.Duration
Repeat string
Priority int
Status string
CreatedTime time.Time
CompletedTime time.Time
Order float64
TaskID int64
ParentID int64
FolderName string `csv:"Folder Name"`
ListName string `csv:"List Name"`
Title string `csv:"Title"`
TagsList string `csv:"Tags"`
Tags []string `csv:"-"`
Content string `csv:"Content"`
IsChecklistString string `csv:"Is Check list"`
IsChecklist bool `csv:"-"`
StartDate tickTickTime `csv:"Start Date"`
DueDate tickTickTime `csv:"Due Date"`
ReminderDuration string `csv:"Reminder"`
Reminder time.Duration `csv:"-"`
Repeat string `csv:"Repeat"`
Priority int `csv:"Priority"`
Status string `csv:"Status"`
CreatedTime tickTickTime `csv:"Created Time"`
CompletedTime tickTickTime `csv:"Completed Time"`
Order float64 `csv:"Order"`
TaskID int64 `csv:"taskId"`
ParentID int64 `csv:"parentId"`
}
type tickTickTime struct {
time.Time
}
func (date *tickTickTime) UnmarshalCSV(csv string) (err error) {
date.Time = time.Time{}
if csv == "" {
return nil
}
date.Time, err = time.Parse(timeISO, csv)
return err
}
// Copied from https://stackoverflow.com/a/57617885
@ -119,19 +136,22 @@ func convertTickTickToVikunja(tasks []*tickTickTask) (result []*models.Namespace
ID: t.TaskID,
Title: t.Title,
Description: t.Content,
StartDate: t.StartDate,
EndDate: t.DueDate,
DueDate: t.DueDate,
Reminders: []time.Time{
t.DueDate.Add(t.Reminder * -1),
},
Done: t.Status == "1",
DoneAt: t.CompletedTime,
Position: t.Order,
Labels: labels,
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,
},
}
if !t.DueDate.IsZero() && t.Reminder > 0 {
task.Task.Reminders = []time.Time{
t.DueDate.Add(t.Reminder * -1),
}
}
if t.ParentID != 0 {
task.RelatedTasks = map[models.RelationKind][]*models.Task{
models.RelationKindParenttask: {{ID: t.ParentID}},
@ -165,6 +185,22 @@ func (m *Migrator) Name() string {
return "ticktick"
}
func newLineSkipDecoder(r io.Reader, linesToSkip int) gocsv.SimpleDecoder {
reader := csv.NewReader(r)
// reader.FieldsPerRecord = -1
for i := 0; i < linesToSkip; i++ {
_, err := reader.Read()
if err != nil {
if errors.Is(err, io.EOF) {
break
}
log.Debugf("[TickTick Migration] CSV parse error: %s", err)
}
}
reader.FieldsPerRecord = 0
return gocsv.NewSimpleDecoderFromCSVReader(reader)
}
// Migrate takes a ticktick export, parses it and imports everything in it into Vikunja.
// @Summary Import all lists, tasks etc. from a TickTick backup export
// @Description Imports all projects, tasks, notes, reminders, subtasks and files from a TickTick backup export into Vikunja.
@ -178,85 +214,26 @@ func (m *Migrator) Name() string {
// @Router /migration/ticktick/migrate [post]
func (m *Migrator) Migrate(user *user.User, file io.ReaderAt, size int64) error {
fr := io.NewSectionReader(file, 0, size)
r := csv.NewReader(fr)
//r := csv.NewReader(fr)
allTasks := []*tickTickTask{}
line := 0
for {
decode := newLineSkipDecoder(fr, 3)
err := gocsv.UnmarshalDecoder(decode, &allTasks)
if err != nil {
return err
}
record, err := r.Read()
if err != nil {
if errors.Is(err, io.EOF) {
break
}
log.Debugf("[TickTick Migration] CSV parse error: %s", err)
for _, task := range allTasks {
if task.IsChecklistString == "Y" {
task.IsChecklist = true
}
line++
if line <= 4 {
continue
reminder := parseDuration(task.ReminderDuration)
if reminder > 0 {
task.Reminder = reminder
}
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)
task.Tags = strings.Split(task.TagsList, ", ")
}
vikunjaTasks := convertTickTickToVikunja(allTasks)

View File

@ -26,12 +26,15 @@ import (
)
func TestConvertTicktickTasksToVikunja(t *testing.T) {
time1, err := time.Parse(time.RFC3339Nano, "2022-11-18T03:00:00.4770000Z")
t1, err := time.Parse(time.RFC3339Nano, "2022-11-18T03:00:00.4770000Z")
require.NoError(t, err)
time2, err := time.Parse(time.RFC3339Nano, "2022-12-18T03:00:00.4770000Z")
time1 := tickTickTime{Time: t1}
t2, err := time.Parse(time.RFC3339Nano, "2022-12-18T03:00:00.4770000Z")
require.NoError(t, err)
time3, err := time.Parse(time.RFC3339Nano, "2022-12-10T03:00:00.4770000Z")
time2 := tickTickTime{Time: t2}
t3, err := time.Parse(time.RFC3339Nano, "2022-12-10T03:00:00.4770000Z")
require.NoError(t, err)
time3 := tickTickTime{Time: t3}
duration, err := time.ParseDuration("24h")
require.NoError(t, err)
@ -91,9 +94,9 @@ func TestConvertTicktickTasksToVikunja(t *testing.T) {
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[0].Title, tickTickTasks[0].Title)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[0].Description, tickTickTasks[0].Content)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[0].StartDate, tickTickTasks[0].StartDate)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[0].EndDate, tickTickTasks[0].DueDate)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[0].DueDate, tickTickTasks[0].DueDate)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[0].StartDate, tickTickTasks[0].StartDate.Time)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[0].EndDate, tickTickTasks[0].DueDate.Time)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[0].DueDate, tickTickTasks[0].DueDate.Time)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[0].Labels, []*models.Label{
{Title: "label1"},
{Title: "label2"},
@ -105,7 +108,7 @@ func TestConvertTicktickTasksToVikunja(t *testing.T) {
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[1].Title, tickTickTasks[1].Title)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[1].Position, tickTickTasks[1].Order)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[1].Done, true)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[1].DoneAt, tickTickTasks[1].CompletedTime)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[1].DoneAt, tickTickTasks[1].CompletedTime.Time)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[1].RelatedTasks, models.RelatedTaskMap{
models.RelationKindParenttask: []*models.Task{
{
@ -116,9 +119,9 @@ func TestConvertTicktickTasksToVikunja(t *testing.T) {
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[2].Title, tickTickTasks[2].Title)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[2].Description, tickTickTasks[2].Content)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[2].StartDate, tickTickTasks[2].StartDate)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[2].EndDate, tickTickTasks[2].DueDate)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[2].DueDate, tickTickTasks[2].DueDate)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[2].StartDate, tickTickTasks[2].StartDate.Time)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[2].EndDate, tickTickTasks[2].DueDate.Time)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[2].DueDate, tickTickTasks[2].DueDate.Time)
assert.Equal(t, vikunjaTasks[0].Lists[0].Tasks[2].Labels, []*models.Label{
{Title: "label1"},
{Title: "label2"},

View File

@ -20,6 +20,7 @@ import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/url"
"sort"
"strconv"
@ -76,7 +77,6 @@ type dueDate struct {
type item struct {
ID string `json:"id"`
LegacyID string `json:"legacy_id"`
UserID string `json:"user_id"`
ProjectID string `json:"project_id"`
Content string `json:"content"`
@ -310,6 +310,12 @@ func convertTodoistToVikunja(sync *sync, doneItems map[string]*doneItem) (fullVi
}
for _, i := range sync.Items {
if i == nil {
// This should never happen
continue
}
task := &models.TaskWithComments{
Task: models.Task{
Title: i.Content,
@ -524,11 +530,14 @@ func (m *Migration) Migrate(u *user.User) (err error) {
// Get everything with the sync api
form := url.Values{
"token": []string{token},
"sync_token": []string{"*"},
"resource_types": []string{"[\"all\"]"},
}
resp, err := migration.DoPost("https://api.todoist.com/sync/v9/sync", form)
bearerHeader := map[string]string{
"Authorization": "Bearer " + token,
}
resp, err := migration.DoPostWithHeaders("https://api.todoist.com/sync/v9/sync", form, bearerHeader)
if err != nil {
return
}
@ -547,7 +556,7 @@ func (m *Migration) Migrate(u *user.User) (err error) {
doneItems := make(map[string]*doneItem)
for {
resp, err = migration.DoPost("https://api.todoist.com/sync/v9/completed/get_all?limit=200&offset="+strconv.Itoa(offset), form)
resp, err = migration.DoPostWithHeaders("https://api.todoist.com/sync/v9/completed/get_all?limit=200&offset="+strconv.Itoa(offset), form, bearerHeader)
if err != nil {
return
}
@ -571,21 +580,26 @@ func (m *Migration) Migrate(u *user.User) (err error) {
doneItems[i.TaskID] = i
// need to get done item data
resp, err = migration.DoPost("https://api.todoist.com/sync/v9/items/get", url.Values{
"token": []string{token},
resp, err = migration.DoPostWithHeaders("https://api.todoist.com/sync/v9/items/get", url.Values{
"item_id": []string{i.TaskID},
})
}, bearerHeader)
if err != nil {
return
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusNotFound {
// Done items of deleted projects may show up here but since the project is already deleted
// we can't show them individually and the api returns a 404.
continue
}
doneI := &itemWrapper{}
err = json.NewDecoder(resp.Body).Decode(doneI)
if err != nil {
return
}
log.Debugf("[Todoist Migration] Retrieved full task data for done task %s", i.TaskID)
log.Debugf("[Todoist Migration] Retrieved full task data for done task %s", i.ID)
syncResponse.Items = append(syncResponse.Items, doneI.Item)
}
@ -600,7 +614,7 @@ func (m *Migration) Migrate(u *user.User) (err error) {
log.Debugf("[Todoist Migration] Getting archived projects for user %d", u.ID)
// Get all archived projects
resp, err = migration.DoPost("https://api.todoist.com/sync/v9/projects/get_archived", form)
resp, err = migration.DoPostWithHeaders("https://api.todoist.com/sync/v9/projects/get_archived", form, bearerHeader)
if err != nil {
return
}
@ -616,9 +630,9 @@ func (m *Migration) Migrate(u *user.User) (err error) {
log.Debugf("[Todoist Migration] Got %d archived projects for user %d", len(archivedProjects), u.ID)
log.Debugf("[Todoist Migration] Getting data for archived projects for user %d", u.ID)
// Project data is not included in the regular sync for archived projects so we need to get all of those by hand
// Project data is not included in the regular sync for archived projects, so we need to get all of those by hand
for _, p := range archivedProjects {
resp, err = migration.DoPost("https://api.todoist.com/sync/v9/projects/get_data?project_id="+p.ID, form)
resp, err = migration.DoPostWithHeaders("https://api.todoist.com/sync/v9/projects/get_data?project_id="+p.ID, form, bearerHeader)
if err != nil {
return
}

View File

@ -205,7 +205,7 @@ func UploadAvatar(c echo.Context) (err error) {
u.AvatarFileID = f.ID
u.AvatarProvider = "upload"
if _, err := user.UpdateUser(s, u); err != nil {
if _, err := user.UpdateUser(s, u, false); err != nil {
_ = s.Rollback()
return handler.HandleHTTPError(err, c)
}

View File

@ -133,7 +133,7 @@ func ChangeUserAvatarProvider(c echo.Context) error {
user.AvatarProvider = uap.AvatarProvider
_, err = user2.UpdateUser(s, user)
_, err = user2.UpdateUser(s, user, false)
if err != nil {
_ = s.Rollback()
return handler.HandleHTTPError(err, c)
@ -199,7 +199,7 @@ func UpdateGeneralUserSettings(c echo.Context) error {
user.Timezone = us.Timezone
user.OverdueTasksRemindersTime = us.OverdueTasksRemindersTime
_, err = user2.UpdateUser(s, user)
_, err = user2.UpdateUser(s, user, true)
if err != nil {
_ = s.Rollback()
return handler.HandleHTTPError(err, c)

View File

@ -419,7 +419,7 @@ func GetUserFromClaims(claims jwt.MapClaims) (user *User, err error) {
}
// UpdateUser updates a user
func UpdateUser(s *xorm.Session, user *User) (updatedUser *User, err error) {
func UpdateUser(s *xorm.Session, user *User, forceOverride bool) (updatedUser *User, err error) {
// Check if it exists
theUser, err := GetUserWithEmail(s, &User{ID: user.ID})
@ -442,7 +442,7 @@ func UpdateUser(s *xorm.Session, user *User) (updatedUser *User, err error) {
}
// Check if we have a name
if user.Name == "" {
if user.Name == "" && !forceOverride {
user.Name = theUser.Name
}

View File

@ -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))
})