Compare commits
114 Commits
6236171c95
...
b693983789
Author | SHA1 | Date |
---|---|---|
renovate | b693983789 | |
renovate | b0622732a1 | |
renovate | 072b82412b | |
renovate | ea539cc284 | |
Joseph Kavanagh | 36bf3d216a | |
renovate | efd0970971 | |
renovate | 677ca03489 | |
kolaente | 1fa74cba64 | |
kolaente | 0b7762590f | |
kolaente | c3e0e6405a | |
kolaente | 57e5d10eee | |
kolaente | 88a2cede19 | |
kolaente | 093d0c65ca | |
renovate | da2d5e41c7 | |
kolaente | 9bf32aae99 | |
kolaente | 2aea1691cf | |
kolaente | 4829c89940 | |
kolaente | cf05de19b3 | |
renovate | 2ae1da473e | |
renovate | 62361d8ad9 | |
renovate | 98316a04b2 | |
renovate | ae192c1291 | |
renovate | e3210a11df | |
renovate | 5763aca428 | |
renovate | 609497d08c | |
renovate | b92c370d6d | |
renovate | 0b707369d0 | |
renovate | e64f06fabc | |
renovate | bcfaa78c1a | |
renovate | 29eb0765f3 | |
renovate | 473692cf7a | |
renovate | b9819de157 | |
renovate | b3e520427b | |
renovate | cb74337738 | |
renovate | a03d119a06 | |
kolaente | 2683ef23d5 | |
kolaente | 516c812043 | |
kolaente | 9eca971c93 | |
kolaente | cc612d505f | |
renovate | 53703dd0c4 | |
renovate | 068ad56d2b | |
renovate | a20cbc99fd | |
renovate | eb47161c64 | |
renovate | b194715690 | |
kolaente | 8f55af07c9 | |
renovate | 9869fd694a | |
renovate | ff7270838e | |
renovate | 4bdf147c89 | |
renovate | 1443f8aa2d | |
renovate | af306e5ad3 | |
renovate | 9fbda1d503 | |
renovate | 445703e155 | |
renovate | 4c05912633 | |
renovate | 8dfbb2c344 | |
renovate | 84fe135f69 | |
renovate | 0a6e6dcac1 | |
jayden-c | dcb52c00f1 | |
kolaente | 50b65a517d | |
kolaente | d7e47a28d4 | |
kolaente | c2b6119434 | |
renovate | f46c1c5d13 | |
renovate | 66589ca37d | |
renovate | 45a8c63015 | |
renovate | fce8b89b45 | |
stephen-hill | 82a3330412 | |
ajgon | fb9fa27488 | |
kolaente | cd6cd840a2 | |
kolaente | c2f33868c8 | |
kolaente | 73a99ebd92 | |
kolaente | 68998e90a4 | |
renovate | c67456d420 | |
renovate | 2ab27c72f2 | |
renovate | df5c5e3be2 | |
renovate | 1f067930a2 | |
renovate | 0ca1560bf1 | |
kolaente | 4de8ec56a6 | |
kolaente | ae8db176db | |
renovate | 56efbdc297 | |
renovate | 4764a8e4f0 | |
kolaente | dbd6f36da6 | |
kolaente | 4255bc3a94 | |
kolaente | bb086eb9f8 | |
renovate | c4da0b424b | |
renovate | 037457e865 | |
renovate | e51f125931 | |
renovate | 57d447b165 | |
renovate | 4136255c67 | |
renovate | 0f2013f915 | |
renovate | 0ca31aed28 | |
renovate | 351fe1f624 | |
renovate | abfdae0012 | |
renovate | 5f5936c972 | |
renovate | 111ac92619 | |
kolaente | bf00ea4939 | |
kolaente | 465f6d90ab | |
renovate | 07bda3eae3 | |
kolaente | 03d818fd9f | |
kolaente | f019ae42bb | |
kolaente | 9000f2c3cd | |
kolaente | cc1bb3083f | |
kolaente | be47459c14 | |
renovate | 086f005e3d | |
kolaente | 24bb7e98fa | |
kolaente | 496c38de8e | |
konrad | 90146aea5b | |
renovate | fc51a3e76f | |
renovate | 4154247d95 | |
renovate | e3956527d4 | |
kolaente | 15e5a9069b | |
kolaente | c9d0f519ee | |
renovate | 72f7c3333a | |
kolaente | d7b74500c3 | |
kolaente | bd24743640 | |
renovate | e2dd9d85b5 |
56
.drone.yml
56
.drone.yml
|
@ -62,7 +62,7 @@ services:
|
|||
- name: tmp-mysql-migration
|
||||
path: /var/lib/mysql
|
||||
- name: test-postgres-unit
|
||||
image: postgres:13
|
||||
image: postgres:14
|
||||
environment:
|
||||
POSTGRES_PASSWORD: vikunjatest
|
||||
POSTGRES_DB: vikunjatest
|
||||
|
@ -72,7 +72,7 @@ services:
|
|||
commands:
|
||||
- docker-entrypoint.sh -c fsync=off -c full_page_writes=off # turns of wal
|
||||
- name: test-postgres-integration
|
||||
image: postgres:13
|
||||
image: postgres:14
|
||||
environment:
|
||||
POSTGRES_PASSWORD: vikunjatest
|
||||
POSTGRES_DB: vikunjatest
|
||||
|
@ -82,7 +82,7 @@ services:
|
|||
commands:
|
||||
- docker-entrypoint.sh -c fsync=off -c full_page_writes=off # turns of wal
|
||||
- name: test-postgres-migration
|
||||
image: postgres:13
|
||||
image: postgres:14
|
||||
environment:
|
||||
POSTGRES_PASSWORD: vikunjatest
|
||||
POSTGRES_DB: vikunjatest
|
||||
|
@ -459,7 +459,7 @@ steps:
|
|||
|
||||
# Push the releases to our pseudo-s3-bucket
|
||||
- name: release-latest
|
||||
image: plugins/s3:1
|
||||
image: plugins/s3
|
||||
pull: true
|
||||
settings:
|
||||
bucket: vikunja-releases
|
||||
|
@ -481,7 +481,7 @@ steps:
|
|||
depends_on: [ sign-release ]
|
||||
|
||||
- name: release-version
|
||||
image: plugins/s3:1
|
||||
image: plugins/s3
|
||||
pull: true
|
||||
settings:
|
||||
bucket: vikunja-releases
|
||||
|
@ -514,7 +514,7 @@ steps:
|
|||
|
||||
# Push the os releases to our pseudo-s3-bucket
|
||||
- name: release-os-latest
|
||||
image: plugins/s3:1
|
||||
image: plugins/s3
|
||||
pull: true
|
||||
settings:
|
||||
bucket: vikunja-releases
|
||||
|
@ -536,7 +536,7 @@ steps:
|
|||
depends_on: [ build-os-packages ]
|
||||
|
||||
- name: release-os-version
|
||||
image: plugins/s3:1
|
||||
image: plugins/s3
|
||||
pull: true
|
||||
settings:
|
||||
bucket: vikunja-releases
|
||||
|
@ -576,7 +576,7 @@ steps:
|
|||
|
||||
# Push the releases to our pseudo-s3-bucket
|
||||
- name: release-deb
|
||||
image: plugins/s3:1
|
||||
image: plugins/s3
|
||||
pull: true
|
||||
settings:
|
||||
bucket: vikunja-releases
|
||||
|
@ -662,7 +662,7 @@ steps:
|
|||
image: docker:git
|
||||
commands:
|
||||
- git fetch --tags
|
||||
- name: docker-arm-latest
|
||||
- name: docker-arm-unstable
|
||||
image: plugins/docker:linux-arm
|
||||
pull: true
|
||||
settings:
|
||||
|
@ -671,7 +671,7 @@ steps:
|
|||
password:
|
||||
from_secret: docker_password
|
||||
repo: vikunja/api
|
||||
tags: latest-linux-arm
|
||||
tags: unstable-linux-arm
|
||||
depends_on: [ fetch-tags ]
|
||||
when:
|
||||
ref:
|
||||
|
@ -693,7 +693,7 @@ steps:
|
|||
ref:
|
||||
- "refs/tags/**"
|
||||
|
||||
- name: docker-arm64-latest
|
||||
- name: docker-arm64-unstable
|
||||
image: plugins/docker:linux-arm64
|
||||
pull: true
|
||||
settings:
|
||||
|
@ -702,7 +702,7 @@ steps:
|
|||
password:
|
||||
from_secret: docker_password
|
||||
repo: vikunja/api
|
||||
tags: latest-linux-arm64
|
||||
tags: unstable-linux-arm64
|
||||
depends_on: [ fetch-tags ]
|
||||
when:
|
||||
ref:
|
||||
|
@ -746,7 +746,8 @@ steps:
|
|||
image: docker:git
|
||||
commands:
|
||||
- git fetch --tags
|
||||
- name: docker-latest
|
||||
|
||||
- name: docker-unstable
|
||||
image: plugins/docker:linux-amd64
|
||||
pull: true
|
||||
settings:
|
||||
|
@ -755,7 +756,7 @@ steps:
|
|||
password:
|
||||
from_secret: docker_password
|
||||
repo: vikunja/api
|
||||
tags: latest-linux-amd64
|
||||
tags: unstable-linux-amd64
|
||||
depends_on: [ fetch-tags ]
|
||||
when:
|
||||
ref:
|
||||
|
@ -792,13 +793,13 @@ depends_on:
|
|||
- docker-arm-release
|
||||
|
||||
steps:
|
||||
- name: manifest-latest
|
||||
- name: manifest-unstable
|
||||
pull: always
|
||||
image: plugins/manifest
|
||||
settings:
|
||||
tags: latest
|
||||
tags: unstable
|
||||
ignore_missing: true
|
||||
spec: docker-manifest-latest.tmpl
|
||||
spec: docker-manifest-unstable.tmpl
|
||||
password:
|
||||
from_secret: docker_password
|
||||
username:
|
||||
|
@ -807,7 +808,7 @@ steps:
|
|||
ref:
|
||||
- refs/heads/main
|
||||
|
||||
- name: manifest
|
||||
- name: manifest-release
|
||||
pull: always
|
||||
image: plugins/manifest
|
||||
settings:
|
||||
|
@ -822,6 +823,23 @@ steps:
|
|||
ref:
|
||||
- "refs/tags/**"
|
||||
|
||||
- name: manifest-release-latest
|
||||
pull: always
|
||||
image: plugins/manifest
|
||||
depends_on:
|
||||
- clone
|
||||
settings:
|
||||
tags: latest
|
||||
ignore_missing: true
|
||||
spec: docker-manifest.tmpl
|
||||
password:
|
||||
from_secret: docker_password
|
||||
username:
|
||||
from_secret: docker_username
|
||||
when:
|
||||
ref:
|
||||
- "refs/tags/**"
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
|
@ -856,6 +874,6 @@ steps:
|
|||
- failure
|
||||
---
|
||||
kind: signature
|
||||
hmac: 2cee8cac22aae8b4a25ffa28c47d9defc6ce227e0d262b57677f9adbf8badc3e
|
||||
hmac: 4b36351eaff966fb0e28775059a8a4e091eca7a3ef4c52df2a050fd6c59d409e
|
||||
|
||||
...
|
||||
|
|
195
CHANGELOG.md
195
CHANGELOG.md
|
@ -2,11 +2,184 @@
|
|||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres
|
||||
to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
All releases can be found on https://code.vikunja.io/api/releases.
|
||||
|
||||
## [0.18.1] - 2021-09-08
|
||||
|
||||
### Fixed
|
||||
|
||||
* Docs: Add another third-party tutorial link
|
||||
* Don't try to export items which do not have a parent
|
||||
* fix(deps): update golang.org/x/sys commit hash to 6f6e228 (#970)
|
||||
* fix(deps): update golang.org/x/sys commit hash to c212e73 (#971)
|
||||
* Fix exporting tasks from archived lists
|
||||
* Fix lint
|
||||
* Fix tasks not exported
|
||||
* Fix tmp export file created in the wrong path
|
||||
|
||||
## [0.18.0] - 2021-09-05
|
||||
|
||||
### Added
|
||||
|
||||
* Add default list setting (#875)
|
||||
* Add menu link to Vikunja Cloud in docs
|
||||
* Add more logging and better error messages for openid authentication + clarify docs
|
||||
* Add more logging for test data api endpoint
|
||||
* Add searching for tasks by index
|
||||
* Add setting for first day of the week
|
||||
* Add support of Unix socket (#912)
|
||||
* Add truncate parameter to test fixtures setup
|
||||
* Notify the user after three failed login attempts
|
||||
* Reorder tasks, lists and kanban buckets (#923)
|
||||
* Send a notification on failed TOTP
|
||||
* Task mentions (#926)
|
||||
* Try to get more information about the user when authenticating with openid
|
||||
* User account deletion (#937)
|
||||
* User Data Export and import (#967)
|
||||
|
||||
### Changed
|
||||
|
||||
* Allow running migration 20210711173657 multiple times to fix issues when it didn't completely run through previously
|
||||
* Better logging for errors while importing a bunch of tasks
|
||||
* Change task title to TEXT instead of varchar(250) to allow for longer task titles
|
||||
* Disable the user account after 10 failed password attempts
|
||||
* Docs: Add a note about default password
|
||||
* Docs: Add another youtube tutorial
|
||||
* Docs: Add ios to the list of not working caldav clients
|
||||
* Docs: Add k8s-at-home Helm Chart for Vikunja
|
||||
* Docs: Add other installation resources
|
||||
* Docs: Add translation docs
|
||||
* Docs: Fix rewrite rules in apache example configs
|
||||
* Docs: Translation now happening at crowdin
|
||||
* Docs: Update translation guidelines
|
||||
* Don't fail when removing the last bucket in migration from other services
|
||||
* Don't notify the user who created the team
|
||||
* Don't use the mariadb root user in docker-compose examples
|
||||
* Ensure case insensitive search on postgres (#927)
|
||||
* Increase test timeout
|
||||
* Only filter out failing openid providers if multiple are configured and one of them failed
|
||||
* Only send an email about failed totp after three failed attempts
|
||||
* Rearrange setting frontend url in config
|
||||
* Refactor user email confirmation + password reset handling (#919)
|
||||
* Rename and sign drone config
|
||||
* Replace jwt-go with github.com/golang-jwt/jwt
|
||||
* Reset failed totp attempts when logging in successfully
|
||||
* Save user tokens as text and not varchar
|
||||
* Save user tokens as varchar(450) and not text to fix mysql indexing issues
|
||||
* Set todoist migration redirect url to the frontend url by default
|
||||
* Show config full paths and env variables with config options
|
||||
* Switch the :latest docker image tag to contain the latest release instead of the latest unstable
|
||||
* Tune test db server settings to speed up tests (#939)
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fix authentication callback
|
||||
* Fix duplicating empty lists
|
||||
* Fix error handling when deleting an attachment file
|
||||
* Fix error when searching for a namespace returned no results
|
||||
* Fix error when searching for a namespace with subscribers
|
||||
* Fix goimports
|
||||
* Fix importing archived projects and done items from todoist
|
||||
* Fix jwt middleware
|
||||
* Fix lint
|
||||
* Fix mapping task priorities from Vikunja to calDAV
|
||||
* Fix moving the done bucket around
|
||||
* Fix old references to master in docs
|
||||
* Fix panic on invalid smtp config
|
||||
* Fix parsing openid config when using a json config file
|
||||
* Fix saving pointer values to memory keyvalue
|
||||
* Fix saving reminders of repeating tasks
|
||||
* Fix setting a saved filter as favorite
|
||||
* Fix setting task favorite status of related tasks
|
||||
* Fix setting up keyvalue storage in tests
|
||||
* Fix swagger docs for create requests
|
||||
* Fix task relations not getting properly cleaned up when deleting them
|
||||
* Fix tests & lint
|
||||
* Make sure a bucket exists or use the default bucket when importing tasks
|
||||
* Make sure all associated entities of a task are deleted when the task is deleted
|
||||
* Make sure list / task favorites are set per user, not per entity (#915)
|
||||
* Make sure the configured frontend url always has a / at the end
|
||||
* Refactor & fix storing struct-values in redis keyvalue
|
||||
* Todoist migration: don't panic if no reminder was found for task
|
||||
|
||||
### Dependency updates
|
||||
|
||||
* fix(deps): update golang.org/x/sys commit hash to 63515b4 (#959)
|
||||
* fix(deps): update golang.org/x/sys commit hash to 97244b9 (#965)
|
||||
* fix(deps): update golang.org/x/sys commit hash to f475640 (#962)
|
||||
* fix(deps): update golang.org/x/sys commit hash to f4d4317 (#961)
|
||||
* fix(deps): update module github.com/lib/pq to v1.10.3 (#963)
|
||||
* Update alpine Docker tag to v3.13 (#884)
|
||||
* Update alpine Docker tag to v3.14 (#889)
|
||||
* Update golang.org/x/crypto commit hash to 0a44fdf (#944)
|
||||
* Update golang.org/x/crypto commit hash to 0ba0e8f (#943)
|
||||
* Update golang.org/x/crypto commit hash to 32db794 (#949)
|
||||
* Update golang.org/x/crypto commit hash to 5ff15b2 (#891)
|
||||
* Update golang.org/x/crypto commit hash to a769d52 (#916)
|
||||
* Update golang.org/x/image commit hash to 775e3b0 (#880)
|
||||
* Update golang.org/x/image commit hash to a66eb64 (#900)
|
||||
* Update golang.org/x/image commit hash to e6eecd4 (#893)
|
||||
* Update golang.org/x/net commit hash to 37e1c6af
|
||||
* Update golang.org/x/oauth2 commit hash to 14747e6 (#894)
|
||||
* Update golang.org/x/oauth2 commit hash to 2bc19b1 (#955)
|
||||
* Update golang.org/x/oauth2 commit hash to 6f1e639 (#931)
|
||||
* Update golang.org/x/oauth2 commit hash to 7df4dd6 (#952)
|
||||
* Update golang.org/x/oauth2 commit hash to a41e5a7 (#902)
|
||||
* Update golang.org/x/oauth2 commit hash to a8dc77f (#896)
|
||||
* Update golang.org/x/oauth2 commit hash to bce0382 (#895)
|
||||
* Update golang.org/x/oauth2 commit hash to d040287 (#888)
|
||||
* Update golang.org/x/oauth2 commit hash to f6687ab (#862)
|
||||
* Update golang.org/x/oauth2 commit hash to faf39c7 (#935)
|
||||
* Update golang.org/x/sys commit hash to 00dd8d7 (#953)
|
||||
* Update golang.org/x/sys commit hash to 15123e1 (#946)
|
||||
* Update golang.org/x/sys commit hash to 1e6c022 (#947)
|
||||
* Update golang.org/x/sys commit hash to 30e4713 (#945)
|
||||
* Update golang.org/x/sys commit hash to 41cdb87 (#956)
|
||||
* Update golang.org/x/sys commit hash to 7d9622a (#948)
|
||||
* Update golang.org/x/sys commit hash to bfb29a6 (#951)
|
||||
* Update golang.org/x/sys commit hash to d867a43 (#934)
|
||||
* Update golang.org/x/sys commit hash to e5e7981 (#933)
|
||||
* Update golang.org/x/sys commit hash to f52c844 (#954)
|
||||
* Update golang.org/x/term commit hash to 6886f2d (#887)
|
||||
* Update module getsentry/sentry-go to v0.11.0 (#869)
|
||||
* Update module github.com/coreos/go-oidc to v3 (#885)
|
||||
* Update module github.com/gabriel-vasile/mimetype to v1.3.1 (#904)
|
||||
* Update module github.com/golang-jwt/jwt to v3.2.2 (#928)
|
||||
* Update module github.com/golang-jwt/jwt to v4 (#930)
|
||||
* Update module github.com/go-redis/redis/v8 to v8.11.0 (#903)
|
||||
* Update module github.com/go-redis/redis/v8 to v8.11.1 (#925)
|
||||
* Update module github.com/go-redis/redis/v8 to v8.11.2 (#932)
|
||||
* Update module github.com/go-redis/redis/v8 to v8.11.3 (#942)
|
||||
* Update module github.com/iancoleman/strcase to v0.2.0 (#918)
|
||||
* Update module github.com/labstack/echo/v4 to v4.4.0 (#917)
|
||||
* Update module github.com/labstack/echo/v4 to v4.5.0 (#929)
|
||||
* Update module github.com/mattn/go-sqlite3 to v1.14.8 (#921)
|
||||
* Update module github.com/spf13/cobra to v1.2.0 (#905)
|
||||
* Update module github.com/spf13/cobra to v1.2.1 (#906)
|
||||
* Update module github.com/spf13/viper to v1.8.0 (#890)
|
||||
* Update module github.com/spf13/viper to v1.8.1 (#899)
|
||||
* Update module github.com/swaggo/swag to v1.7.1 (#936)
|
||||
* Update module github.com/yuin/goldmark to v1.3.8 (#892)
|
||||
* Update module github.com/yuin/goldmark to v1.3.9 (#901)
|
||||
* Update module github.com/yuin/goldmark to v1.4.0 (#908)
|
||||
* Update module go-redis/redis/v8 to v8.10.0 (#882)
|
||||
* Update module go-redis/redis/v8 to v8.7.1 (#807)
|
||||
* Update module go-testfixtures/testfixtures/v3 to v3.6.1 (#868)
|
||||
* Update module lib/pq to v1.10.2 (#865)
|
||||
* Update module prometheus/client_golang to v1.11.0 (#879)
|
||||
* Update module yuin/goldmark to v1.3.6 (#863)
|
||||
* Update module yuin/goldmark to v1.3.7 (#867)
|
||||
* Update monachus/hugo Docker tag to v0.75.1 (#940)
|
||||
|
||||
## [0.17.1] - 2021-06-09
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fix parsing openid config when using a json config file
|
||||
|
||||
## [0.17.0] - 2021-05-14
|
||||
|
||||
### Added
|
||||
|
@ -446,7 +619,7 @@ All releases can be found on https://code.vikunja.io/api/releases.
|
|||
|
||||
## [0.14.0] - 2020-07-01
|
||||
|
||||
### Added
|
||||
### Added
|
||||
|
||||
* Add ability to run the docker container with configurable user and group ids
|
||||
* Add better errors if the sqlite db file is not writable
|
||||
|
@ -660,7 +833,7 @@ All releases can be found on https://code.vikunja.io/api/releases.
|
|||
|
||||
## [0.12] - 2020-04-04
|
||||
|
||||
#### Added
|
||||
#### Added
|
||||
|
||||
* Add support for archiving lists and namespaces (#152)
|
||||
* Colors for lists and namespaces (#155)
|
||||
|
@ -684,7 +857,7 @@ All releases can be found on https://code.vikunja.io/api/releases.
|
|||
|
||||
## [0.11] - 2020-03-01
|
||||
|
||||
### Added
|
||||
### Added
|
||||
|
||||
* Add config options for cors handling (#124)
|
||||
* Add config options for task attachments (#125)
|
||||
|
@ -752,7 +925,7 @@ All releases can be found on https://code.vikunja.io/api/releases.
|
|||
|
||||
## [0.9] - 2019-11-24
|
||||
|
||||
### Added
|
||||
### Added
|
||||
|
||||
* Task Attachments (#104)
|
||||
* Task Relations (#103)
|
||||
|
@ -765,7 +938,8 @@ All releases can be found on https://code.vikunja.io/api/releases.
|
|||
### Fixed
|
||||
|
||||
* Fix default logging settings (#107)
|
||||
* Fixed a bug where adding assignees or reminders via an update would re-create them and not respect already inserted ones, leaving a lot of garbage
|
||||
* Fixed a bug where adding assignees or reminders via an update would re-create them and not respect already inserted
|
||||
ones, leaving a lot of garbage
|
||||
* Fixed a bug where deleting an attachment would cause a nil panic
|
||||
* Fixed building docs theme
|
||||
* Fixed error when setting max file size on 32-Bit systems
|
||||
|
@ -778,7 +952,7 @@ All releases can be found on https://code.vikunja.io/api/releases.
|
|||
* Fixed removing reminders
|
||||
* Small link share fixes (#96)
|
||||
|
||||
### Changed
|
||||
### Changed
|
||||
|
||||
* Improve pagination (#105)
|
||||
* Moved `teams_{namespace|list}_*` to `{namespace|list}_teams_*` for better consistency (#101)
|
||||
|
@ -899,7 +1073,8 @@ All releases can be found on https://code.vikunja.io/api/releases.
|
|||
|
||||
* Updated libraries
|
||||
* Updated drone to version 1
|
||||
* Releases are now signed with our pgp key (more info about this on [the download page](https://vikunja.io/en/download/)).
|
||||
* Releases are now signed with our pgp key (more info about this
|
||||
on [the download page](https://vikunja.io/en/download/)).
|
||||
|
||||
## [0.5] - 2018-12-02
|
||||
|
||||
|
@ -929,7 +1104,7 @@ All releases can be found on https://code.vikunja.io/api/releases.
|
|||
|
||||
## [0.3] - 2018-11-02
|
||||
|
||||
### Added
|
||||
### Added
|
||||
|
||||
* Password reset
|
||||
* Email verification when registering
|
||||
|
|
|
@ -23,7 +23,7 @@ RUN if [ -n "${VIKUNJA_VERSION}" ]; then git checkout "${VIKUNJA_VERSION}"; fi \
|
|||
# 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.14
|
||||
FROM alpine:3.15
|
||||
LABEL maintainer="maintainers@vikunja.io"
|
||||
|
||||
WORKDIR /app/vikunja/
|
||||
|
|
|
@ -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.17.0-brightgreen.svg)](https://dl.vikunja.io)
|
||||
[![Download](https://img.shields.io/badge/download-v0.18.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)
|
||||
|
|
|
@ -3,6 +3,9 @@ service:
|
|||
# Default is a random token which will be generated at each startup of vikunja.
|
||||
# (This means all already issued tokens will be invalid once you restart vikunja)
|
||||
JWTSecret: "<jwt-secret>"
|
||||
# The duration of the issed JWT tokens in seconds.
|
||||
# The default is 259200 seconds (3 Days).
|
||||
jwtttl: 259200
|
||||
# The interface on which to run the webserver
|
||||
interface: ":3456"
|
||||
# Path to Unix socket. If set, it will be created and used instead of tcp
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
image: vikunja/api:latest
|
||||
image: vikunja/api:unstable
|
||||
manifests:
|
||||
-
|
||||
image: vikunja/api:latest-linux-amd64
|
||||
image: vikunja/api:unstable-linux-amd64
|
||||
platform:
|
||||
architecture: amd64
|
||||
os: linux
|
||||
-
|
||||
image: vikunja/api:latest-linux-arm64
|
||||
image: vikunja/api:unstable-linux-arm64
|
||||
platform:
|
||||
architecture: arm64
|
||||
os: linux
|
||||
-
|
||||
image: vikunja/api:latest-linux-arm
|
||||
image: vikunja/api:unstable-linux-arm
|
||||
platform:
|
||||
architecture: arm
|
||||
os: linux
|
|
@ -31,10 +31,10 @@ menu:
|
|||
url: https://vikunja.io/en/
|
||||
weight: 10
|
||||
- name: Features
|
||||
url: https://vikunja.io/en/features
|
||||
url: https://vikunja.io/features
|
||||
weight: 20
|
||||
- name: Download
|
||||
url: https://vikunja.io/en/download
|
||||
url: https://vikunja.io/download
|
||||
weight: 30
|
||||
- name: Docs
|
||||
url: https://vikunja.io/docs
|
||||
|
|
|
@ -10,8 +10,8 @@ menu:
|
|||
|
||||
# Database
|
||||
|
||||
Vikunja uses [xorm](http://xorm.io/) as an abstraction layer to handle the database connection.
|
||||
Please refer to [their](http://xorm.io/docs/) documentation on how to exactly use it.
|
||||
Vikunja uses [xorm](https://xorm.io/) as an abstraction layer to handle the database connection.
|
||||
Please refer to [their](https://xorm.io/docs/) documentation on how to exactly use it.
|
||||
|
||||
{{< table_of_contents >}}
|
||||
|
||||
|
@ -24,7 +24,7 @@ In other packages, use the `db.NewSession()` method to get a new database sessio
|
|||
|
||||
To add a new table to the database, create the struct and [add a migration for it]({{< ref "db-migrations.md" >}}).
|
||||
|
||||
To learn more about how to configure your struct to create "good" tables, refer to [the xorm documentaion](http://xorm.io/docs/).
|
||||
To learn more about how to configure your struct to create "good" tables, refer to [the xorm documentaion](https://xorm.io/docs/).
|
||||
|
||||
In most cases you will also need to implement the `TableName() string` method on the new struct to make sure the table
|
||||
name matches the rest of the tables - plural.
|
||||
|
|
|
@ -12,11 +12,45 @@ menu:
|
|||
|
||||
# Development
|
||||
|
||||
We use go modules to manage third-party libraries for Vikunja, so you'll need at least go `1.11` to use these.
|
||||
{{< table_of_contents >}}
|
||||
|
||||
## General
|
||||
|
||||
To contribute to Vikunja, fork the project and work on the main branch.
|
||||
Once you feel like your changes are ready, open a PR in the respective repo.
|
||||
A maintainer will take a look and give you feedback. Once everyone is happy, the PR gets merged and released.
|
||||
|
||||
If you plan to do a bigger change, it is better to open an issue for discussion first.
|
||||
|
||||
## API
|
||||
|
||||
The code for the api is located at [code.vikunja.io/api](https://code.vikunja.io/api).
|
||||
|
||||
We use go modules to manage third-party libraries for Vikunja, so you'll need at least go `1.11` to use these.
|
||||
|
||||
A lot of developing tasks are automated using a Magefile, so make sure to [take a look at it]({{< ref "mage.md">}}).
|
||||
|
||||
Make sure to check the other doc articles for specific development tasks like [testing]({{< ref "test.md">}}),
|
||||
Make sure to check the other doc articles for specific development tasks like [testing]({{< ref "test.md">}}),
|
||||
[database migrations]({{< ref "db-migrations.md" >}}) and the [project structure]({{< ref "structure.md" >}}).
|
||||
|
||||
## Frontend requirements
|
||||
|
||||
The code for the frontend is located at [code.vikunja.io/frontend](https://code.vikunja.io/frontend).
|
||||
|
||||
You need to have yarn v1 and nodejs in version 16 installed.
|
||||
|
||||
## Git flow
|
||||
|
||||
The `main` branch is the latest and bleeding edge branch with all changes. Unstable releases are automatically
|
||||
created from this branch.
|
||||
|
||||
A release gets tagged from the main branch with the version name as tag name.
|
||||
|
||||
Backports and point-releases should go to a `release/version` branch, based on the tag they are building on top of.
|
||||
|
||||
## Conventional commits
|
||||
|
||||
We're using [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) because they greatly simplify
|
||||
generating release notes.
|
||||
|
||||
It is not required to use them when creating a PR, but appreciated.
|
||||
|
|
|
@ -14,7 +14,17 @@ It is possible to migrate data from other to-do services to Vikunja.
|
|||
To make this easier, we have put together a few helpers which are documented on this page.
|
||||
|
||||
In general, each migrator implements a migrator interface which is then called from a client.
|
||||
The interface makes it possible to use helper methods which handle http an focus only on the implementation of the migrator itself.
|
||||
The interface makes it possible to use helper methods which handle http and focus only on the implementation of the migrator itself.
|
||||
|
||||
There are two ways of migrating data from another service:
|
||||
1. Through the auth-based flow where the user gives you access to their data at the third-party service through an
|
||||
oauth flow. You can then call the service's api on behalf of your user to get all the data.
|
||||
The Todoist, Trello and Microsoft To-Do Migrators use this pattern.
|
||||
2. A file migration where the user uploads a file obtained from some third-party service. In your migrator, you need
|
||||
to parse the file and create the lists, tasks etc.
|
||||
The Vikunja File Import uses this pattern.
|
||||
|
||||
To differentiate the two, there are two different interfaces you must implement.
|
||||
|
||||
{{< table_of_contents >}}
|
||||
|
||||
|
@ -23,13 +33,16 @@ The interface makes it possible to use helper methods which handle http an focus
|
|||
All migrator implementations live in their own package in `pkg/modules/migration/<name-of-the-service>`.
|
||||
When creating a new migrator, you should place all related code inside that module.
|
||||
|
||||
## Migrator interface
|
||||
## Migrator Interface
|
||||
|
||||
The migrator interface is defined as follows:
|
||||
|
||||
```go
|
||||
// Migrator is the basic migrator interface which is shared among all migrators
|
||||
type Migrator interface {
|
||||
// Name holds the name of the migration.
|
||||
// This is used to show the name to users and to keep track of users who already migrated.
|
||||
Name() string
|
||||
// Migrate is the interface used to migrate a user's tasks from another platform to vikunja.
|
||||
// The user object is the user who's tasks will be migrated.
|
||||
Migrate(user *models.User) error
|
||||
|
@ -37,9 +50,20 @@ type Migrator interface {
|
|||
// The use case for this are Oauth flows, where the server token should remain hidden and not
|
||||
// known to the frontend.
|
||||
AuthURL() string
|
||||
}
|
||||
```
|
||||
|
||||
## File Migrator Interface
|
||||
|
||||
```go
|
||||
// FileMigrator handles importing Vikunja data from a file. The implementation of it determines the format.
|
||||
type FileMigrator interface {
|
||||
// Name holds the name of the migration.
|
||||
// This is used to show the name to users and to keep track of users who already migrated.
|
||||
Name() string
|
||||
// Migrate is the interface used to migrate a user's tasks, list and other things from a file to vikunja.
|
||||
// The user object is the user who's tasks will be migrated.
|
||||
Migrate(user *user.User, file io.ReaderAt, size int64) error
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -54,15 +78,26 @@ authUrl, Status and Migrate methods.
|
|||
```go
|
||||
// This is an example for the Wunderlist migrator
|
||||
if config.MigrationWunderlistEnable.GetBool() {
|
||||
wunderlistMigrationHandler := &migrationHandler.MigrationWeb{
|
||||
wunderlistMigrationHandler := &migrationHandler.MigrationWeb{
|
||||
MigrationStruct: func() migration.Migrator {
|
||||
return &wunderlist.Migration{}
|
||||
},
|
||||
}
|
||||
wunderlistMigrationHandler.RegisterRoutes(m)
|
||||
wunderlistMigrationHandler.RegisterRoutes(m)
|
||||
}
|
||||
```
|
||||
|
||||
And for the file migrator:
|
||||
|
||||
```go
|
||||
vikunjaFileMigrationHandler := &migrationHandler.FileMigratorWeb{
|
||||
MigrationStruct: func() migration.FileMigrator {
|
||||
return &vikunja_file.FileMigrator{}
|
||||
},
|
||||
}
|
||||
vikunjaFileMigrationHandler.RegisterRoutes(m)
|
||||
```
|
||||
|
||||
You should also document the routes with [swagger annotations]({{< ref "swagger-docs.md" >}}).
|
||||
|
||||
## Insertion helper method
|
||||
|
@ -70,7 +105,8 @@ You should also document the routes with [swagger annotations]({{< ref "swagger-
|
|||
There is a method available in the `migration` package which takes a fully nested Vikunja structure and creates it with all relations.
|
||||
This means you start by adding a namespace, then add lists inside of that namespace, then tasks in the lists and so on.
|
||||
|
||||
The root structure must be present as `[]*models.NamespaceWithLists`.
|
||||
The root structure must be present as `[]*models.NamespaceWithListsAndTasks`. It allows to represent all of Vikunja's
|
||||
hierachie as a single data structure.
|
||||
|
||||
Then call the method like so:
|
||||
|
||||
|
@ -85,14 +121,16 @@ err = migration.InsertFromStructure(fullVikunjaHierachie, user)
|
|||
|
||||
## Configuration
|
||||
|
||||
You should add at least an option to enable or disable the migration.
|
||||
If your migrator is an oauth-based one, you should add at least an option to enable or disable it.
|
||||
Chances are, you'll need some more options for things like client ID and secret
|
||||
(if the other service uses oAuth as an authentication flow).
|
||||
|
||||
The easiest way to implement an on/off switch is to check whether your migration service is enabled or not when
|
||||
registering the routes, and then simply don't registering the routes in the case it is disabled.
|
||||
registering the routes, and then simply don't registering the routes in case it is disabled.
|
||||
|
||||
File based migrators can always be enabled.
|
||||
|
||||
### Making the migrator public in `/info`
|
||||
|
||||
You should make your migrator available in the `/info` endpoint so that frontends can display options to enable them or not.
|
||||
To do this, add an entry to `pkg/routes/api/v1/info.go`.
|
||||
To do this, add an entry to the `AvailableMigrators` field in `pkg/routes/api/v1/info.go`.
|
||||
|
|
|
@ -25,29 +25,52 @@ Genauer definiert:
|
|||
* “falsch” anstatt “nicht korrekt/inkorrekt”
|
||||
* “Wende dich an …” anstatt “kontaktiere …”
|
||||
* In derselben Zeit übersetzen (sonst wird aus dem englischen “is“ das deutsche “war”)
|
||||
* Richtige Anführungszeichen verwenden. Also „“ statt '' oder ' oder ` oder ´
|
||||
* Richtige Anführungszeichen verwenden. Also `„“` statt `''` oder `'` oder ` oder ´
|
||||
* `„` für beginnende Anführungszeichen, `“` für schließende Anführungszeichen
|
||||
|
||||
Es gelten Artikel und Worttrennungen aus dem [Duden](https://duden.de).
|
||||
|
||||
## Gendern
|
||||
## Formulierungen
|
||||
|
||||
Wo möglich, sollte eine geschlechtsneutrale Anrede verwendet werden.
|
||||
Falls diese sehr umständlich würden (siehe oben „Amtsdeutsch-Umschreibungen“), soll mit *Doppelpunkt* gegendert werden.
|
||||
|
||||
Beispiel: „Benutzer:in“
|
||||
* `Account` statt `Konto`.
|
||||
* `TOTP` immer als ein Wort und Groß.
|
||||
* `CalDAV` immer so.
|
||||
* `löschen` oder `entfernen` je nach Kontext. Wenn etwas *gelöscht* wird, existiert das gelöschte Objekt und danach
|
||||
nicht mehr und hat evtl. andere Objekte mitgelöscht (z.B. eine Aufgabe). Wird etwas *entfernt*, bezieht sich das
|
||||
meistens auf die Beziehung zu einem anderen Objekt. Das entfernte Objekt existiert danach immernoch, z.B. beim
|
||||
Entfernen eine:r Nutzer:in aus einem Team.
|
||||
* Analog zu `löschen` oder `entfernen` gilt ähnliches für `hinzufügen` oder `erstellen`. Eine Aufgabe wird *erstellt*,
|
||||
aber ein:e Nutzer:in nur zu einem Team *hinzugefügt*.
|
||||
* `Anmeldename` anstatt `Benutzer:innenname`
|
||||
|
||||
## Formulierungen in Modals und Buttons
|
||||
|
||||
Es sollten die gleichen Formulierungen auf Buttons und Modals verwendet werden.
|
||||
|
||||
Beispiel: Wenn der Button mit `löschen` beschriftet ist, sollte im Modal die Frage lauten `Willst du das wirklich löschen?` und nicht `Willst du das wirklich entfernen?`.
|
||||
Gleiches gilt für Erfolgs/Fehlermeldungen nach der Aktion.
|
||||
Beispiel: Wenn der Button mit `löschen` beschriftet ist, sollte im Modal die Frage
|
||||
lauten `Willst du das wirklich löschen?` und nicht `Willst du das wirklich entfernen?`. Gleiches gilt für
|
||||
Erfolgs/Fehlermeldungen nach der Aktion.
|
||||
|
||||
## Gendern
|
||||
|
||||
Wo möglich, sollte eine geschlechtsneutrale Anrede verwendet werden. Falls diese sehr umständlich würden (siehe oben
|
||||
„Amtsdeutsch-Umschreibungen“), soll mit *Doppelpunkt* gegendert werden.
|
||||
|
||||
Beispiel: „Benutzer:in“
|
||||
|
||||
## Trennungen
|
||||
|
||||
* E-Mail-Adresse (siehe Duden)
|
||||
|
||||
## Wörter und Ausdrücke
|
||||
|
||||
| Englisches Original | Verwendung in deutscher Übersetzung |
|
||||
| ------------------- | -------------------- |
|
||||
| Bucket | Spalte |
|
||||
| Namespace | Namespace |
|
||||
| Link Share | Linkfreigabe |
|
||||
| Username | Anmeldename |
|
||||
|
||||
## Weiterführende Links
|
||||
|
||||
* http://docs.translatehouse.org/projects/localization-guide/en/latest/guide/translation_guidelines_german.html
|
||||
* https://docs.translatehouse.org/projects/localization-guide/en/latest/guide/translation_guidelines_german.html
|
||||
|
|
|
@ -48,7 +48,7 @@ Strings in other languages will be synced through weblate and should not be adde
|
|||
|
||||
## Requesting a new language
|
||||
|
||||
If you want to start translating Vikunja in a language not yet available in Vikunja, please request the language through the weblate interface.
|
||||
If you have issues with this or need a discussion before doing so, pleace [contact us](https://vikunja.io/contact/) or [start a discussion in the forum](https://community.vikunja.io).
|
||||
If you want to start translating Vikunja in a language not yet available in Vikunja, please request the language through the crowdin interface.
|
||||
If you have issues with this or need a discussion before doing so, please [contact us](https://vikunja.io/contact/) or [start a discussion in the forum](https://community.vikunja.io).
|
||||
|
||||
Once at least 50% of all translation strings are translated and approved, they will be added and distributed with the Vikunja frontend for users to select and use Vikunja with them.
|
||||
|
|
|
@ -10,12 +10,17 @@ menu:
|
|||
|
||||
# What to backup
|
||||
|
||||
Vikunja does not store any data outside of the database.
|
||||
So, all you need to backup are the contents of that database and maybe the config file.
|
||||
There are two parts you need to back up: The database and attachment files.
|
||||
|
||||
{{< table_of_contents >}}
|
||||
|
||||
## MySQL
|
||||
## Files
|
||||
|
||||
To back up attachments and other files, it is enough to copy them [from the attachments folder]({{< ref "config.md" >}}#basepath) to some other place.
|
||||
|
||||
## Database
|
||||
|
||||
### MySQL
|
||||
|
||||
To create a backup from mysql use the `mysqldump` command:
|
||||
|
||||
|
@ -31,7 +36,7 @@ To restore it, simply pipe it back into the `mysql` command:
|
|||
mysql -u <user> -p -h <db-host> <database> < vkunja-backup.sql
|
||||
{{< /highlight >}}
|
||||
|
||||
## PostgreSQL
|
||||
### PostgreSQL
|
||||
|
||||
To create a backup from PostgreSQL use the `pg_dump` command:
|
||||
|
||||
|
@ -49,6 +54,6 @@ psql -U <user> -h <db-host> <database> < vikunja-backup.sql
|
|||
|
||||
For more information, please visit the [relevant PostgreSQL documentation](https://www.postgresql.org/docs/12/backup-dump.html).
|
||||
|
||||
## SQLite
|
||||
### SQLite
|
||||
|
||||
To backup sqllite databases, it is enough to copy the database elsewhere.
|
||||
To back up sqllite databases, it is enough to copy the [database file]({{< ref "config.md" >}}#path) to somwhere else.
|
||||
|
|
|
@ -79,6 +79,18 @@ Full path: `service.JWTSecret`
|
|||
Environment path: `VIKUNJA_SERVICE_JWT_SECRET`
|
||||
|
||||
|
||||
### jwtttl
|
||||
|
||||
The duration of the issed JWT tokens in seconds.
|
||||
The default is 259200 seconds (3 Days).
|
||||
|
||||
Default: `259200`
|
||||
|
||||
Full path: `service.jwtttl`
|
||||
|
||||
Environment path: `VIKUNJA_SERVICE_JWTTTL`
|
||||
|
||||
|
||||
### interface
|
||||
|
||||
The interface on which to run the webserver
|
||||
|
|
|
@ -25,6 +25,9 @@ All examples on this page already reflect this and do not require additional wor
|
|||
|
||||
## Redis
|
||||
|
||||
While Vikunja has support to use redis as a caching backend, you'll probably not need it unless you're using Vikunja
|
||||
with more than a handful of users.
|
||||
|
||||
To use redis, you'll need to add this to the config examples below:
|
||||
|
||||
{{< highlight yaml >}}
|
||||
|
@ -44,6 +47,78 @@ services:
|
|||
image: redis
|
||||
{{< /highlight >}}
|
||||
|
||||
## PostgreSQL
|
||||
|
||||
Vikunja supports postgres, mysql and sqlite as a database backend. The examples on this page use mysql with a mariadb container.
|
||||
To use postgres as a database backend, change the `db` section of the examples to this:
|
||||
|
||||
{{< highlight yaml >}}
|
||||
db:
|
||||
image: postgres:13
|
||||
environment:
|
||||
POSTGRES_PASSWORD: secret
|
||||
POSTGRES_USER: vikunja
|
||||
volumes:
|
||||
- ./db:/var/lib/postgresql/data
|
||||
restart: unless-stopped
|
||||
{{< /highlight >}}
|
||||
|
||||
You'll also need to change the `VIKUNJA_DATABASE_TYPE` to `postgres` on the api container declaration.
|
||||
|
||||
<div class="notification is-warning">
|
||||
<b>NOTE:</b> The mariadb container can sometimes take a while to initialize, especially on the first run.
|
||||
During this time, the api container will fail to start at all. It will automatically restart every few seconds.
|
||||
</div>
|
||||
|
||||
## Example without any proxy
|
||||
|
||||
This example lets you host Vikunja without any reverse proxy in front of it. This is the absolute minimum configuration
|
||||
you need to get something up and running. If you want to host Vikunja on one single port instead of two different ones
|
||||
or need tls termination, check out one of the other examples.
|
||||
|
||||
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.
|
||||
|
||||
{{< highlight yaml >}}
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
db:
|
||||
image: mariadb:10
|
||||
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: supersecret
|
||||
MYSQL_USER: vikunja
|
||||
MYSQL_PASSWORD: secret
|
||||
MYSQL_DATABASE: vikunja
|
||||
volumes:
|
||||
- ./db:/var/lib/mysql
|
||||
restart: unless-stopped
|
||||
api:
|
||||
image: vikunja/api
|
||||
environment:
|
||||
VIKUNJA_DATABASE_HOST: db
|
||||
VIKUNJA_DATABASE_PASSWORD: secret
|
||||
VIKUNJA_DATABASE_TYPE: mysql
|
||||
VIKUNJA_DATABASE_USER: vikunja
|
||||
VIKUNJA_DATABASE_DATABASE: vikunja
|
||||
ports:
|
||||
- 3456:3456
|
||||
volumes:
|
||||
- ./files:/app/vikunja/files
|
||||
depends_on:
|
||||
- db
|
||||
restart: unless-stopped
|
||||
frontend:
|
||||
image: vikunja/frontend
|
||||
ports:
|
||||
- 80:80
|
||||
environment:
|
||||
VIKUNJA_API_URL: http://<your-ip-here>:3456/api/v1
|
||||
restart: unless-stopped
|
||||
{{< /highlight >}}
|
||||
|
||||
## Example with traefik 2
|
||||
|
||||
This example assumes [traefik](https://traefik.io) version 2 installed and configured to [use docker as a configuration provider](https://docs.traefik.io/providers/docker/).
|
||||
|
@ -203,9 +278,9 @@ services:
|
|||
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: supersecret
|
||||
MYSQL_USER: vikunja
|
||||
MYSQL_PASSWORD: secret
|
||||
MYSQL_DATABASE: vikunja
|
||||
MYSQL_USER: vikunja
|
||||
MYSQL_PASSWORD: secret
|
||||
MYSQL_DATABASE: vikunja
|
||||
volumes:
|
||||
- ./db:/var/lib/mysql
|
||||
restart: unless-stopped
|
||||
|
@ -260,10 +335,10 @@ services:
|
|||
image: mariadb:10
|
||||
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: supersecret
|
||||
MYSQL_USER: vikunja
|
||||
MYSQL_PASSWORD: secret
|
||||
MYSQL_DATABASE: vikunja
|
||||
MYSQL_ROOT_PASSWORD: supersecret
|
||||
MYSQL_USER: vikunja
|
||||
MYSQL_PASSWORD: secret
|
||||
MYSQL_DATABASE: vikunja
|
||||
volumes:
|
||||
- ./db:/var/lib/mysql
|
||||
restart: unless-stopped
|
||||
|
|
|
@ -44,3 +44,11 @@ This document provides an overview and instructions for the different methods.
|
|||
|
||||
A third-party Helm Chart is available from the k8s-at-home project [here](https://github.com/k8s-at-home/charts/tree/master/charts/stable/vikunja).
|
||||
|
||||
## Other installation resources
|
||||
|
||||
* [Docker Compose is MUCH Easier Than you Think - Let's Install Vikunja](https://www.youtube.com/watch?v=fGlz2PkXjuo) (Youtube)
|
||||
* [Setup Vikunja using Docker Compose - Homelab Wiki](https://thehomelab.wiki/books/docker/page/setup-vikunja-using-docker-compose)
|
||||
* [A Closer look at Vikunja - Email Notifications - Enable or Disable Registrations - Allow Attachments](https://www.youtube.com/watch?v=47wj9pRT6Gw) (Youtube)
|
||||
* [Install Vikunja in Docker for self-hosted Task Tracking](https://smarthomepursuits.com/install-vikunja-in-docker-for-self-hosted-task-tracking/)
|
||||
* [Self-Hosted To-Do List with Vikunja in Docker](https://www.youtube.com/watch?v=DqyqDWpEvKI) (Youtube)
|
||||
* [Vikunja self-hosted (step by step)](https://nguyenminhhung.com/vikunja-self-hosted-step-by-step/)
|
||||
|
|
|
@ -11,7 +11,7 @@ menu:
|
|||
# API Documentation
|
||||
|
||||
You can find the api docs under `http://vikunja.tld/api/v1/docs` of your instance.
|
||||
A public instance is available on [try.vikunja.io](http://try.vikunja.io/api/v1/docs).
|
||||
A public instance is available on [try.vikunja.io](https://try.vikunja.io/api/v1/docs).
|
||||
|
||||
These docs are autgenerated from annotations in the code with swagger.
|
||||
|
||||
|
|
37
go.mod
37
go.mod
|
@ -21,33 +21,31 @@ require (
|
|||
gitea.com/xorm/xorm-redis-cache v0.2.0
|
||||
github.com/ThreeDotsLabs/watermill v1.1.1
|
||||
github.com/adlio/trello v1.9.0
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751
|
||||
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef
|
||||
github.com/beevik/etree v1.1.0 // indirect
|
||||
github.com/c2h5oh/datasize v0.0.0-20200825124411-48ed595a09d2
|
||||
github.com/coreos/go-oidc/v3 v3.0.0
|
||||
github.com/coreos/go-oidc/v3 v3.1.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.3.1
|
||||
github.com/gabriel-vasile/mimetype v1.4.0
|
||||
github.com/getsentry/sentry-go v0.11.0
|
||||
github.com/go-errors/errors v1.1.1 // indirect
|
||||
github.com/go-redis/redis/v8 v8.11.3
|
||||
github.com/go-redis/redis/v8 v8.11.4
|
||||
github.com/go-sql-driver/mysql v1.6.0
|
||||
github.com/go-testfixtures/testfixtures/v3 v3.6.1
|
||||
github.com/golang-jwt/jwt/v4 v4.0.0
|
||||
github.com/golang-jwt/jwt/v4 v4.1.0
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/iancoleman/strcase v0.2.0
|
||||
github.com/imdario/mergo v0.3.12
|
||||
github.com/labstack/echo/v4 v4.5.0
|
||||
github.com/labstack/gommon v0.3.0
|
||||
github.com/labstack/echo/v4 v4.6.1
|
||||
github.com/labstack/gommon v0.3.1
|
||||
github.com/laurent22/ical-go v0.1.1-0.20181107184520-7e5d6ade8eef
|
||||
github.com/lib/pq v1.10.2
|
||||
github.com/lib/pq v1.10.4
|
||||
github.com/magefile/mage v1.11.0
|
||||
github.com/mattn/go-isatty v0.0.13 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.8
|
||||
github.com/mattn/go-sqlite3 v1.14.9
|
||||
github.com/olekukonko/tablewriter v0.0.5
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
|
||||
github.com/pquerna/otp v1.3.0
|
||||
|
@ -56,18 +54,17 @@ require (
|
|||
github.com/samedi/caldav-go v3.0.0+incompatible
|
||||
github.com/spf13/afero v1.6.0
|
||||
github.com/spf13/cobra v1.2.1
|
||||
github.com/spf13/viper v1.8.1
|
||||
github.com/spf13/viper v1.9.0
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/swaggo/swag v1.7.1
|
||||
github.com/ulule/limiter/v3 v3.8.0
|
||||
github.com/yuin/goldmark v1.4.0
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5
|
||||
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d
|
||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f
|
||||
github.com/swaggo/swag v1.7.4
|
||||
github.com/ulule/limiter/v3 v3.9.0
|
||||
github.com/yuin/goldmark v1.4.4
|
||||
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871
|
||||
golang.org/x/image v0.0.0-20211028202545-6944b10bf410
|
||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
golang.org/x/sys v0.0.0-20210820121016-41cdb8703e55
|
||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b
|
||||
golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
gopkg.in/d4l3k/messagediff.v1 v1.2.1
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||
|
|
298
go.sum
298
go.sum
|
@ -19,6 +19,11 @@ cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmW
|
|||
cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg=
|
||||
cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8=
|
||||
cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0=
|
||||
cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY=
|
||||
cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM=
|
||||
cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=
|
||||
cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=
|
||||
cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
|
||||
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
|
||||
|
@ -28,6 +33,7 @@ cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM7
|
|||
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/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
|
||||
cloud.google.com/go/firestore v1.6.0/go.mod h1:afJwI0vaXwAG54kI7A//lP/lSPDkQORQuMkv56TxEPU=
|
||||
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
|
||||
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
|
||||
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
|
||||
|
@ -54,6 +60,7 @@ github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMd
|
|||
github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY=
|
||||
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
|
||||
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
|
||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||
|
@ -66,12 +73,12 @@ github.com/ThreeDotsLabs/watermill v1.1.1 h1:+9NXqWQvplzxBru2CIInvVOZeKUnM+Nysg4
|
|||
github.com/ThreeDotsLabs/watermill v1.1.1/go.mod h1:Qd1xNFxolCAHCzcMrm6RnjW0manbvN+DJVWc1MWRFlI=
|
||||
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
||||
github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
||||
github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
|
||||
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||
|
@ -79,6 +86,7 @@ github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hC
|
|||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
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/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
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=
|
||||
github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
|
||||
|
@ -97,8 +105,11 @@ github.com/c2h5oh/datasize v0.0.0-20200825124411-48ed595a09d2/go.mod h1:S/7n9cop
|
|||
github.com/cenkalti/backoff/v3 v3.0.0 h1:ske+9nBpD9qZsTBoF41nW5L+AIuFBKMeze18XQ3eG1c=
|
||||
github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
||||
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
|
@ -106,13 +117,14 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
|
|||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
|
||||
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
|
||||
github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||
github.com/coreos/go-oidc/v3 v3.0.0 h1:/mAA0XMgYJw2Uqm7WKGCsKnjitE/+A0FFbOmiRJm7LQ=
|
||||
github.com/coreos/go-oidc/v3 v3.0.0/go.mod h1:rEJ/idjfUyfkBit1eI1fvyr+64/g9dcKpAm8MJMesvo=
|
||||
github.com/coreos/go-oidc/v3 v3.1.0 h1:6avEvcdvTa1qYsOZ6I5PRkSYHzpTNWgKYmaJfaYbrRw=
|
||||
github.com/coreos/go-oidc/v3 v3.1.0/go.mod h1:rEJ/idjfUyfkBit1eI1fvyr+64/g9dcKpAm8MJMesvo=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
|
||||
|
@ -158,16 +170,19 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
|
|||
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw=
|
||||
github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/gabriel-vasile/mimetype v1.3.1 h1:qevA6c2MtE1RorlScnixeG0VA1H4xrXyhyX3oWBynNQ=
|
||||
github.com/gabriel-vasile/mimetype v1.3.1/go.mod h1:fA8fi6KUiG7MgQQ+mEWotXoEOvmxRtOJlERCzSmRvr8=
|
||||
github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
|
||||
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
|
||||
github.com/gabriel-vasile/mimetype v1.4.0 h1:Cn9dkdYsMIu56tGho+fqzh7XmvY2YyGU0FnbhiOsEro=
|
||||
github.com/gabriel-vasile/mimetype v1.4.0/go.mod h1:fA8fi6KUiG7MgQQ+mEWotXoEOvmxRtOJlERCzSmRvr8=
|
||||
github.com/garyburd/redigo v1.6.0 h1:0VruCpn7yAIIu7pWVClQC8wxCJEcG3nyzpMSHKi1PQc=
|
||||
github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
|
||||
github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
|
||||
|
@ -179,6 +194,7 @@ github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NB
|
|||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
|
||||
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
|
||||
github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
|
||||
github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
|
||||
github.com/go-chi/chi v4.0.2+incompatible h1:maB6vn6FqCxrpz4FqWdh4+lwpyZIQS7YEAUcHlgXVRs=
|
||||
github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||
|
@ -209,9 +225,10 @@ github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvSc
|
|||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
|
||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||
github.com/go-redis/redis/v8 v8.4.2/go.mod h1:A1tbYoHSa1fXwN+//ljcCYYJeLmVrwL9hbQN45Jdy0M=
|
||||
github.com/go-redis/redis/v8 v8.11.3 h1:GCjoYp8c+yQTJfc0n69iwSiHjvuAdruxl7elnZCxgt8=
|
||||
github.com/go-redis/redis/v8 v8.11.3/go.mod h1:xNJ9xDG09FsIPwh3bWdk+0oDWHbtF9rPN0F/oD9XeKc=
|
||||
github.com/go-redis/redis/v8 v8.11.4 h1:kHoYkfZP6+pe04aFTnhDH6GDROa5yJdHJVNxV3F46Tg=
|
||||
github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w=
|
||||
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=
|
||||
|
@ -225,16 +242,17 @@ github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6Wezm
|
|||
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
|
||||
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
|
||||
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
|
||||
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
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.0.0 h1:RAqyYixv1p7uEnocuy8P1nru5wprCh/MH2BIlW5z5/o=
|
||||
github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
||||
github.com/golang-jwt/jwt/v4 v4.1.0 h1:XUgk2Ex5veyVFVeLm0xhusUTQybEbexJXrvPNOKkSY0=
|
||||
github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
|
||||
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/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||
|
@ -251,6 +269,7 @@ github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt
|
|||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
|
||||
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
|
@ -271,6 +290,7 @@ github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw
|
|||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
||||
|
@ -294,6 +314,7 @@ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/
|
|||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
|
@ -305,28 +326,36 @@ github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLe
|
|||
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||
github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
|
||||
github.com/hashicorp/consul/api v1.10.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M=
|
||||
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
||||
github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms=
|
||||
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||
github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o=
|
||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||
github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI=
|
||||
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
|
||||
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
|
||||
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
|
||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
|
@ -339,8 +368,11 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
|||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
||||
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
||||
github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY=
|
||||
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
||||
github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
|
||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||
github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk=
|
||||
github.com/iancoleman/strcase v0.2.0 h1:05I4QRnGpI0m37iZQRuskXh+w77mr6Z41lwQzuHLwW0=
|
||||
github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
|
@ -411,7 +443,6 @@ github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMW
|
|||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||
|
@ -429,6 +460,7 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
|
|||
github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/compress v1.10.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||
github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
|
||||
github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/kolaente/caldav-go v3.0.1-0.20190524174923-9e5cd1688227+incompatible h1:PkEEpmbrFXlMul8cOplR8nkcIM/NDbx+H6fq2+vaKAA=
|
||||
github.com/kolaente/caldav-go v3.0.1-0.20190524174923-9e5cd1688227+incompatible/go.mod h1:y1UhTNI4g0hVymJrI6yJ5/ohy09hNBeU8iJEZjgdDOw=
|
||||
|
@ -441,6 +473,7 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxv
|
|||
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
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=
|
||||
|
@ -448,10 +481,12 @@ 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/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g=
|
||||
github.com/labstack/echo/v4 v4.1.16/go.mod h1:awO+5TzAjvL8XpibdsfXxPgHr+orhtXZJZIQCVjogKI=
|
||||
github.com/labstack/echo/v4 v4.5.0 h1:JXk6H5PAw9I3GwizqUHhYyS4f45iyGebR/c1xNCeOCY=
|
||||
github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y=
|
||||
github.com/labstack/echo/v4 v4.6.1 h1:OMVsrnNFzYlGSdaiYGHbgWQnr+JM7NG+B9suCPie14M=
|
||||
github.com/labstack/echo/v4 v4.6.1/go.mod h1:RnjgMWNDB9g/HucVWhQYNQP9PvbYf6adqftqryo7s9k=
|
||||
github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0=
|
||||
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
|
||||
github.com/labstack/gommon v0.3.1 h1:OomWaJXm7xR6L1HmEtGyQf26TEn7V6X88mktX9kee9o=
|
||||
github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
|
||||
github.com/laurent22/ical-go v0.1.1-0.20181107184520-7e5d6ade8eef h1:RZnRnSID1skF35j/15KJ6hKZkdIC/teQClJK5wP5LU4=
|
||||
github.com/laurent22/ical-go v0.1.1-0.20181107184520-7e5d6ade8eef/go.mod h1:4LATl0uhhtytR6p9n1AlktDyIz4u2iUnWEdI3L/hXiw=
|
||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
|
@ -460,8 +495,11 @@ github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
|||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
|
||||
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lib/pq v1.10.3 h1:v9QZf2Sn6AmjXtQeFpdoq/eaNtYP6IN+7lcrygsIAtg=
|
||||
github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk=
|
||||
github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lithammer/shortuuid/v3 v3.0.4 h1:uj4xhotfY92Y1Oa6n6HUiFn87CdoEHYUlTy0+IgbLrs=
|
||||
github.com/lithammer/shortuuid/v3 v3.0.4/go.mod h1:RviRjexKqIzx/7r1peoAITm6m7gnif/h+0zmolKJjzw=
|
||||
github.com/magefile/mage v1.11.0 h1:C/55Ywp9BpgVVclD3lRnSYCwXTYxmSppIgLeDYlNuls=
|
||||
|
@ -476,18 +514,23 @@ github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJ
|
|||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
|
||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs=
|
||||
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
||||
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
|
||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA=
|
||||
github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-oci8 v0.0.0-20191108001511-cbd8d5bc1da0/go.mod h1:/M9VLO+lUPmxvoOK2PfWRZ8mTtB4q1Hy9lEGijv9Nr8=
|
||||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
|
@ -498,13 +541,17 @@ github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A
|
|||
github.com/mattn/go-sqlite3 v1.14.7/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mattn/go-sqlite3 v1.14.8 h1:gDp86IdQsN/xWjIEmr9MF6o9mpksUgh0fu+9ByFxzIU=
|
||||
github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mattn/go-sqlite3 v1.14.9 h1:10HX2Td0ocZpYEjhilsuo6WWtUqttj2Kb0KtD86/KYA=
|
||||
github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
||||
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8=
|
||||
github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
|
||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
|
@ -512,8 +559,9 @@ github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS4
|
|||
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
|
||||
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/mapstructure v1.4.2 h1:6h7AQ0yhTcIsmFmnAwQls75jp2Gzs4iB8W7pjMO+rqo=
|
||||
github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
|
@ -547,15 +595,16 @@ github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa
|
|||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
|
||||
github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
|
||||
github.com/onsi/gomega v1.15.0 h1:WjP/FQ/sk43MRmnEcT+MlDw2TFvkrXlprrPST/IudjU=
|
||||
github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0=
|
||||
github.com/onsi/gomega v1.16.0 h1:6gjqkI8iiRHMvdccRJM8rVKjCWk6ZIm6FTm3ddIe4/c=
|
||||
github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
||||
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pelletier/go-toml v1.9.3 h1:zeC5b1GviRUyKYd6OJPvBU/mcVDVoL1OhT17FCt5dSQ=
|
||||
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM=
|
||||
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
|
||||
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
|
||||
|
@ -567,6 +616,7 @@ github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZ
|
|||
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/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
|
||||
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=
|
||||
|
@ -608,29 +658,31 @@ github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0
|
|||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/sagikazarmark/crypt v0.1.0/go.mod h1:B/mN0msZuINBtQ1zZLEQcegFJJf9vnYIR88KRMEuODE=
|
||||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
|
||||
github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
||||
github.com/shopspring/decimal v0.0.0-20191009025716-f1972eb1d1f5 h1:Gojs/hac/DoYEM7WEICT45+hNWczIeuL5D21e5/HPAw=
|
||||
github.com/shopspring/decimal v0.0.0-20191009025716-f1972eb1d1f5/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
|
||||
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
|
||||
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY=
|
||||
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
|
||||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA=
|
||||
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
||||
github.com/spf13/cobra v1.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw=
|
||||
github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk=
|
||||
|
@ -641,8 +693,9 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn
|
|||
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.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||
github.com/spf13/viper v1.8.1 h1:Kq1fyeebqsBfbjZj4EL7gj2IO0mMaiyjYUWcUsl2O44=
|
||||
github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns=
|
||||
github.com/spf13/viper v1.9.0 h1:yR6EXjTp0y0cLN8OZg1CRZmOBdI88UcGkhgyJhu6nZk=
|
||||
github.com/spf13/viper v1.9.0/go.mod h1:+i6ajR7OX2XaiBkrcZJFK21htRk7eDeLg7+O6bhUPP4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
|
@ -655,8 +708,10 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc
|
|||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/swaggo/swag v1.7.1 h1:gY9ZakXlNWg/i/v5bQBic7VMZ4teq4m89lpiao74p/s=
|
||||
github.com/swaggo/swag v1.7.1/go.mod h1:gAiHxNTb9cIpNmA/VEGUP+CyZMCP/EW7mdtc8Bny+p8=
|
||||
github.com/swaggo/swag v1.7.3 h1:ucB7irEdRrhjmW+Z1Ss4GjO68oPKQFjSgOR8BCAvcbU=
|
||||
github.com/swaggo/swag v1.7.3/go.mod h1:zD8h6h4SPv7t3l+4BKdRquqW1ASWjKZgT6Qv9z3kNqI=
|
||||
github.com/swaggo/swag v1.7.4 h1:up+ixy8yOqJKiFcuhMgkuYuF4xnevuhnFAXXF8OSfNg=
|
||||
github.com/swaggo/swag v1.7.4/go.mod h1:zD8h6h4SPv7t3l+4BKdRquqW1ASWjKZgT6Qv9z3kNqI=
|
||||
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
|
||||
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
|
||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||
|
@ -665,6 +720,8 @@ github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljT
|
|||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/ulule/limiter/v3 v3.8.0 h1:rq76QxDIq5s/rvXc/A6HRHuGmehi/JE18qK3FaRUxKg=
|
||||
github.com/ulule/limiter/v3 v3.8.0/go.mod h1:TpV4HWgOM7M43mrkE7MU1S62/XtuoZ/C9PL+ExxeTK4=
|
||||
github.com/ulule/limiter/v3 v3.9.0 h1:ebASTkd6QNNUGhuDrWqImMpsg9GtItgNgxF3nKao58Q=
|
||||
github.com/ulule/limiter/v3 v3.9.0/go.mod h1:icWc7rrF3T07dj59AhU4+HqKh0uWeh1wKct31wmcoF0=
|
||||
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
|
||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||
github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
|
||||
|
@ -672,12 +729,14 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw
|
|||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w=
|
||||
github.com/valyala/fasthttp v1.17.0/go.mod h1:jjraHZVbKOXftJfsOYoAjaeygpj5hr8ermTRJNroD7A=
|
||||
github.com/valyala/fasthttp v1.31.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD3K/7o2Cus=
|
||||
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/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
|
||||
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
|
||||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
|
@ -691,8 +750,14 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
|
|||
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
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.0 h1:OtISOGfH6sOWa1/qXqqAiOIAO6Z5J3AEAE18WAq6BiQ=
|
||||
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.1 h1:/vn0k+RBvwlxEmP5E7SZMqNxPhfMVFEJiykr15/0XKM=
|
||||
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.4.2 h1:5qVKCqCRBaGz8EepBTi7pbIw8gGCFnB1Mi6kXU4dYv8=
|
||||
github.com/yuin/goldmark v1.4.2/go.mod h1:rmuwmfZ0+bvzB24eSC//bk1R1Zp3hM0OXYv/G2LIilg=
|
||||
github.com/yuin/goldmark v1.4.3 h1:eTEYYWtQLjK7+WK45Tk81OTkp/0UvAyqUj8flU0nTO4=
|
||||
github.com/yuin/goldmark v1.4.3/go.mod h1:rmuwmfZ0+bvzB24eSC//bk1R1Zp3hM0OXYv/G2LIilg=
|
||||
github.com/yuin/goldmark v1.4.4 h1:zNWRjYUW32G9KirMXYHQHVNFkXvMI7LpgNW2AgYAoIs=
|
||||
github.com/yuin/goldmark v1.4.4/go.mod h1:rmuwmfZ0+bvzB24eSC//bk1R1Zp3hM0OXYv/G2LIilg=
|
||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
|
||||
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
|
||||
|
@ -707,6 +772,7 @@ go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
|||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
|
||||
go.opentelemetry.io/otel v0.14.0/go.mod h1:vH5xEuwy7Rts0GNtsCW3HYQoZDY+OmBJ6t1bFGGlxgw=
|
||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
|
@ -725,22 +791,23 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U
|
|||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210812204632-0ba0e8f03122 h1:AOT7vJYHE32m61R8d1WlcqhOO1AocesDsKpcMq+UOaA=
|
||||
golang.org/x/crypto v0.0.0-20210812204632-0ba0e8f03122/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e h1:VvfwVmMH40bpMeizC9/K7ipM5Qjucuu16RWfneFPyhQ=
|
||||
golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ=
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa h1:idItI2DDfCokpg0N51B2VtiLdJ4vAuXC9fnCb2gACo4=
|
||||
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211115234514-b4de73f9ece8 h1:5QRxNnVsaJP6NAse0UdkRgL3zHMvCRRkrDVLNdNpdy4=
|
||||
golang.org/x/crypto v0.0.0-20211115234514-b4de73f9ece8/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 h1:/pEO3GD/ABYAjuakUS6xSEmmlyVS4kxBNkeA9tLJiTI=
|
||||
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
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=
|
||||
|
@ -756,6 +823,8 @@ golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+o
|
|||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d h1:RNPAfi2nHY7C2srAV8A49jpsYr0ADedCk1wq6fTMTvs=
|
||||
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
|
||||
golang.org/x/image v0.0.0-20211028202545-6944b10bf410 h1:hTftEOvwiOq2+O8k2D5/Q7COC7k5Qcrgc2TFURJYnvQ=
|
||||
golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
|
||||
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=
|
||||
|
@ -803,6 +872,7 @@ golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLL
|
|||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
|
@ -830,9 +900,13 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
|
|||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
|
||||
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q=
|
||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210913180222-943fd674d43e h1:+b/22bPvDYt4NPDcy4xAGCmON713ONAWFeY3Z7I3tR8=
|
||||
golang.org/x/net v0.0.0-20210913180222-943fd674d43e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
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=
|
||||
|
@ -845,12 +919,16 @@ golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ
|
|||
golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210810183815-faf39c7919d5 h1:Ati8dO7+U7mxpkPSxBZQEvzHVUYB/MqCklCN8ig5w/o=
|
||||
golang.org/x/oauth2 v0.0.0-20210810183815-faf39c7919d5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210817223510-7df4dd6e12ab h1:llrcWN/wOwO+6gAyfBzxb5hZ+c3mriU/0+KNgYu6adA=
|
||||
golang.org/x/oauth2 v0.0.0-20210817223510-7df4dd6e12ab/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f h1:Qmd2pbz05z7z6lm0DrgQVVPuBm92jqujBKMHMOlOQEw=
|
||||
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1 h1:B333XXssMuKQeBwiNODx4TupZy7bf4sxFZnN2ZOcvUE=
|
||||
golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20211028175245-ba495a64dcb5 h1:v79phzBz03tsVCUTbvTBmmC3CUXF5mKYt7DA4ZVldpM=
|
||||
golang.org/x/oauth2 v0.0.0-20211028175245-ba495a64dcb5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg=
|
||||
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
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=
|
||||
|
@ -886,8 +964,12 @@ golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
@ -895,6 +977,7 @@ golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
@ -927,29 +1010,69 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e h1:WUoyKPm6nCo1BnNUvPGnFG3T5DUVem42yDJZZ4CNxMA=
|
||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210816032535-30e4713e60e3 h1:7hHxyYeKyS0AU/brXAMuc+9BxCO/a4vL1DoUVLDTVIo=
|
||||
golang.org/x/sys v0.0.0-20210816032535-30e4713e60e3/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71 h1:ikCpsnYR+Ew0vu99XlDp55lGgDJdIMx3f4a18jfse/s=
|
||||
golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912 h1:uCLL3g5wH2xjxVREVuAbP9JM5PPKjRbXKRa6IBjkzmU=
|
||||
golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210817142637-7d9622a276b7 h1:lQ8Btl/sJr2+f4ql7ffKUKfnV0BsgsICvm0oEeINAQY=
|
||||
golang.org/x/sys v0.0.0-20210817142637-7d9622a276b7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210817190340-bfb29a6856f2 h1:c8PlLMqBbOHoqtjteWm5/kbe6rNY2pbRfbIMVnepueo=
|
||||
golang.org/x/sys v0.0.0-20210817190340-bfb29a6856f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210818153620-00dd8d7831e7 h1:/bmDWM82ZX7TawqxuI8kVjKI0TXHdSY6pHJArewwHtU=
|
||||
golang.org/x/sys v0.0.0-20210818153620-00dd8d7831e7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c h1:Lyn7+CqXIiC+LOR9aHD6jDK+hPcmAuCfuXztd1v4w1Q=
|
||||
golang.org/x/sys v0.0.0-20210819135213-f52c844e1c1c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210820121016-41cdb8703e55 h1:rw6UNGRMfarCepjI8qOepea/SXwIBVfTKjztZ5gBbq4=
|
||||
golang.org/x/sys v0.0.0-20210820121016-41cdb8703e55/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210910150752-751e447fb3d0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211015200801-69063c4bb744 h1:KzbpndAYEM+4oHRp9JmB2ewj0NHHxO3Z0g7Gus2O1kk=
|
||||
golang.org/x/sys v0.0.0-20211015200801-69063c4bb744/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 h1:id054HUawV2/6IGm2IV8KZQjqtwAOo2CYlOToYqa0d0=
|
||||
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211020064051-0ec99a608a1b h1:byBDhtWGQmWDrv1MlEv/BzGRMkw36h9QqsNnZQcDhRw=
|
||||
golang.org/x/sys v0.0.0-20211020064051-0ec99a608a1b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211020174200-9d6173849985 h1:LOlKVhfDyahgmqa97awczplwkjzNaELFg3zRIJ13RYo=
|
||||
golang.org/x/sys v0.0.0-20211020174200-9d6173849985/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211022215931-8e5104632af7 h1:e2q1CMOFXDvurT2sa2yhJAkuA2n8Rd9tMDd7Tcfvs6M=
|
||||
golang.org/x/sys v0.0.0-20211022215931-8e5104632af7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211023085530-d6a326fbbf70 h1:SeSEfdIxyvwGJliREIJhRPPXvW6sDlLT+UQ3B0hD0NA=
|
||||
golang.org/x/sys v0.0.0-20211023085530-d6a326fbbf70/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211025112917-711f33c9992c h1:i4MLwL3EbCgobekQtkVW94UBSPLMadfEGtKq+CAFsEU=
|
||||
golang.org/x/sys v0.0.0-20211025112917-711f33c9992c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359 h1:2B5p2L5IfGiD7+b9BOoRMC6DgObAVZV+Fsp050NqXik=
|
||||
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211029165221-6e7872819dc8 h1:M69LAlWZCshgp0QSzyDcSsSIejIEeuaCVpmwcKwyLMk=
|
||||
golang.org/x/sys v0.0.0-20211029165221-6e7872819dc8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211030160813-b3129d9d1021 h1:giLT+HuUP/gXYrG2Plg9WTjj4qhfgaW424ZIFog3rlk=
|
||||
golang.org/x/sys v0.0.0-20211030160813-b3129d9d1021/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211031064116-611d5d643895 h1:iaNpwpnrgL5jzWS0vCNnfa8HqzxveCFpFx3uC/X4Tps=
|
||||
golang.org/x/sys v0.0.0-20211031064116-611d5d643895/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211101204403-39c9dd37992c h1:rnNohYBMnXA07uGnZ9CSWNhIu4Gob4FqWS43lLqZ2sU=
|
||||
golang.org/x/sys v0.0.0-20211101204403-39c9dd37992c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211102061401-a2f17f7b995c h1:QOfDMdrf/UwlVR0UBq2Mpr58UzNtvgJRXA4BgPfFACs=
|
||||
golang.org/x/sys v0.0.0-20211102061401-a2f17f7b995c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211102192858-4dd72447c267 h1:7zYaz3tjChtpayGDzu6H0hDAUM5zIGA2XW7kRNgQ0jc=
|
||||
golang.org/x/sys v0.0.0-20211102192858-4dd72447c267/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211103184734-ae416a5f93c7 h1:wQUOddybiV2Rfc8FX691KCOx5yEoZlfwpBjtKV6huYo=
|
||||
golang.org/x/sys v0.0.0-20211103184734-ae416a5f93c7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b h1:1VkfZQv42XQlA/jchYumAnv1UPo6RgF9rJFkTgZIxO4=
|
||||
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c h1:+8miTPjMCTXwih7BQmvWwd0PjdBZq2MKp/qQaahSzEM=
|
||||
golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211106132015-ebca88c72f68 h1:Ywe/f3fNleF8I6F6qv3MeFoSZ6CTf2zBMMa/7qVML8M=
|
||||
golang.org/x/sys v0.0.0-20211106132015-ebca88c72f68/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211107104306-e0b2ad06fe42 h1:G2DDmludOQZoWbpCr7OKDxnl478ZBGMcOhrv+ooX/Q4=
|
||||
golang.org/x/sys v0.0.0-20211107104306-e0b2ad06fe42/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211109184856-51b60fd695b3 h1:T6tyxxvHMj2L1R2kZg0uNMpS8ZhB9lRa9XRGTCSA65w=
|
||||
golang.org/x/sys v0.0.0-20211109184856-51b60fd695b3/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211110154304-99a53858aa08 h1:WecRHqgE09JBkh/584XIE6PMz5KKE/vER4izNUi30AQ=
|
||||
golang.org/x/sys v0.0.0-20211110154304-99a53858aa08/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211111213525-f221eed1c01e h1:zeJt6jBtVDK23XK9QXcmG0FvO0elikp0dYZQZOeL1y0=
|
||||
golang.org/x/sys v0.0.0-20211111213525-f221eed1c01e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211113001501-0c823b97ae02 h1:7NCfEGl0sfUojmX78nK9pBJuUlSZWEJA/TwASvfiPLo=
|
||||
golang.org/x/sys v0.0.0-20211113001501-0c823b97ae02/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c h1:DHcbWVXeY+0Y8HHKR+rbLwnoh2F4tNCY7rTiHJ30RmA=
|
||||
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1 h1:kwrAHlwJ0DUBZwQ238v+Uod/3eZ8B2K5rYsUHBQvzmI=
|
||||
golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b h1:9zKuko04nR4gjZ4+DNjHqRlAJqbJETHwiNKDqTfOjfE=
|
||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
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=
|
||||
|
@ -957,8 +1080,9 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
|||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
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 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
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=
|
||||
|
@ -985,6 +1109,7 @@ golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgw
|
|||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191109212701-97ad0ed33101/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
|
@ -1024,8 +1149,12 @@ golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4f
|
|||
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.2 h1:kRBLX7v7Af8W7Gdbbc908OJcdgtK8bOz9Uaj8/F1ACA=
|
||||
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
@ -1056,6 +1185,12 @@ google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjR
|
|||
google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU=
|
||||
google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94=
|
||||
google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8=
|
||||
google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo=
|
||||
google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4=
|
||||
google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw=
|
||||
google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=
|
||||
google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
|
||||
google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
|
@ -1106,7 +1241,18 @@ google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6D
|
|||
google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||
google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A=
|
||||
google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
|
||||
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
||||
google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
||||
google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
||||
google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24=
|
||||
google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
|
||||
google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k=
|
||||
google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
|
||||
google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48=
|
||||
google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w=
|
||||
google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
||||
google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
|
@ -1127,7 +1273,13 @@ google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA5
|
|||
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||
google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
|
||||
google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
|
||||
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
|
@ -1139,8 +1291,9 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
|
|||
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
|
||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
|
||||
|
@ -1158,8 +1311,9 @@ gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AW
|
|||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
|
||||
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
|
||||
gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
|
||||
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.63.2 h1:tGK/CyBg7SMzb60vP1M03vNZ3VDu3wGQJwn7Sxi9r3c=
|
||||
gopkg.in/ini.v1 v1.63.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
|
||||
gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w=
|
||||
gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
|
|
22
magefile.go
22
magefile.go
|
@ -74,9 +74,19 @@ var (
|
|||
}
|
||||
)
|
||||
|
||||
func runCmdWithOutput(name string, arg ...string) (output []byte, err error) {
|
||||
cmd := exec.Command(name, arg...)
|
||||
output, err = cmd.Output()
|
||||
if err != nil {
|
||||
ee := err.(*exec.ExitError)
|
||||
return nil, fmt.Errorf("error running command: %s, %s", string(ee.Stderr), err)
|
||||
}
|
||||
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func setVersion() {
|
||||
versionCmd := exec.Command("git", "describe", "--tags", "--always", "--abbrev=10")
|
||||
version, err := versionCmd.Output()
|
||||
version, err := runCmdWithOutput("git", "describe", "--tags", "--always", "--abbrev=10")
|
||||
if err != nil {
|
||||
fmt.Printf("Error getting version: %s\n", err)
|
||||
os.Exit(1)
|
||||
|
@ -117,8 +127,7 @@ func setExecutable() {
|
|||
}
|
||||
|
||||
func setApiPackages() {
|
||||
cmd := exec.Command("go", "list", "all")
|
||||
pkgs, err := cmd.Output()
|
||||
pkgs, err := runCmdWithOutput("go", "list", "all")
|
||||
if err != nil {
|
||||
fmt.Printf("Error getting packages: %s\n", err)
|
||||
os.Exit(1)
|
||||
|
@ -145,8 +154,7 @@ func setRootPath() {
|
|||
|
||||
func setGoFiles() {
|
||||
// GOFILES := $(shell find . -name "*.go" -type f ! -path "*/bindata.go")
|
||||
cmd := exec.Command("find", ".", "-name", "*.go", "-type", "f", "!", "-path", "*/bindata.go")
|
||||
files, err := cmd.Output()
|
||||
files, err := runCmdWithOutput("find", ".", "-name", "*.go", "-type", "f", "!", "-path", "*/bindata.go")
|
||||
if err != nil {
|
||||
fmt.Printf("Error getting go files: %s\n", err)
|
||||
os.Exit(1)
|
||||
|
@ -170,7 +178,6 @@ func initVars() {
|
|||
setVersion()
|
||||
setBinLocation()
|
||||
setPkgVersion()
|
||||
setApiPackages()
|
||||
setGoFiles()
|
||||
Ldflags = `-X "` + PACKAGE + `/pkg/version.Version=` + VersionNumber + `" -X "main.Tags=` + Tags + `"`
|
||||
}
|
||||
|
@ -340,6 +347,7 @@ type Test mg.Namespace
|
|||
// Runs all tests except integration tests
|
||||
func (Test) Unit() {
|
||||
mg.Deps(initVars)
|
||||
setApiPackages()
|
||||
// We run everything sequentially and not in parallel to prevent issues with real test databases
|
||||
args := append([]string{"test", Goflags[0], "-p", "1", "-timeout", "20m"}, ApiPackages...)
|
||||
runAndStreamOutput("go", args...)
|
||||
|
|
|
@ -26,7 +26,7 @@ import (
|
|||
"github.com/laurent22/ical-go"
|
||||
)
|
||||
|
||||
func GetCaldavTodosForTasks(list *models.List, listTasks []*models.Task) string {
|
||||
func GetCaldavTodosForTasks(list *models.ListWithTasksAndBuckets, listTasks []*models.TaskWithComments) string {
|
||||
|
||||
// Make caldav todos from Vikunja todos
|
||||
var caldavtodos []*Todo
|
||||
|
|
|
@ -103,7 +103,7 @@ func getUserFromArg(s *xorm.Session, arg string) *user.User {
|
|||
log.Fatalf("Invalid user id: %s", err)
|
||||
}
|
||||
|
||||
u, err := user.GetUserByID(s, id)
|
||||
u, err := user.GetUserWithEmail(s, &user.User{ID: id})
|
||||
if err != nil {
|
||||
log.Fatalf("Could not get user: %s", err)
|
||||
}
|
||||
|
|
|
@ -37,6 +37,7 @@ type Key string
|
|||
const (
|
||||
// #nosec
|
||||
ServiceJWTSecret Key = `service.JWTSecret`
|
||||
ServiceJWTTTL Key = `service.jwtttl`
|
||||
ServiceInterface Key = `service.interface`
|
||||
ServiceUnixSocket Key = `service.unixsocket`
|
||||
ServiceUnixSocketMode Key = `service.unixsocketmode`
|
||||
|
@ -226,6 +227,7 @@ func InitDefaultConfig() {
|
|||
|
||||
// Service
|
||||
ServiceJWTSecret.setDefault(random)
|
||||
ServiceJWTTTL.setDefault(259200)
|
||||
ServiceInterface.setDefault(":3456")
|
||||
ServiceUnixSocket.setDefault("")
|
||||
ServiceFrontendurl.setDefault("")
|
||||
|
|
|
@ -22,6 +22,8 @@ import (
|
|||
"strconv"
|
||||
"time"
|
||||
|
||||
"xorm.io/xorm"
|
||||
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/db"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
|
@ -77,7 +79,18 @@ func Create(f io.Reader, realname string, realsize uint64, a web.Auth) (file *Fi
|
|||
|
||||
// CreateWithMime creates a new file from an FileHeader and sets its mime type
|
||||
func CreateWithMime(f io.Reader, realname string, realsize uint64, a web.Auth, mime string) (file *File, err error) {
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
file, err = CreateWithMimeAndSession(s, f, realname, realsize, a, mime)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
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()))
|
||||
|
@ -96,21 +109,13 @@ func CreateWithMime(f io.Reader, realname string, realsize uint64, a web.Auth, m
|
|||
Mime: mime,
|
||||
}
|
||||
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
_, err = s.Insert(file)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return
|
||||
}
|
||||
|
||||
// Save the file to storage with its new ID as path
|
||||
err = file.Save(f)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -97,6 +97,7 @@ func FullInit() {
|
|||
user.RegisterTokenCleanupCron()
|
||||
user.RegisterDeletionNotificationCron()
|
||||
models.RegisterUserDeletionCron()
|
||||
models.RegisterOldExportCleanupCron()
|
||||
|
||||
// Start processing events
|
||||
go func() {
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
// 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 integrations
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"code.vikunja.io/api/pkg/routes"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestHealthcheck(t *testing.T) {
|
||||
t.Run("healthcheck", func(t *testing.T) {
|
||||
rec, err := newTestRequest(t, http.MethodGet, routes.HealthcheckHandler, ``, nil, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), "OK")
|
||||
})
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
// 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 migration
|
||||
|
||||
import (
|
||||
"src.techknowlogick.com/xormigrate"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
type users20210829194722 struct {
|
||||
ExportFileID int64 `xorm:"bigint null" json:"-"`
|
||||
}
|
||||
|
||||
func (users20210829194722) TableName() string {
|
||||
return "users"
|
||||
}
|
||||
|
||||
func init() {
|
||||
migrations = append(migrations, &xormigrate.Migration{
|
||||
ID: "20210829194722",
|
||||
Description: "Add data export file id to users",
|
||||
Migrate: func(tx *xorm.Engine) error {
|
||||
return tx.Sync2(users20210829194722{})
|
||||
},
|
||||
Rollback: func(tx *xorm.Engine) error {
|
||||
return nil
|
||||
},
|
||||
})
|
||||
}
|
|
@ -21,6 +21,16 @@ import (
|
|||
"code.vikunja.io/web"
|
||||
)
|
||||
|
||||
// DataExportRequestEvent represents a DataExportRequestEvent event
|
||||
type DataExportRequestEvent struct {
|
||||
User *user.User
|
||||
}
|
||||
|
||||
// Name defines the name for DataExportRequestEvent
|
||||
func (t *DataExportRequestEvent) Name() string {
|
||||
return "user.export.request"
|
||||
}
|
||||
|
||||
/////////////////
|
||||
// Task Events //
|
||||
/////////////////
|
||||
|
@ -257,3 +267,13 @@ type TeamDeletedEvent struct {
|
|||
func (t *TeamDeletedEvent) Name() string {
|
||||
return "team.deleted"
|
||||
}
|
||||
|
||||
// UserDataExportRequestedEvent represents a UserDataExportRequestedEvent event
|
||||
type UserDataExportRequestedEvent struct {
|
||||
User *user.User
|
||||
}
|
||||
|
||||
// Name defines the name for UserDataExportRequestedEvent
|
||||
func (t *UserDataExportRequestedEvent) Name() string {
|
||||
return "user.export.requested"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,366 @@
|
|||
// 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 models
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/cron"
|
||||
"code.vikunja.io/api/pkg/db"
|
||||
"code.vikunja.io/api/pkg/files"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/notifications"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"code.vikunja.io/api/pkg/utils"
|
||||
"code.vikunja.io/api/pkg/version"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func ExportUserData(s *xorm.Session, u *user.User) (err error) {
|
||||
exportDir := config.FilesBasePath.GetString() + "/user-export-tmp/"
|
||||
err = os.MkdirAll(exportDir, 0700)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tmpFilename := exportDir + strconv.FormatInt(u.ID, 10) + "_" + time.Now().Format("2006-01-02_15-03-05") + ".zip"
|
||||
|
||||
// Open zip
|
||||
dumpFile, err := os.Create(tmpFilename)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error opening dump file: %s", err)
|
||||
}
|
||||
defer dumpFile.Close()
|
||||
|
||||
dumpWriter := zip.NewWriter(dumpFile)
|
||||
defer dumpWriter.Close()
|
||||
|
||||
// Get the data
|
||||
err = exportListsAndTasks(s, u, dumpWriter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Task attachment files
|
||||
err = exportTaskAttachments(s, u, dumpWriter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Saved filters
|
||||
err = exportSavedFilters(s, u, dumpWriter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Background files
|
||||
err = exportListBackgrounds(s, u, dumpWriter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Vikunja Version
|
||||
err = utils.WriteBytesToZip("VERSION", []byte(version.Version), dumpWriter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If we reuse the same file again, saving it as a file in Vikunja will save it as a file with 0 bytes in size.
|
||||
// Closing and reopening does work.
|
||||
dumpWriter.Close()
|
||||
dumpFile.Close()
|
||||
|
||||
exported, err := os.Open(tmpFilename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
stat, err := exported.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
exportFile, err := files.CreateWithMimeAndSession(s, exported, tmpFilename, uint64(stat.Size()), u, "application/zip")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Save the file id with the user
|
||||
u.ExportFileID = exportFile.ID
|
||||
_, err = s.Cols("export_file_id").Update(u)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Remove the old file
|
||||
err = os.Remove(exported.Name())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Send a notification
|
||||
return notifications.Notify(u, &DataExportReadyNotification{
|
||||
User: u,
|
||||
})
|
||||
}
|
||||
|
||||
func exportListsAndTasks(s *xorm.Session, u *user.User, wr *zip.Writer) (err error) {
|
||||
|
||||
namspaces, _, _, err := (&Namespace{IsArchived: true}).ReadAll(s, u, "", -1, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
namespaceIDs := []int64{}
|
||||
namespaces := []*NamespaceWithListsAndTasks{}
|
||||
listMap := make(map[int64]*ListWithTasksAndBuckets)
|
||||
listIDs := []int64{}
|
||||
for _, n := range namspaces.([]*NamespaceWithLists) {
|
||||
if n.ID < 1 {
|
||||
// Don't include filters
|
||||
continue
|
||||
}
|
||||
|
||||
nn := &NamespaceWithListsAndTasks{
|
||||
Namespace: n.Namespace,
|
||||
Lists: []*ListWithTasksAndBuckets{},
|
||||
}
|
||||
|
||||
for _, l := range n.Lists {
|
||||
ll := &ListWithTasksAndBuckets{
|
||||
List: *l,
|
||||
BackgroundFileID: l.BackgroundFileID,
|
||||
Tasks: []*TaskWithComments{},
|
||||
}
|
||||
nn.Lists = append(nn.Lists, ll)
|
||||
listMap[l.ID] = ll
|
||||
listIDs = append(listIDs, l.ID)
|
||||
}
|
||||
|
||||
namespaceIDs = append(namespaceIDs, n.ID)
|
||||
namespaces = append(namespaces, nn)
|
||||
}
|
||||
|
||||
if len(namespaceIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get all lists
|
||||
lists, err := getListsForNamespaces(s, namespaceIDs, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tasks, _, _, err := getTasksForLists(s, lists, u, &taskOptions{
|
||||
page: 0,
|
||||
perPage: -1,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
taskMap := make(map[int64]*TaskWithComments, len(tasks))
|
||||
for _, t := range tasks {
|
||||
taskMap[t.ID] = &TaskWithComments{
|
||||
Task: *t,
|
||||
}
|
||||
if _, exists := listMap[t.ListID]; !exists {
|
||||
log.Debugf("[User Data Export] List %d does not exist for task %d, omitting", t.ListID, t.ID)
|
||||
continue
|
||||
}
|
||||
listMap[t.ListID].Tasks = append(listMap[t.ListID].Tasks, taskMap[t.ID])
|
||||
}
|
||||
|
||||
comments := []*TaskComment{}
|
||||
err = s.
|
||||
Join("LEFT", "tasks", "tasks.id = task_comments.task_id").
|
||||
In("tasks.list_id", listIDs).
|
||||
Find(&comments)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, c := range comments {
|
||||
if _, exists := taskMap[c.TaskID]; !exists {
|
||||
log.Debugf("[User Data Export] Task %d does not exist for comment %d, omitting", c.TaskID, c.ID)
|
||||
continue
|
||||
}
|
||||
taskMap[c.TaskID].Comments = append(taskMap[c.TaskID].Comments, c)
|
||||
}
|
||||
|
||||
buckets := []*Bucket{}
|
||||
err = s.In("list_id", listIDs).Find(&buckets)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, b := range buckets {
|
||||
if _, exists := listMap[b.ListID]; !exists {
|
||||
log.Debugf("[User Data Export] List %d does not exist for bucket %d, omitting", b.ListID, b.ID)
|
||||
continue
|
||||
}
|
||||
listMap[b.ListID].Buckets = append(listMap[b.ListID].Buckets, b)
|
||||
}
|
||||
|
||||
data, err := json.Marshal(namespaces)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utils.WriteBytesToZip("data.json", data, wr)
|
||||
}
|
||||
|
||||
func exportTaskAttachments(s *xorm.Session, u *user.User, wr *zip.Writer) (err error) {
|
||||
lists, _, _, err := getRawListsForUser(
|
||||
s,
|
||||
&listOptions{
|
||||
user: u,
|
||||
page: -1,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tasks, _, _, err := getRawTasksForLists(s, lists, u, &taskOptions{page: -1})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
taskIDs := []int64{}
|
||||
for _, t := range tasks {
|
||||
taskIDs = append(taskIDs, t.ID)
|
||||
}
|
||||
|
||||
tas, err := getTaskAttachmentsByTaskIDs(s, taskIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fs := make(map[int64]io.ReadCloser)
|
||||
for _, ta := range tas {
|
||||
if err := ta.File.LoadFileByID(); err != nil {
|
||||
return err
|
||||
}
|
||||
fs[ta.FileID] = ta.File.File
|
||||
}
|
||||
|
||||
return utils.WriteFilesToZip(fs, wr)
|
||||
}
|
||||
|
||||
func exportSavedFilters(s *xorm.Session, u *user.User, wr *zip.Writer) (err error) {
|
||||
filters, err := getSavedFiltersForUser(s, u)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data, err := json.Marshal(filters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return utils.WriteBytesToZip("filters.json", data, wr)
|
||||
}
|
||||
|
||||
func exportListBackgrounds(s *xorm.Session, u *user.User, wr *zip.Writer) (err error) {
|
||||
lists, _, _, err := getRawListsForUser(
|
||||
s,
|
||||
&listOptions{
|
||||
user: u,
|
||||
page: -1,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fs := make(map[int64]io.ReadCloser)
|
||||
for _, l := range lists {
|
||||
if l.BackgroundFileID == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
bgFile := &files.File{
|
||||
ID: l.BackgroundFileID,
|
||||
}
|
||||
err = bgFile.LoadFileByID()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
fs[l.BackgroundFileID] = bgFile.File
|
||||
}
|
||||
|
||||
return utils.WriteFilesToZip(fs, wr)
|
||||
}
|
||||
|
||||
func RegisterOldExportCleanupCron() {
|
||||
const logPrefix = "[User Export Cleanup Cron] "
|
||||
|
||||
err := cron.Schedule("0 * * * *", func() {
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
users := []*user.User{}
|
||||
err := s.Where("export_file_id IS NOT NULL AND export_file_id != ?", 0).Find(&users)
|
||||
if err != nil {
|
||||
log.Errorf(logPrefix+"Could not get users with export files: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
fileIDs := []int64{}
|
||||
for _, u := range users {
|
||||
fileIDs = append(fileIDs, u.ExportFileID)
|
||||
}
|
||||
|
||||
fs := []*files.File{}
|
||||
err = s.Where("created < ?", time.Now().Add(-time.Hour*24*7)).In("id", fileIDs).Find(&fs)
|
||||
if err != nil {
|
||||
log.Errorf(logPrefix+"Could not get users with export files: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(fs) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
log.Debugf(logPrefix+"Removing %d old user data exports...", len(fs))
|
||||
|
||||
for _, f := range fs {
|
||||
err = f.Delete()
|
||||
if err != nil {
|
||||
log.Errorf(logPrefix+"Could not remove user export file %d: %s", f.ID, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
_, err = s.In("export_file_id", fileIDs).Cols("export_file_id").Update(&user.User{})
|
||||
if err != nil {
|
||||
log.Errorf(logPrefix+"Could not update user export file state: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
log.Debugf(logPrefix+"Removed %d old user data exports...", len(fs))
|
||||
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("Could not old export cleanup cron: %s", err)
|
||||
}
|
||||
}
|
|
@ -51,12 +51,6 @@ type List struct {
|
|||
|
||||
// The user who created this list.
|
||||
Owner *user.User `xorm:"-" json:"owner" valid:"-"`
|
||||
// An array of tasks which belong to the list.
|
||||
// Deprecated: you should use the dedicated task list endpoint because it has support for pagination and filtering
|
||||
Tasks []*Task `xorm:"-" json:"-"`
|
||||
|
||||
// Only used for migration.
|
||||
Buckets []*Bucket `xorm:"-" json:"-"`
|
||||
|
||||
// Whether or not a list is archived.
|
||||
IsArchived bool `xorm:"not null default false" json:"is_archived" query:"is_archived"`
|
||||
|
@ -85,6 +79,15 @@ type List struct {
|
|||
web.Rights `xorm:"-" json:"-"`
|
||||
}
|
||||
|
||||
type ListWithTasksAndBuckets struct {
|
||||
List
|
||||
// An array of tasks which belong to the list.
|
||||
Tasks []*TaskWithComments `xorm:"-" json:"tasks"`
|
||||
// Only used for migration.
|
||||
Buckets []*Bucket `xorm:"-" json:"buckets"`
|
||||
BackgroundFileID int64 `xorm:"null" json:"background_file_id"`
|
||||
}
|
||||
|
||||
// TableName returns a better name for the lists table
|
||||
func (l *List) TableName() string {
|
||||
return "lists"
|
||||
|
@ -537,12 +540,10 @@ func (l *List) CheckIsArchived(s *xorm.Session) (err error) {
|
|||
return nil
|
||||
}
|
||||
|
||||
// CreateOrUpdateList updates a list or creates it if it doesn't exist
|
||||
func CreateOrUpdateList(s *xorm.Session, list *List, auth web.Auth) (err error) {
|
||||
|
||||
func checkListBeforeUpdateOrDelete(s *xorm.Session, list *List) error {
|
||||
// Check if the namespace exists
|
||||
if list.NamespaceID != 0 && list.NamespaceID != FavoritesPseudoNamespace.ID {
|
||||
_, err = GetNamespaceByID(s, list.NamespaceID)
|
||||
if list.NamespaceID > 0 {
|
||||
_, err := GetNamespaceByID(s, list.NamespaceID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -562,65 +563,115 @@ func CreateOrUpdateList(s *xorm.Session, list *List, auth web.Auth) (err error)
|
|||
}
|
||||
}
|
||||
|
||||
if list.ID == 0 {
|
||||
_, err = s.Insert(list)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
list.Position = calculateDefaultPosition(list.ID, list.Position)
|
||||
_, err = s.Where("id = ?", list.ID).Update(list)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if list.IsFavorite {
|
||||
if err := addToFavorites(s, list.ID, auth, FavoriteKindList); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// We need to specify the cols we want to update here to be able to un-archive lists
|
||||
colsToUpdate := []string{
|
||||
"title",
|
||||
"is_archived",
|
||||
"identifier",
|
||||
"hex_color",
|
||||
"background_file_id",
|
||||
"position",
|
||||
}
|
||||
if list.Description != "" {
|
||||
colsToUpdate = append(colsToUpdate, "description")
|
||||
}
|
||||
func CreateList(s *xorm.Session, list *List, auth web.Auth) (err error) {
|
||||
err = list.CheckIsArchived(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
wasFavorite, err := isFavorite(s, list.ID, auth, FavoriteKindList)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if list.IsFavorite && !wasFavorite {
|
||||
if err := addToFavorites(s, list.ID, auth, FavoriteKindList); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
doer, err := user.GetFromAuth(auth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !list.IsFavorite && wasFavorite {
|
||||
if err := removeFromFavorite(s, list.ID, auth, FavoriteKindList); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
list.OwnerID = doer.ID
|
||||
list.Owner = doer
|
||||
list.ID = 0 // Otherwise only the first time a new list would be created
|
||||
|
||||
_, err = s.
|
||||
ID(list.ID).
|
||||
Cols(colsToUpdate...).
|
||||
Update(list)
|
||||
if err != nil {
|
||||
err = checkListBeforeUpdateOrDelete(s, list)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = s.Insert(list)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
list.Position = calculateDefaultPosition(list.ID, list.Position)
|
||||
_, err = s.Where("id = ?", list.ID).Update(list)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if list.IsFavorite {
|
||||
if err := addToFavorites(s, list.ID, auth, FavoriteKindList); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new first bucket for this list
|
||||
b := &Bucket{
|
||||
ListID: list.ID,
|
||||
Title: "Backlog",
|
||||
}
|
||||
err = b.Create(s, auth)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return events.Dispatch(&ListCreatedEvent{
|
||||
List: list,
|
||||
Doer: doer,
|
||||
})
|
||||
}
|
||||
|
||||
func UpdateList(s *xorm.Session, list *List, auth web.Auth, updateListBackground bool) (err error) {
|
||||
err = checkListBeforeUpdateOrDelete(s, list)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// We need to specify the cols we want to update here to be able to un-archive lists
|
||||
colsToUpdate := []string{
|
||||
"title",
|
||||
"is_archived",
|
||||
"identifier",
|
||||
"hex_color",
|
||||
"position",
|
||||
}
|
||||
if list.Description != "" {
|
||||
colsToUpdate = append(colsToUpdate, "description")
|
||||
}
|
||||
|
||||
if updateListBackground {
|
||||
colsToUpdate = append(colsToUpdate, "background_file_id")
|
||||
}
|
||||
|
||||
wasFavorite, err := isFavorite(s, list.ID, auth, FavoriteKindList)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if list.IsFavorite && !wasFavorite {
|
||||
if err := addToFavorites(s, list.ID, auth, FavoriteKindList); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !list.IsFavorite && wasFavorite {
|
||||
if err := removeFromFavorite(s, list.ID, auth, FavoriteKindList); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
_, err = s.
|
||||
ID(list.ID).
|
||||
Cols(colsToUpdate...).
|
||||
Update(list)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = events.Dispatch(&ListUpdatedEvent{
|
||||
List: list,
|
||||
Doer: auth,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
l, err := GetListSimpleByID(s, list.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -665,15 +716,7 @@ func (l *List) Update(s *xorm.Session, a web.Auth) (err error) {
|
|||
return nil
|
||||
}
|
||||
|
||||
err = CreateOrUpdateList(s, l, a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return events.Dispatch(&ListUpdatedEvent{
|
||||
List: l,
|
||||
Doer: a,
|
||||
})
|
||||
return UpdateList(s, l, a, false)
|
||||
}
|
||||
|
||||
func updateListLastUpdated(s *xorm.Session, list *List) error {
|
||||
|
@ -706,39 +749,12 @@ func updateListByTaskID(s *xorm.Session, taskID int64) (err error) {
|
|||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /namespaces/{namespaceID}/lists [put]
|
||||
func (l *List) Create(s *xorm.Session, a web.Auth) (err error) {
|
||||
err = l.CheckIsArchived(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
doer, err := user.GetFromAuth(a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
l.OwnerID = doer.ID
|
||||
l.Owner = doer
|
||||
l.ID = 0 // Otherwise only the first time a new list would be created
|
||||
|
||||
err = CreateOrUpdateList(s, l, a)
|
||||
err = CreateList(s, l, a)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Create a new first bucket for this list
|
||||
b := &Bucket{
|
||||
ListID: l.ID,
|
||||
Title: "Backlog",
|
||||
}
|
||||
err = b.Create(s, a)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return events.Dispatch(&ListCreatedEvent{
|
||||
List: l,
|
||||
Doer: doer,
|
||||
})
|
||||
return l.ReadOne(s, a)
|
||||
}
|
||||
|
||||
// Delete implements the delete method of CRUDable
|
||||
|
|
|
@ -75,7 +75,7 @@ func (ld *ListDuplicate) Create(s *xorm.Session, doer web.Auth) (err error) {
|
|||
ld.List.Identifier = "" // Reset the identifier to trigger regenerating a new one
|
||||
// Set the owner to the current user
|
||||
ld.List.OwnerID = doer.GetID()
|
||||
if err := CreateOrUpdateList(s, ld.List, doer); err != nil {
|
||||
if err := CreateList(s, ld.List, doer); err != nil {
|
||||
// If there is no available unique list identifier, just reset it.
|
||||
if IsErrListIdentifierIsNotUnique(err) {
|
||||
ld.List.Identifier = ""
|
||||
|
|
|
@ -50,6 +50,7 @@ func RegisterListeners() {
|
|||
events.RegisterListener((&TaskCommentUpdatedEvent{}).Name(), &HandleTaskCommentEditMentions{})
|
||||
events.RegisterListener((&TaskCreatedEvent{}).Name(), &HandleTaskCreateMentions{})
|
||||
events.RegisterListener((&TaskUpdatedEvent{}).Name(), &HandleTaskUpdatedMentions{})
|
||||
events.RegisterListener((&UserDataExportRequestedEvent{}).Name(), &HandleUserDataExport{})
|
||||
}
|
||||
|
||||
//////
|
||||
|
@ -246,6 +247,11 @@ func (s *SendTaskAssignedNotification) Handle(msg *message.Message) (err error)
|
|||
|
||||
log.Debugf("Sending task assigned notifications to %d subscribers for task %d", len(subscribers), event.Task.ID)
|
||||
|
||||
task, err := GetTaskByIDSimple(sess, event.Task.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, subscriber := range subscribers {
|
||||
if subscriber.UserID == event.Doer.ID {
|
||||
continue
|
||||
|
@ -253,7 +259,7 @@ func (s *SendTaskAssignedNotification) Handle(msg *message.Message) (err error)
|
|||
|
||||
n := &TaskAssignedNotification{
|
||||
Doer: event.Doer,
|
||||
Task: event.Task,
|
||||
Task: &task,
|
||||
Assignee: event.Assignee,
|
||||
}
|
||||
err = notifications.Notify(subscriber.User, n)
|
||||
|
@ -562,3 +568,41 @@ func (s *SendTeamMemberAddedNotification) Handle(msg *message.Message) (err erro
|
|||
Team: event.Team,
|
||||
})
|
||||
}
|
||||
|
||||
// HandleUserDataExport represents a listener
|
||||
type HandleUserDataExport struct {
|
||||
}
|
||||
|
||||
// Name defines the name for the HandleUserDataExport listener
|
||||
func (s *HandleUserDataExport) Name() string {
|
||||
return "handle.user.data.export"
|
||||
}
|
||||
|
||||
// Handle is executed when the event HandleUserDataExport listens on is fired
|
||||
func (s *HandleUserDataExport) Handle(msg *message.Message) (err error) {
|
||||
event := &UserDataExportRequestedEvent{}
|
||||
err = json.Unmarshal(msg.Payload, event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("Starting to export user data for user %d...", event.User.ID)
|
||||
|
||||
sess := db.NewSession()
|
||||
defer sess.Close()
|
||||
err = sess.Begin()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = ExportUserData(sess, event.User)
|
||||
if err != nil {
|
||||
_ = sess.Rollback()
|
||||
return
|
||||
}
|
||||
|
||||
log.Debugf("Done exporting user data for user %d...", event.User.ID)
|
||||
|
||||
err = sess.Commit()
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -187,6 +187,11 @@ type NamespaceWithLists struct {
|
|||
Lists []*List `xorm:"-" json:"lists"`
|
||||
}
|
||||
|
||||
type NamespaceWithListsAndTasks struct {
|
||||
Namespace
|
||||
Lists []*ListWithTasksAndBuckets `xorm:"-" json:"lists"`
|
||||
}
|
||||
|
||||
func makeNamespaceSlice(namespaces map[int64]*NamespaceWithLists, userMap map[int64]*user.User, subscriptions map[int64]*Subscription) []*NamespaceWithLists {
|
||||
all := make([]*NamespaceWithLists, 0, len(namespaces))
|
||||
for _, n := range namespaces {
|
||||
|
|
|
@ -112,7 +112,7 @@ type TaskAssignedNotification struct {
|
|||
func (n *TaskAssignedNotification) ToMail() *notifications.Mail {
|
||||
return notifications.NewMail().
|
||||
Subject(n.Task.Title+"("+n.Task.GetFullIdentifier()+")"+" has been assigned to "+n.Assignee.GetName()).
|
||||
Line(n.Doer.GetName()+" has assigned this task to "+n.Assignee.GetName()).
|
||||
Line(n.Doer.GetName()+" has assigned this task to "+n.Assignee.GetName()+".").
|
||||
Action("View Task", n.Task.GetFrontendURL())
|
||||
}
|
||||
|
||||
|
@ -302,3 +302,29 @@ func (n *UserMentionedInTaskNotification) ToDB() interface{} {
|
|||
func (n *UserMentionedInTaskNotification) Name() string {
|
||||
return "task.mentioned"
|
||||
}
|
||||
|
||||
// DataExportReadyNotification represents a DataExportReadyNotification notification
|
||||
type DataExportReadyNotification struct {
|
||||
User *user.User `json:"user"`
|
||||
}
|
||||
|
||||
// ToMail returns the mail notification for DataExportReadyNotification
|
||||
func (n *DataExportReadyNotification) ToMail() *notifications.Mail {
|
||||
return notifications.NewMail().
|
||||
Subject("Your Vikunja Data Export is ready").
|
||||
Greeting("Hi "+n.User.GetName()+",").
|
||||
Line("Your Vikunja Data Export is ready for you to download. Click the button below to download it:").
|
||||
Action("Download", config.ServiceFrontendurl.GetString()+"user/export/download").
|
||||
Line("The download will be available for the next 7 days.").
|
||||
Line("Have a nice day!")
|
||||
}
|
||||
|
||||
// ToDB returns the DataExportReadyNotification notification in a format which can be saved in the db
|
||||
func (n *DataExportReadyNotification) ToDB() interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Name returns the name of the notification
|
||||
func (n *DataExportReadyNotification) Name() string {
|
||||
return "data.export.ready"
|
||||
}
|
||||
|
|
|
@ -149,6 +149,11 @@ func (tf *TaskCollection) ReadAll(s *xorm.Session, a web.Auth, search string, pa
|
|||
return nil, 0, 0, err
|
||||
}
|
||||
|
||||
sf.Filters.SortByArr = tf.SortByArr
|
||||
sf.Filters.SortBy = tf.SortBy
|
||||
sf.Filters.OrderByArr = tf.OrderByArr
|
||||
sf.Filters.OrderBy = tf.OrderBy
|
||||
|
||||
return sf.getTaskCollection().ReadAll(s, a, search, page, perPage)
|
||||
}
|
||||
|
||||
|
|
|
@ -1081,6 +1081,24 @@ func TestTaskCollection_ReadAll(t *testing.T) {
|
|||
task2,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "saved filter with sort order",
|
||||
fields: fields{
|
||||
ListID: -2,
|
||||
SortBy: []string{"title", "id"},
|
||||
OrderBy: []string{"desc", "asc"},
|
||||
},
|
||||
args: args{
|
||||
a: &user.User{ID: 1},
|
||||
},
|
||||
want: []*Task{
|
||||
task9,
|
||||
task8,
|
||||
task7,
|
||||
task6,
|
||||
task5,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
|
|
@ -129,6 +129,11 @@ type Task struct {
|
|||
web.Rights `xorm:"-" json:"-"`
|
||||
}
|
||||
|
||||
type TaskWithComments struct {
|
||||
Task
|
||||
Comments []*TaskComment `xorm:"-" json:"comments"`
|
||||
}
|
||||
|
||||
// TableName returns the table name for listtasks
|
||||
func (Task) TableName() string {
|
||||
return "tasks"
|
||||
|
|
|
@ -54,9 +54,13 @@ func deleteUsers() {
|
|||
return
|
||||
}
|
||||
|
||||
log.Debugf("Found %d users scheduled for deletion", len(users))
|
||||
|
||||
now := time.Now()
|
||||
|
||||
for _, u := range users {
|
||||
if u.DeletionScheduledAt.Before(time.Now()) {
|
||||
log.Debugf("User %d is not yet scheduled for deletion.", u.ID)
|
||||
if u.DeletionScheduledAt.Before(now) {
|
||||
log.Debugf("User %d is not yet scheduled for deletion. Scheduled at %s, now is %s", u.ID, u.DeletionScheduledAt, now)
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -73,6 +77,8 @@ func deleteUsers() {
|
|||
return
|
||||
}
|
||||
|
||||
log.Debugf("Deleted user %d", u.ID)
|
||||
|
||||
err = s.Commit()
|
||||
if err != nil {
|
||||
log.Errorf("Could not commit transaction: %s", err)
|
||||
|
@ -167,7 +173,9 @@ func DeleteUser(s *xorm.Session, u *user.User) (err error) {
|
|||
return err
|
||||
}
|
||||
|
||||
return notifications.Notify(u, &user.AccountDeletedNotification{})
|
||||
return notifications.Notify(u, &user.AccountDeletedNotification{
|
||||
User: u,
|
||||
})
|
||||
}
|
||||
|
||||
func ensureNamespaceAdminUser(s *xorm.Session, n *Namespace) (hadUsers bool, err error) {
|
||||
|
|
|
@ -24,6 +24,7 @@ import (
|
|||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"code.vikunja.io/web"
|
||||
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
@ -51,18 +52,22 @@ func NewUserAuthTokenResponse(u *user.User, c echo.Context) error {
|
|||
}
|
||||
|
||||
// NewUserJWTAuthtoken generates and signes a new jwt token for a user. This is a global function to be able to call it from integration tests.
|
||||
func NewUserJWTAuthtoken(user *user.User) (token string, err error) {
|
||||
func NewUserJWTAuthtoken(u *user.User) (token string, err error) {
|
||||
t := jwt.New(jwt.SigningMethodHS256)
|
||||
|
||||
var ttl = time.Duration(config.ServiceJWTTTL.GetInt64())
|
||||
var exp = time.Now().Add(time.Second * ttl).Unix()
|
||||
|
||||
// Set claims
|
||||
claims := t.Claims.(jwt.MapClaims)
|
||||
claims["type"] = AuthTypeUser
|
||||
claims["id"] = user.ID
|
||||
claims["username"] = user.Username
|
||||
claims["email"] = user.Email
|
||||
claims["exp"] = time.Now().Add(time.Hour * 72).Unix()
|
||||
claims["name"] = user.Name
|
||||
claims["emailRemindersEnabled"] = user.EmailRemindersEnabled
|
||||
claims["id"] = u.ID
|
||||
claims["username"] = u.Username
|
||||
claims["email"] = u.Email
|
||||
claims["exp"] = exp
|
||||
claims["name"] = u.Name
|
||||
claims["emailRemindersEnabled"] = u.EmailRemindersEnabled
|
||||
claims["isLocalUser"] = u.Issuer == user.IssuerLocal
|
||||
|
||||
// Generate encoded token and send it as response.
|
||||
return t.SignedString([]byte(config.ServiceJWTSecret.GetString()))
|
||||
|
@ -72,6 +77,9 @@ func NewUserJWTAuthtoken(user *user.User) (token string, err error) {
|
|||
func NewLinkShareJWTAuthtoken(share *models.LinkSharing) (token string, err error) {
|
||||
t := jwt.New(jwt.SigningMethodHS256)
|
||||
|
||||
var ttl = time.Duration(config.ServiceJWTTTL.GetInt64())
|
||||
var exp = time.Now().Add(time.Second * ttl).Unix()
|
||||
|
||||
// Set claims
|
||||
claims := t.Claims.(jwt.MapClaims)
|
||||
claims["type"] = AuthTypeLinkShare
|
||||
|
@ -80,7 +88,8 @@ func NewLinkShareJWTAuthtoken(share *models.LinkSharing) (token string, err erro
|
|||
claims["list_id"] = share.ListID
|
||||
claims["right"] = share.Right
|
||||
claims["sharedByID"] = share.SharedByID
|
||||
claims["exp"] = time.Now().Add(time.Hour * 72).Unix()
|
||||
claims["exp"] = exp
|
||||
claims["isLocalUser"] = true // Link shares are always local
|
||||
|
||||
// Generate encoded token and send it as response.
|
||||
return t.SignedString([]byte(config.ServiceJWTSecret.GetString()))
|
||||
|
|
|
@ -21,6 +21,7 @@ import (
|
|||
"io/ioutil"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
|
@ -64,7 +65,7 @@ func (g *Provider) GetAvatar(user *user.User, size int64) ([]byte, string, error
|
|||
}
|
||||
if !exists || needsRefetch {
|
||||
log.Debugf("Gravatar for user %d with size %d not cached, requesting from gravatar...", user.ID, size)
|
||||
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "https://www.gravatar.com/avatar/"+utils.Md5String(user.Email)+"?s="+sizeString+"&d=mp", nil)
|
||||
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "https://www.gravatar.com/avatar/"+utils.Md5String(strings.ToLower(user.Email))+"?s="+sizeString+"&d=mp", nil)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
|
|
@ -300,7 +300,7 @@ func RemoveListBackground(c echo.Context) error {
|
|||
|
||||
list.BackgroundFileID = 0
|
||||
list.BackgroundInformation = nil
|
||||
err = list.Update(s, auth)
|
||||
err = models.UpdateList(s, list, auth, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -163,14 +163,14 @@ func (p *Provider) Search(s *xorm.Session, search string, page int64) (result []
|
|||
}
|
||||
|
||||
if existsForPage {
|
||||
log.Debugf("Serving initial unsplash collection for page %d from cache, last updated at %v", page, emptySearchResult.lastCached)
|
||||
log.Debugf("Serving initial wallpaper topic from unsplash for page %d from cache, last updated at %v", page, emptySearchResult.lastCached)
|
||||
return emptySearchResult.images[page], nil
|
||||
}
|
||||
|
||||
log.Debugf("Retrieving initial unsplash collection for page %d from unsplash api", page)
|
||||
log.Debugf("Retrieving initial wallpaper topic from unsplash for page %d from unsplash api", page)
|
||||
|
||||
collectionResult := []*Photo{}
|
||||
err = doGet("collections/317099/photos?page="+strconv.FormatInt(page, 10)+"&per_page=25&order_by=latest", &collectionResult)
|
||||
err = doGet("topics/wallpapers/photos?page="+strconv.FormatInt(page, 10)+"&per_page=25&order_by=latest", &collectionResult)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -21,19 +21,15 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"code.vikunja.io/api/pkg/db"
|
||||
"code.vikunja.io/api/pkg/files"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/utils"
|
||||
"code.vikunja.io/api/pkg/version"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// Change to deflate to gain better compression
|
||||
// see http://golang.org/pkg/archive/zip/#pkg-constants
|
||||
const compressionUsed = zip.Deflate
|
||||
|
||||
// Dump creates a zip file with all vikunja files at filename
|
||||
func Dump(filename string) error {
|
||||
dumpFile, err := os.Create(filename)
|
||||
|
@ -55,7 +51,7 @@ func Dump(filename string) error {
|
|||
|
||||
// Version
|
||||
log.Info("Start dumping version file...")
|
||||
err = writeBytesToZip("VERSION", []byte(version.Version), dumpWriter)
|
||||
err = utils.WriteBytesToZip("VERSION", []byte(version.Version), dumpWriter)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error saving version: %s", err)
|
||||
}
|
||||
|
@ -68,7 +64,7 @@ func Dump(filename string) error {
|
|||
return fmt.Errorf("error saving database data: %s", err)
|
||||
}
|
||||
for t, d := range data {
|
||||
err = writeBytesToZip("database/"+t+".json", d, dumpWriter)
|
||||
err = utils.WriteBytesToZip("database/"+t+".json", d, dumpWriter)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error writing database table %s: %s", t, err)
|
||||
}
|
||||
|
@ -81,21 +77,12 @@ func Dump(filename string) error {
|
|||
if err != nil {
|
||||
return fmt.Errorf("error saving file: %s", err)
|
||||
}
|
||||
for fid, file := range allFiles {
|
||||
header := &zip.FileHeader{
|
||||
Name: "files/" + strconv.FormatInt(fid, 10),
|
||||
Method: compressionUsed,
|
||||
}
|
||||
w, err := dumpWriter.CreateHeader(header)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = io.Copy(w, file)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error writing file %d: %s", fid, err)
|
||||
}
|
||||
_ = file.Close()
|
||||
|
||||
err = utils.WriteFilesToZip(allFiles, dumpWriter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infof("Dumped files")
|
||||
|
||||
log.Info("Done creating dump")
|
||||
|
@ -123,7 +110,7 @@ func writeFileToZip(filename string, writer *zip.Writer) error {
|
|||
}
|
||||
|
||||
header.Name = info.Name()
|
||||
header.Method = compressionUsed
|
||||
header.Method = utils.CompressionUsed
|
||||
|
||||
w, err := writer.CreateHeader(header)
|
||||
if err != nil {
|
||||
|
@ -132,16 +119,3 @@ func writeFileToZip(filename string, writer *zip.Writer) error {
|
|||
_, err = io.Copy(w, fileToZip)
|
||||
return err
|
||||
}
|
||||
|
||||
func writeBytesToZip(filename string, data []byte, writer *zip.Writer) (err error) {
|
||||
header := &zip.FileHeader{
|
||||
Name: filename,
|
||||
Method: compressionUsed,
|
||||
}
|
||||
w, err := writer.CreateHeader(header)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = w.Write(data)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ import (
|
|||
"code.vikunja.io/api/pkg/initialize"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/migration"
|
||||
|
||||
"src.techknowlogick.com/xormigrate"
|
||||
)
|
||||
|
||||
|
@ -194,6 +195,7 @@ func Restore(filename string) error {
|
|||
///////
|
||||
// Done
|
||||
log.Infof("Done restoring dump.")
|
||||
log.Infof("Restart Vikunja to make sure the new configuration file is applied.")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ import (
|
|||
|
||||
// InsertFromStructure takes a fully nested Vikunja data structure and a user and then creates everything for this user
|
||||
// (Namespaces, tasks, etc. Even attachments and relations.)
|
||||
func InsertFromStructure(str []*models.NamespaceWithLists, user *user.User) (err error) {
|
||||
func InsertFromStructure(str []*models.NamespaceWithListsAndTasks, user *user.User) (err error) {
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
|
@ -45,7 +45,7 @@ func InsertFromStructure(str []*models.NamespaceWithLists, user *user.User) (err
|
|||
return s.Commit()
|
||||
}
|
||||
|
||||
func insertFromStructure(s *xorm.Session, str []*models.NamespaceWithLists, user *user.User) (err error) {
|
||||
func insertFromStructure(s *xorm.Session, str []*models.NamespaceWithListsAndTasks, user *user.User) (err error) {
|
||||
|
||||
log.Debugf("[creating structure] Creating %d namespaces", len(str))
|
||||
|
||||
|
@ -129,7 +129,7 @@ func insertFromStructure(s *xorm.Session, str []*models.NamespaceWithLists, user
|
|||
|
||||
// Create all tasks
|
||||
for _, t := range tasks {
|
||||
setBucketOrDefault(t)
|
||||
setBucketOrDefault(&t.Task)
|
||||
|
||||
t.ListID = l.ID
|
||||
err = t.Create(s, user)
|
||||
|
@ -221,6 +221,15 @@ func insertFromStructure(s *xorm.Session, str []*models.NamespaceWithLists, user
|
|||
}
|
||||
log.Debugf("[creating structure] Associated task %d with label %d", t.ID, lb.ID)
|
||||
}
|
||||
|
||||
for _, comment := range t.Comments {
|
||||
comment.TaskID = t.ID
|
||||
err = comment.Create(s, user)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
log.Debugf("[creating structure] Created new comment %d", comment.ID)
|
||||
}
|
||||
}
|
||||
|
||||
// All tasks brought their own bucket with them, therefore the newly created default bucket is just extra space
|
||||
|
|
|
@ -32,79 +32,95 @@ func TestInsertFromStructure(t *testing.T) {
|
|||
}
|
||||
t.Run("normal", func(t *testing.T) {
|
||||
db.LoadAndAssertFixtures(t)
|
||||
testStructure := []*models.NamespaceWithLists{
|
||||
testStructure := []*models.NamespaceWithListsAndTasks{
|
||||
{
|
||||
Namespace: models.Namespace{
|
||||
Title: "Test1",
|
||||
Description: "Lorem Ipsum",
|
||||
},
|
||||
Lists: []*models.List{
|
||||
Lists: []*models.ListWithTasksAndBuckets{
|
||||
{
|
||||
Title: "Testlist1",
|
||||
Description: "Something",
|
||||
List: models.List{
|
||||
Title: "Testlist1",
|
||||
Description: "Something",
|
||||
},
|
||||
Buckets: []*models.Bucket{
|
||||
{
|
||||
ID: 1234,
|
||||
Title: "Test Bucket",
|
||||
},
|
||||
},
|
||||
Tasks: []*models.Task{
|
||||
Tasks: []*models.TaskWithComments{
|
||||
{
|
||||
Title: "Task1",
|
||||
Description: "Lorem",
|
||||
Task: models.Task{
|
||||
Title: "Task1",
|
||||
Description: "Lorem",
|
||||
},
|
||||
},
|
||||
{
|
||||
Title: "Task with related tasks",
|
||||
RelatedTasks: map[models.RelationKind][]*models.Task{
|
||||
models.RelationKindSubtask: {
|
||||
Task: models.Task{
|
||||
Title: "Task with related tasks",
|
||||
RelatedTasks: map[models.RelationKind][]*models.Task{
|
||||
models.RelationKindSubtask: {
|
||||
{
|
||||
Title: "Related to task with related task",
|
||||
Description: "As subtask",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Task: models.Task{
|
||||
Title: "Task with attachments",
|
||||
Attachments: []*models.TaskAttachment{
|
||||
{
|
||||
Title: "Related to task with related task",
|
||||
Description: "As subtask",
|
||||
File: &files.File{
|
||||
Name: "testfile",
|
||||
Size: 4,
|
||||
FileContent: []byte{1, 2, 3, 4},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Title: "Task with attachments",
|
||||
Attachments: []*models.TaskAttachment{
|
||||
{
|
||||
File: &files.File{
|
||||
Name: "testfile",
|
||||
Size: 4,
|
||||
FileContent: []byte{1, 2, 3, 4},
|
||||
Task: models.Task{
|
||||
Title: "Task with labels",
|
||||
Labels: []*models.Label{
|
||||
{
|
||||
Title: "Label1",
|
||||
HexColor: "ff00ff",
|
||||
},
|
||||
{
|
||||
Title: "Label2",
|
||||
HexColor: "ff00ff",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Title: "Task with labels",
|
||||
Labels: []*models.Label{
|
||||
{
|
||||
Title: "Label1",
|
||||
HexColor: "ff00ff",
|
||||
},
|
||||
{
|
||||
Title: "Label2",
|
||||
HexColor: "ff00ff",
|
||||
Task: models.Task{
|
||||
Title: "Task with same label",
|
||||
Labels: []*models.Label{
|
||||
{
|
||||
Title: "Label1",
|
||||
HexColor: "ff00ff",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Title: "Task with same label",
|
||||
Labels: []*models.Label{
|
||||
{
|
||||
Title: "Label1",
|
||||
HexColor: "ff00ff",
|
||||
},
|
||||
Task: models.Task{
|
||||
Title: "Task in a bucket",
|
||||
BucketID: 1234,
|
||||
},
|
||||
},
|
||||
{
|
||||
Title: "Task in a bucket",
|
||||
BucketID: 1234,
|
||||
},
|
||||
{
|
||||
Title: "Task in a nonexisting bucket",
|
||||
BucketID: 1111,
|
||||
Task: models.Task{
|
||||
Title: "Task in a nonexisting bucket",
|
||||
BucketID: 1111,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public Licensee as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public Licensee
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"code.vikunja.io/api/pkg/modules/migration"
|
||||
user2 "code.vikunja.io/api/pkg/user"
|
||||
"code.vikunja.io/web/handler"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
func status(ms migration.MigratorName, c echo.Context) error {
|
||||
user, err := user2.GetCurrentUser(c)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err, c)
|
||||
}
|
||||
|
||||
status, err := migration.GetMigrationStatus(ms, user)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err, c)
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, status)
|
||||
}
|
|
@ -84,15 +84,5 @@ func (mw *MigrationWeb) Migrate(c echo.Context) error {
|
|||
func (mw *MigrationWeb) Status(c echo.Context) error {
|
||||
ms := mw.MigrationStruct()
|
||||
|
||||
user, err := user2.GetCurrentUser(c)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err, c)
|
||||
}
|
||||
|
||||
status, err := migration.GetMigrationStatus(ms, user)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err, c)
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, status)
|
||||
return status(ms, c)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
// 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 handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/api/pkg/modules/migration"
|
||||
user2 "code.vikunja.io/api/pkg/user"
|
||||
"code.vikunja.io/web/handler"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
type FileMigratorWeb struct {
|
||||
MigrationStruct func() migration.FileMigrator
|
||||
}
|
||||
|
||||
// RegisterRoutes registers all routes for migration
|
||||
func (fw *FileMigratorWeb) RegisterRoutes(g *echo.Group) {
|
||||
ms := fw.MigrationStruct()
|
||||
g.GET("/"+ms.Name()+"/status", fw.Status)
|
||||
g.PUT("/"+ms.Name()+"/migrate", fw.Migrate)
|
||||
}
|
||||
|
||||
// Migrate calls the migration method
|
||||
func (fw *FileMigratorWeb) Migrate(c echo.Context) error {
|
||||
ms := fw.MigrationStruct()
|
||||
|
||||
// Get the user from context
|
||||
user, err := user2.GetCurrentUser(c)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err, c)
|
||||
}
|
||||
|
||||
file, err := c.FormFile("import")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
src, err := file.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
// Do the migration
|
||||
err = ms.Migrate(user, src, file.Size)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err, c)
|
||||
}
|
||||
|
||||
err = migration.SetMigrationStatus(ms, user)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err, c)
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, models.Message{Message: "Everything was migrated successfully."})
|
||||
}
|
||||
|
||||
// Status returns whether or not a user has already done this migration
|
||||
func (fw *FileMigratorWeb) Status(c echo.Context) error {
|
||||
ms := fw.MigrationStruct()
|
||||
|
||||
return status(ms, c)
|
||||
}
|
|
@ -26,10 +26,22 @@ import (
|
|||
|
||||
// DownloadFile downloads a file and returns its contents
|
||||
func DownloadFile(url string) (buf *bytes.Buffer, err error) {
|
||||
return DownloadFileWithHeaders(url, nil)
|
||||
}
|
||||
|
||||
// DownloadFileWithHeaders downloads a file and allows you to pass in headers
|
||||
func DownloadFileWithHeaders(url string, headers http.Header) (buf *bytes.Buffer, err error) {
|
||||
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for key, h := range headers {
|
||||
for _, hh := range h {
|
||||
req.Header.Add(key, hh)
|
||||
}
|
||||
}
|
||||
|
||||
hc := http.Client{}
|
||||
resp, err := hc.Do(req)
|
||||
if err != nil {
|
||||
|
@ -38,6 +50,7 @@ func DownloadFile(url string) (buf *bytes.Buffer, err error) {
|
|||
defer resp.Body.Close()
|
||||
buf = &bytes.Buffer{}
|
||||
_, err = buf.ReadFrom(resp.Body)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -243,15 +243,15 @@ func getMicrosoftTodoData(token string) (microsoftTodoData []*list, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
func convertMicrosoftTodoData(todoData []*list) (vikunjsStructure []*models.NamespaceWithLists, err error) {
|
||||
func convertMicrosoftTodoData(todoData []*list) (vikunjsStructure []*models.NamespaceWithListsAndTasks, err error) {
|
||||
|
||||
// One namespace with all lists
|
||||
vikunjsStructure = []*models.NamespaceWithLists{
|
||||
vikunjsStructure = []*models.NamespaceWithListsAndTasks{
|
||||
{
|
||||
Namespace: models.Namespace{
|
||||
Title: "Migrated from Microsoft Todo",
|
||||
},
|
||||
Lists: []*models.List{},
|
||||
Lists: []*models.ListWithTasksAndBuckets{},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -262,8 +262,10 @@ func convertMicrosoftTodoData(todoData []*list) (vikunjsStructure []*models.Name
|
|||
log.Debugf("[Microsoft Todo Migration] Converting list %s", l.ID)
|
||||
|
||||
// Lists only with title
|
||||
list := &models.List{
|
||||
Title: l.DisplayName,
|
||||
list := &models.ListWithTasksAndBuckets{
|
||||
List: models.List{
|
||||
Title: l.DisplayName,
|
||||
},
|
||||
}
|
||||
|
||||
log.Debugf("[Microsoft Todo Migration] Converting %d tasks", len(l.Tasks))
|
||||
|
@ -340,7 +342,7 @@ func convertMicrosoftTodoData(todoData []*list) (vikunjsStructure []*models.Name
|
|||
}
|
||||
}
|
||||
|
||||
list.Tasks = append(list.Tasks, task)
|
||||
list.Tasks = append(list.Tasks, &models.TaskWithComments{Task: *task})
|
||||
log.Debugf("[Microsoft Todo Migration] Done converted %d tasks", len(l.Tasks))
|
||||
}
|
||||
|
||||
|
|
|
@ -102,57 +102,79 @@ func TestConverting(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
expectedHierachie := []*models.NamespaceWithLists{
|
||||
expectedHierachie := []*models.NamespaceWithListsAndTasks{
|
||||
{
|
||||
Namespace: models.Namespace{
|
||||
Title: "Migrated from Microsoft Todo",
|
||||
},
|
||||
Lists: []*models.List{
|
||||
Lists: []*models.ListWithTasksAndBuckets{
|
||||
{
|
||||
Title: "List 1",
|
||||
Tasks: []*models.Task{
|
||||
List: models.List{
|
||||
Title: "List 1",
|
||||
},
|
||||
Tasks: []*models.TaskWithComments{
|
||||
{
|
||||
Title: "Task 1",
|
||||
Description: "This is a description",
|
||||
},
|
||||
{
|
||||
Title: "Task 2",
|
||||
Done: true,
|
||||
DoneAt: testtimeTime,
|
||||
},
|
||||
{
|
||||
Title: "Task 3",
|
||||
Priority: 1,
|
||||
},
|
||||
{
|
||||
Title: "Task 4",
|
||||
Priority: 3,
|
||||
},
|
||||
{
|
||||
Title: "Task 5",
|
||||
Reminders: []time.Time{
|
||||
testtimeTime,
|
||||
Task: models.Task{
|
||||
Title: "Task 1",
|
||||
Description: "This is a description",
|
||||
},
|
||||
},
|
||||
{
|
||||
Title: "Task 6",
|
||||
DueDate: testtimeTime,
|
||||
Task: models.Task{
|
||||
Title: "Task 2",
|
||||
Done: true,
|
||||
DoneAt: testtimeTime,
|
||||
},
|
||||
},
|
||||
{
|
||||
Title: "Task 7",
|
||||
DueDate: testtimeTime,
|
||||
RepeatAfter: 60 * 60 * 24 * 7, // The amount of seconds in a week
|
||||
Task: models.Task{
|
||||
Title: "Task 3",
|
||||
Priority: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
Task: models.Task{
|
||||
Title: "Task 4",
|
||||
Priority: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
Task: models.Task{
|
||||
Title: "Task 5",
|
||||
Reminders: []time.Time{
|
||||
testtimeTime,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Task: models.Task{
|
||||
Title: "Task 6",
|
||||
DueDate: testtimeTime,
|
||||
},
|
||||
},
|
||||
{
|
||||
Task: models.Task{
|
||||
Title: "Task 7",
|
||||
DueDate: testtimeTime,
|
||||
RepeatAfter: 60 * 60 * 24 * 7, // The amount of seconds in a week
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Title: "List 2",
|
||||
Tasks: []*models.Task{
|
||||
List: models.List{
|
||||
Title: "List 2",
|
||||
},
|
||||
Tasks: []*models.TaskWithComments{
|
||||
{
|
||||
Title: "Task 1",
|
||||
Task: models.Task{
|
||||
Title: "Task 1",
|
||||
},
|
||||
},
|
||||
{
|
||||
Title: "Task 2",
|
||||
Task: models.Task{
|
||||
Title: "Task 2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -37,7 +37,7 @@ func (s *Status) TableName() string {
|
|||
}
|
||||
|
||||
// SetMigrationStatus sets the migration status for a user
|
||||
func SetMigrationStatus(m Migrator, u *user.User) (err error) {
|
||||
func SetMigrationStatus(m MigratorName, u *user.User) (err error) {
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
|
@ -50,7 +50,7 @@ func SetMigrationStatus(m Migrator, u *user.User) (err error) {
|
|||
}
|
||||
|
||||
// GetMigrationStatus returns the migration status for a migration and a user
|
||||
func GetMigrationStatus(m Migrator, u *user.User) (status *Status, err error) {
|
||||
func GetMigrationStatus(m MigratorName, u *user.User) (status *Status, err error) {
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
|
|
|
@ -17,11 +17,20 @@
|
|||
package migration
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
)
|
||||
|
||||
type MigratorName interface {
|
||||
// Name holds the name of the migration.
|
||||
// This is used to show the name to users and to keep track of users who already migrated.
|
||||
Name() string
|
||||
}
|
||||
|
||||
// Migrator is the basic migrator interface which is shared among all migrators
|
||||
type Migrator interface {
|
||||
MigratorName
|
||||
// Migrate is the interface used to migrate a user's tasks from another platform to vikunja.
|
||||
// The user object is the user who's tasks will be migrated.
|
||||
Migrate(user *user.User) error
|
||||
|
@ -29,7 +38,12 @@ type Migrator interface {
|
|||
// The use case for this are Oauth flows, where the server token should remain hidden and not
|
||||
// known to the frontend.
|
||||
AuthURL() string
|
||||
// Title holds the name of the migration.
|
||||
// This is used to show the name to users and to keep track of users who already migrated.
|
||||
Name() string
|
||||
}
|
||||
|
||||
// FileMigrator handles importing Vikunja data from a file. The implementation of it determines the format.
|
||||
type FileMigrator interface {
|
||||
MigratorName
|
||||
// Migrate is the interface used to migrate a user's tasks, list and other things from a file to vikunja.
|
||||
// The user object is the user who's tasks will be migrated.
|
||||
Migrate(user *user.User, file io.ReaderAt, size int64) error
|
||||
}
|
||||
|
|
|
@ -252,28 +252,30 @@ func parseDate(dateString string) (date time.Time, err error) {
|
|||
return date, err
|
||||
}
|
||||
|
||||
func convertTodoistToVikunja(sync *sync, doneItems map[int64]*doneItem) (fullVikunjaHierachie []*models.NamespaceWithLists, err error) {
|
||||
func convertTodoistToVikunja(sync *sync, doneItems map[int64]*doneItem) (fullVikunjaHierachie []*models.NamespaceWithListsAndTasks, err error) {
|
||||
|
||||
newNamespace := &models.NamespaceWithLists{
|
||||
newNamespace := &models.NamespaceWithListsAndTasks{
|
||||
Namespace: models.Namespace{
|
||||
Title: "Migrated from todoist",
|
||||
},
|
||||
}
|
||||
|
||||
// A map for all vikunja lists with the project id they're coming from as key
|
||||
lists := make(map[int64]*models.List, 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[int64]*models.Task, 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[int64]*models.Label, len(sync.Labels))
|
||||
|
||||
for _, p := range sync.Projects {
|
||||
list := &models.List{
|
||||
Title: p.Name,
|
||||
HexColor: todoistColors[p.Color],
|
||||
IsArchived: p.IsArchived == 1,
|
||||
list := &models.ListWithTasksAndBuckets{
|
||||
List: models.List{
|
||||
Title: p.Name,
|
||||
HexColor: todoistColors[p.Color],
|
||||
IsArchived: p.IsArchived == 1,
|
||||
},
|
||||
}
|
||||
|
||||
lists[p.ID] = list
|
||||
|
@ -305,11 +307,13 @@ func convertTodoistToVikunja(sync *sync, doneItems map[int64]*doneItem) (fullVik
|
|||
}
|
||||
|
||||
for _, i := range sync.Items {
|
||||
task := &models.Task{
|
||||
Title: i.Content,
|
||||
Created: i.DateAdded.In(config.GetTimeZone()),
|
||||
Done: i.Checked == 1,
|
||||
BucketID: i.SectionID,
|
||||
task := &models.TaskWithComments{
|
||||
Task: models.Task{
|
||||
Title: i.Content,
|
||||
Created: i.DateAdded.In(config.GetTimeZone()),
|
||||
Done: i.Checked == 1,
|
||||
BucketID: i.SectionID,
|
||||
},
|
||||
}
|
||||
|
||||
// Only try to parse the task done at date if the task is actually done
|
||||
|
@ -365,7 +369,7 @@ func convertTodoistToVikunja(sync *sync, doneItems map[int64]*doneItem) (fullVik
|
|||
tasks[i.ParentID].RelatedTasks = make(models.RelatedTaskMap)
|
||||
}
|
||||
|
||||
tasks[i.ParentID].RelatedTasks[models.RelationKindSubtask] = append(tasks[i.ParentID].RelatedTasks[models.RelationKindSubtask], tasks[i.ID])
|
||||
tasks[i.ParentID].RelatedTasks[models.RelationKindSubtask] = append(tasks[i.ParentID].RelatedTasks[models.RelationKindSubtask], &tasks[i.ID].Task)
|
||||
|
||||
// Remove the task from the top level structure, otherwise it is added twice
|
||||
outer:
|
||||
|
@ -449,7 +453,7 @@ func convertTodoistToVikunja(sync *sync, doneItems map[int64]*doneItem) (fullVik
|
|||
tasks[r.ItemID].Reminders = append(tasks[r.ItemID].Reminders, date.In(config.GetTimeZone()))
|
||||
}
|
||||
|
||||
return []*models.NamespaceWithLists{
|
||||
return []*models.NamespaceWithListsAndTasks{
|
||||
newNamespace,
|
||||
}, err
|
||||
}
|
||||
|
|
|
@ -375,210 +375,258 @@ func TestConvertTodoistToVikunja(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
expectedHierachie := []*models.NamespaceWithLists{
|
||||
expectedHierachie := []*models.NamespaceWithListsAndTasks{
|
||||
{
|
||||
Namespace: models.Namespace{
|
||||
Title: "Migrated from todoist",
|
||||
},
|
||||
Lists: []*models.List{
|
||||
Lists: []*models.ListWithTasksAndBuckets{
|
||||
{
|
||||
Title: "Project1",
|
||||
Description: "Lorem Ipsum dolor sit amet\nLorem Ipsum dolor sit amet 2\nLorem Ipsum dolor sit amet 3",
|
||||
HexColor: todoistColors[30],
|
||||
List: models.List{
|
||||
Title: "Project1",
|
||||
Description: "Lorem Ipsum dolor sit amet\nLorem Ipsum dolor sit amet 2\nLorem Ipsum dolor sit amet 3",
|
||||
HexColor: todoistColors[30],
|
||||
},
|
||||
Buckets: []*models.Bucket{
|
||||
{
|
||||
ID: 1234,
|
||||
Title: "Some Bucket",
|
||||
},
|
||||
},
|
||||
Tasks: []*models.Task{
|
||||
Tasks: []*models.TaskWithComments{
|
||||
{
|
||||
Title: "Task400000000",
|
||||
Description: "Lorem Ipsum dolor sit amet",
|
||||
Done: false,
|
||||
Created: time1,
|
||||
Reminders: []time.Time{
|
||||
time.Date(2020, time.June, 15, 0, 0, 0, 0, time.UTC).In(config.GetTimeZone()),
|
||||
time.Date(2020, time.June, 16, 7, 0, 0, 0, time.UTC).In(config.GetTimeZone()),
|
||||
Task: models.Task{
|
||||
Title: "Task400000000",
|
||||
Description: "Lorem Ipsum dolor sit amet",
|
||||
Done: false,
|
||||
Created: time1,
|
||||
Reminders: []time.Time{
|
||||
time.Date(2020, time.June, 15, 0, 0, 0, 0, time.UTC).In(config.GetTimeZone()),
|
||||
time.Date(2020, time.June, 16, 7, 0, 0, 0, time.UTC).In(config.GetTimeZone()),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Title: "Task400000001",
|
||||
Description: "Lorem Ipsum dolor sit amet",
|
||||
Done: false,
|
||||
Created: time1,
|
||||
},
|
||||
{
|
||||
Title: "Task400000002",
|
||||
Done: false,
|
||||
Created: time1,
|
||||
Reminders: []time.Time{
|
||||
time.Date(2020, time.July, 15, 7, 0, 0, 0, time.UTC).In(config.GetTimeZone()),
|
||||
Task: models.Task{
|
||||
Title: "Task400000001",
|
||||
Description: "Lorem Ipsum dolor sit amet",
|
||||
Done: false,
|
||||
Created: time1,
|
||||
},
|
||||
},
|
||||
{
|
||||
Title: "Task400000003",
|
||||
Description: "Lorem Ipsum dolor sit amet",
|
||||
Done: true,
|
||||
DueDate: dueTime,
|
||||
Created: time1,
|
||||
DoneAt: time3,
|
||||
Labels: vikunjaLabels,
|
||||
Reminders: []time.Time{
|
||||
time.Date(2020, time.June, 15, 7, 0, 0, 0, time.UTC).In(config.GetTimeZone()),
|
||||
Task: models.Task{
|
||||
Title: "Task400000002",
|
||||
Done: false,
|
||||
Created: time1,
|
||||
Reminders: []time.Time{
|
||||
time.Date(2020, time.July, 15, 7, 0, 0, 0, time.UTC).In(config.GetTimeZone()),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Title: "Task400000004",
|
||||
Done: false,
|
||||
Created: time1,
|
||||
Labels: vikunjaLabels,
|
||||
},
|
||||
{
|
||||
Title: "Task400000005",
|
||||
Done: true,
|
||||
DueDate: dueTime,
|
||||
Created: time1,
|
||||
DoneAt: time3,
|
||||
Reminders: []time.Time{
|
||||
time.Date(2020, time.June, 15, 7, 0, 0, 0, time.UTC).In(config.GetTimeZone()),
|
||||
Task: models.Task{
|
||||
Title: "Task400000003",
|
||||
Description: "Lorem Ipsum dolor sit amet",
|
||||
Done: true,
|
||||
DueDate: dueTime,
|
||||
Created: time1,
|
||||
DoneAt: time3,
|
||||
Labels: vikunjaLabels,
|
||||
Reminders: []time.Time{
|
||||
time.Date(2020, time.June, 15, 7, 0, 0, 0, time.UTC).In(config.GetTimeZone()),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Title: "Task400000006",
|
||||
Done: true,
|
||||
DueDate: dueTime,
|
||||
Created: time1,
|
||||
DoneAt: time3,
|
||||
RelatedTasks: map[models.RelationKind][]*models.Task{
|
||||
models.RelationKindSubtask: {
|
||||
Task: models.Task{
|
||||
Title: "Task400000004",
|
||||
Done: false,
|
||||
Created: time1,
|
||||
Labels: vikunjaLabels,
|
||||
},
|
||||
},
|
||||
{
|
||||
Task: models.Task{
|
||||
Title: "Task400000005",
|
||||
Done: true,
|
||||
DueDate: dueTime,
|
||||
Created: time1,
|
||||
DoneAt: time3,
|
||||
Reminders: []time.Time{
|
||||
time.Date(2020, time.June, 15, 7, 0, 0, 0, time.UTC).In(config.GetTimeZone()),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Task: models.Task{
|
||||
Title: "Task400000006",
|
||||
Done: true,
|
||||
DueDate: dueTime,
|
||||
Created: time1,
|
||||
DoneAt: time3,
|
||||
RelatedTasks: map[models.RelationKind][]*models.Task{
|
||||
models.RelationKindSubtask: {
|
||||
{
|
||||
Title: "Task with parent",
|
||||
Done: false,
|
||||
Priority: 2,
|
||||
Created: time1,
|
||||
DoneAt: nilTime,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Task: models.Task{
|
||||
Title: "Task400000106",
|
||||
Done: true,
|
||||
DueDate: dueTimeWithTime,
|
||||
Created: time1,
|
||||
DoneAt: time3,
|
||||
Labels: vikunjaLabels,
|
||||
},
|
||||
},
|
||||
{
|
||||
Task: models.Task{
|
||||
Title: "Task400000107",
|
||||
Done: true,
|
||||
Created: time1,
|
||||
DoneAt: time3,
|
||||
},
|
||||
},
|
||||
{
|
||||
Task: models.Task{
|
||||
Title: "Task400000108",
|
||||
Done: true,
|
||||
Created: time1,
|
||||
DoneAt: time3,
|
||||
},
|
||||
},
|
||||
{
|
||||
Task: models.Task{
|
||||
Title: "Task400000109",
|
||||
Done: true,
|
||||
Created: time1,
|
||||
DoneAt: time3,
|
||||
BucketID: 1234,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
List: models.List{
|
||||
Title: "Project2",
|
||||
Description: "Lorem Ipsum dolor sit amet 4\nLorem Ipsum dolor sit amet 5",
|
||||
HexColor: todoistColors[37],
|
||||
},
|
||||
Tasks: []*models.TaskWithComments{
|
||||
{
|
||||
Task: models.Task{
|
||||
Title: "Task400000007",
|
||||
Done: false,
|
||||
DueDate: dueTime,
|
||||
Created: time1,
|
||||
},
|
||||
},
|
||||
{
|
||||
Task: models.Task{
|
||||
Title: "Task400000008",
|
||||
Done: false,
|
||||
DueDate: dueTime,
|
||||
Created: time1,
|
||||
},
|
||||
},
|
||||
{
|
||||
Task: models.Task{
|
||||
Title: "Task400000009",
|
||||
Done: false,
|
||||
Created: time1,
|
||||
Reminders: []time.Time{
|
||||
time.Date(2020, time.June, 15, 7, 0, 0, 0, time.UTC).In(config.GetTimeZone()),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Task: models.Task{
|
||||
Title: "Task400000010",
|
||||
Description: "Lorem Ipsum dolor sit amet",
|
||||
Done: true,
|
||||
Created: time1,
|
||||
DoneAt: time3,
|
||||
},
|
||||
},
|
||||
{
|
||||
Task: models.Task{
|
||||
Title: "Task400000101",
|
||||
Description: "Lorem Ipsum dolor sit amet",
|
||||
Done: false,
|
||||
Created: time1,
|
||||
Attachments: []*models.TaskAttachment{
|
||||
{
|
||||
Title: "Task with parent",
|
||||
Done: false,
|
||||
Priority: 2,
|
||||
Created: time1,
|
||||
DoneAt: nilTime,
|
||||
File: &files.File{
|
||||
Name: "file.md",
|
||||
Mime: "text/plain",
|
||||
Size: 12345,
|
||||
Created: time1,
|
||||
FileContent: exampleFile,
|
||||
},
|
||||
Created: time1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Title: "Task400000106",
|
||||
Done: true,
|
||||
DueDate: dueTimeWithTime,
|
||||
Created: time1,
|
||||
DoneAt: time3,
|
||||
Labels: vikunjaLabels,
|
||||
Task: models.Task{
|
||||
Title: "Task400000102",
|
||||
Done: false,
|
||||
DueDate: dueTime,
|
||||
Created: time1,
|
||||
Labels: vikunjaLabels,
|
||||
},
|
||||
},
|
||||
{
|
||||
Title: "Task400000107",
|
||||
Done: true,
|
||||
Created: time1,
|
||||
DoneAt: time3,
|
||||
Task: models.Task{
|
||||
Title: "Task400000103",
|
||||
Done: false,
|
||||
Created: time1,
|
||||
Labels: vikunjaLabels,
|
||||
},
|
||||
},
|
||||
{
|
||||
Title: "Task400000108",
|
||||
Done: true,
|
||||
Created: time1,
|
||||
DoneAt: time3,
|
||||
Task: models.Task{
|
||||
Title: "Task400000104",
|
||||
Done: false,
|
||||
Created: time1,
|
||||
Labels: vikunjaLabels,
|
||||
},
|
||||
},
|
||||
{
|
||||
Title: "Task400000109",
|
||||
Done: true,
|
||||
Created: time1,
|
||||
DoneAt: time3,
|
||||
BucketID: 1234,
|
||||
Task: models.Task{
|
||||
Title: "Task400000105",
|
||||
Done: false,
|
||||
DueDate: dueTime,
|
||||
Created: time1,
|
||||
Labels: vikunjaLabels,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Title: "Project2",
|
||||
Description: "Lorem Ipsum dolor sit amet 4\nLorem Ipsum dolor sit amet 5",
|
||||
HexColor: todoistColors[37],
|
||||
Tasks: []*models.Task{
|
||||
{
|
||||
Title: "Task400000007",
|
||||
Done: false,
|
||||
DueDate: dueTime,
|
||||
Created: time1,
|
||||
},
|
||||
{
|
||||
Title: "Task400000008",
|
||||
Done: false,
|
||||
DueDate: dueTime,
|
||||
Created: time1,
|
||||
},
|
||||
{
|
||||
Title: "Task400000009",
|
||||
Done: false,
|
||||
Created: time1,
|
||||
Reminders: []time.Time{
|
||||
time.Date(2020, time.June, 15, 7, 0, 0, 0, time.UTC).In(config.GetTimeZone()),
|
||||
},
|
||||
},
|
||||
{
|
||||
Title: "Task400000010",
|
||||
Description: "Lorem Ipsum dolor sit amet",
|
||||
Done: true,
|
||||
Created: time1,
|
||||
DoneAt: time3,
|
||||
},
|
||||
{
|
||||
Title: "Task400000101",
|
||||
Description: "Lorem Ipsum dolor sit amet",
|
||||
Done: false,
|
||||
Created: time1,
|
||||
Attachments: []*models.TaskAttachment{
|
||||
{
|
||||
File: &files.File{
|
||||
Name: "file.md",
|
||||
Mime: "text/plain",
|
||||
Size: 12345,
|
||||
Created: time1,
|
||||
FileContent: exampleFile,
|
||||
},
|
||||
Created: time1,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Title: "Task400000102",
|
||||
Done: false,
|
||||
DueDate: dueTime,
|
||||
Created: time1,
|
||||
Labels: vikunjaLabels,
|
||||
},
|
||||
{
|
||||
Title: "Task400000103",
|
||||
Done: false,
|
||||
Created: time1,
|
||||
Labels: vikunjaLabels,
|
||||
},
|
||||
{
|
||||
Title: "Task400000104",
|
||||
Done: false,
|
||||
Created: time1,
|
||||
Labels: vikunjaLabels,
|
||||
},
|
||||
{
|
||||
Title: "Task400000105",
|
||||
Done: false,
|
||||
DueDate: dueTime,
|
||||
Created: time1,
|
||||
Labels: vikunjaLabels,
|
||||
},
|
||||
List: models.List{
|
||||
Title: "Project3 - Archived",
|
||||
HexColor: todoistColors[37],
|
||||
IsArchived: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
Title: "Project3 - Archived",
|
||||
HexColor: todoistColors[37],
|
||||
IsArchived: true,
|
||||
Tasks: []*models.Task{
|
||||
Tasks: []*models.TaskWithComments{
|
||||
{
|
||||
Title: "Task400000111",
|
||||
Done: true,
|
||||
Created: time1,
|
||||
DoneAt: time3,
|
||||
Task: models.Task{
|
||||
Title: "Task400000111",
|
||||
Done: true,
|
||||
Created: time1,
|
||||
DoneAt: time3,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -144,16 +144,16 @@ func getTrelloData(token string) (trelloData []*trello.Board, err error) {
|
|||
|
||||
// Converts all previously obtained data from trello into the vikunja format.
|
||||
// `trelloData` should contain all boards with their lists and cards respectively.
|
||||
func convertTrelloDataToVikunja(trelloData []*trello.Board) (fullVikunjaHierachie []*models.NamespaceWithLists, err error) {
|
||||
func convertTrelloDataToVikunja(trelloData []*trello.Board, token string) (fullVikunjaHierachie []*models.NamespaceWithListsAndTasks, err error) {
|
||||
|
||||
log.Debugf("[Trello Migration] ")
|
||||
|
||||
fullVikunjaHierachie = []*models.NamespaceWithLists{
|
||||
fullVikunjaHierachie = []*models.NamespaceWithListsAndTasks{
|
||||
{
|
||||
Namespace: models.Namespace{
|
||||
Title: "Imported from Trello",
|
||||
},
|
||||
Lists: []*models.List{},
|
||||
Lists: []*models.ListWithTasksAndBuckets{},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -162,10 +162,12 @@ func convertTrelloDataToVikunja(trelloData []*trello.Board) (fullVikunjaHierachi
|
|||
log.Debugf("[Trello Migration] Converting %d boards to vikunja lists", len(trelloData))
|
||||
|
||||
for _, board := range trelloData {
|
||||
list := &models.List{
|
||||
Title: board.Name,
|
||||
Description: board.Desc,
|
||||
IsArchived: board.Closed,
|
||||
list := &models.ListWithTasksAndBuckets{
|
||||
List: models.List{
|
||||
Title: board.Name,
|
||||
Description: board.Desc,
|
||||
IsArchived: board.Closed,
|
||||
},
|
||||
}
|
||||
|
||||
// Background
|
||||
|
@ -252,7 +254,9 @@ func convertTrelloDataToVikunja(trelloData []*trello.Board) (fullVikunjaHierachi
|
|||
|
||||
log.Debugf("[Trello Migration] Downloading card attachment %s", attachment.ID)
|
||||
|
||||
buf, err := migration.DownloadFile(attachment.URL)
|
||||
buf, err := migration.DownloadFileWithHeaders(attachment.URL, map[string][]string{
|
||||
"Authorization": {`OAuth oauth_consumer_key="` + config.MigrationTrelloKey.GetString() + `", oauth_token="` + token + `"`},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -269,7 +273,7 @@ func convertTrelloDataToVikunja(trelloData []*trello.Board) (fullVikunjaHierachi
|
|||
log.Debugf("[Trello Migration] Downloaded card attachment %s", attachment.ID)
|
||||
}
|
||||
|
||||
list.Tasks = append(list.Tasks, task)
|
||||
list.Tasks = append(list.Tasks, &models.TaskWithComments{Task: *task})
|
||||
}
|
||||
|
||||
list.Buckets = append(list.Buckets, bucket)
|
||||
|
@ -307,7 +311,7 @@ func (m *Migration) Migrate(u *user.User) (err error) {
|
|||
log.Debugf("[Trello Migration] Got all trello data for user %d", u.ID)
|
||||
log.Debugf("[Trello Migration] Start converting trello data for user %d", u.ID)
|
||||
|
||||
fullVikunjaHierachie, err := convertTrelloDataToVikunja(trelloData)
|
||||
fullVikunjaHierachie, err := convertTrelloDataToVikunja(trelloData, m.Token)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -187,16 +187,18 @@ func TestConvertTrelloToVikunja(t *testing.T) {
|
|||
}
|
||||
trelloData[0].Prefs.BackgroundImage = "https://vikunja.io/testimage.jpg" // Using an image which we are hosting, so it'll still be up
|
||||
|
||||
expectedHierachie := []*models.NamespaceWithLists{
|
||||
expectedHierachie := []*models.NamespaceWithListsAndTasks{
|
||||
{
|
||||
Namespace: models.Namespace{
|
||||
Title: "Imported from Trello",
|
||||
},
|
||||
Lists: []*models.List{
|
||||
Lists: []*models.ListWithTasksAndBuckets{
|
||||
{
|
||||
Title: "TestBoard",
|
||||
Description: "This is a description",
|
||||
BackgroundInformation: bytes.NewBuffer(exampleFile),
|
||||
List: models.List{
|
||||
Title: "TestBoard",
|
||||
Description: "This is a description",
|
||||
BackgroundInformation: bytes.NewBuffer(exampleFile),
|
||||
},
|
||||
Buckets: []*models.Bucket{
|
||||
{
|
||||
ID: 1,
|
||||
|
@ -207,37 +209,40 @@ func TestConvertTrelloToVikunja(t *testing.T) {
|
|||
Title: "Test List 2",
|
||||
},
|
||||
},
|
||||
Tasks: []*models.Task{
|
||||
Tasks: []*models.TaskWithComments{
|
||||
{
|
||||
Title: "Test Card 1",
|
||||
Description: "Card Description",
|
||||
BucketID: 1,
|
||||
KanbanPosition: 123,
|
||||
DueDate: time1,
|
||||
Labels: []*models.Label{
|
||||
{
|
||||
Title: "Label 1",
|
||||
HexColor: trelloColorMap["green"],
|
||||
Task: models.Task{
|
||||
Title: "Test Card 1",
|
||||
Description: "Card Description",
|
||||
BucketID: 1,
|
||||
KanbanPosition: 123,
|
||||
DueDate: time1,
|
||||
Labels: []*models.Label{
|
||||
{
|
||||
Title: "Label 1",
|
||||
HexColor: trelloColorMap["green"],
|
||||
},
|
||||
{
|
||||
Title: "Label 2",
|
||||
HexColor: trelloColorMap["orange"],
|
||||
},
|
||||
},
|
||||
{
|
||||
Title: "Label 2",
|
||||
HexColor: trelloColorMap["orange"],
|
||||
},
|
||||
},
|
||||
Attachments: []*models.TaskAttachment{
|
||||
{
|
||||
File: &files.File{
|
||||
Name: "Testimage.jpg",
|
||||
Mime: "image/jpg",
|
||||
Size: uint64(len(exampleFile)),
|
||||
FileContent: exampleFile,
|
||||
Attachments: []*models.TaskAttachment{
|
||||
{
|
||||
File: &files.File{
|
||||
Name: "Testimage.jpg",
|
||||
Mime: "image/jpg",
|
||||
Size: uint64(len(exampleFile)),
|
||||
FileContent: exampleFile,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Title: "Test Card 2",
|
||||
Description: `
|
||||
Task: models.Task{
|
||||
Title: "Test Card 2",
|
||||
Description: `
|
||||
|
||||
## Checklist 1
|
||||
|
||||
|
@ -248,84 +253,105 @@ func TestConvertTrelloToVikunja(t *testing.T) {
|
|||
|
||||
* [ ] Pending Task
|
||||
* [ ] Another Pending Task`,
|
||||
BucketID: 1,
|
||||
KanbanPosition: 124,
|
||||
BucketID: 1,
|
||||
KanbanPosition: 124,
|
||||
},
|
||||
},
|
||||
{
|
||||
Title: "Test Card 3",
|
||||
BucketID: 1,
|
||||
KanbanPosition: 126,
|
||||
Task: models.Task{
|
||||
Title: "Test Card 3",
|
||||
BucketID: 1,
|
||||
KanbanPosition: 126,
|
||||
},
|
||||
},
|
||||
{
|
||||
Title: "Test Card 4",
|
||||
BucketID: 1,
|
||||
KanbanPosition: 127,
|
||||
Labels: []*models.Label{
|
||||
{
|
||||
Title: "Label 2",
|
||||
HexColor: trelloColorMap["orange"],
|
||||
Task: models.Task{
|
||||
Title: "Test Card 4",
|
||||
BucketID: 1,
|
||||
KanbanPosition: 127,
|
||||
Labels: []*models.Label{
|
||||
{
|
||||
Title: "Label 2",
|
||||
HexColor: trelloColorMap["orange"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Title: "Test Card 5",
|
||||
BucketID: 2,
|
||||
KanbanPosition: 111,
|
||||
Labels: []*models.Label{
|
||||
{
|
||||
Title: "Label 3",
|
||||
HexColor: trelloColorMap["blue"],
|
||||
Task: models.Task{
|
||||
Title: "Test Card 5",
|
||||
BucketID: 2,
|
||||
KanbanPosition: 111,
|
||||
Labels: []*models.Label{
|
||||
{
|
||||
Title: "Label 3",
|
||||
HexColor: trelloColorMap["blue"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Title: "Test Card 6",
|
||||
BucketID: 2,
|
||||
KanbanPosition: 222,
|
||||
DueDate: time1,
|
||||
Task: models.Task{
|
||||
Title: "Test Card 6",
|
||||
BucketID: 2,
|
||||
KanbanPosition: 222,
|
||||
DueDate: time1,
|
||||
},
|
||||
},
|
||||
{
|
||||
Title: "Test Card 7",
|
||||
BucketID: 2,
|
||||
KanbanPosition: 333,
|
||||
Task: models.Task{
|
||||
Title: "Test Card 7",
|
||||
BucketID: 2,
|
||||
KanbanPosition: 333,
|
||||
},
|
||||
},
|
||||
{
|
||||
Title: "Test Card 8",
|
||||
BucketID: 2,
|
||||
KanbanPosition: 444,
|
||||
Task: models.Task{
|
||||
Title: "Test Card 8",
|
||||
BucketID: 2,
|
||||
KanbanPosition: 444,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Title: "TestBoard 2",
|
||||
List: models.List{
|
||||
Title: "TestBoard 2",
|
||||
},
|
||||
Buckets: []*models.Bucket{
|
||||
{
|
||||
ID: 3,
|
||||
Title: "Test List 4",
|
||||
},
|
||||
},
|
||||
Tasks: []*models.Task{
|
||||
Tasks: []*models.TaskWithComments{
|
||||
{
|
||||
Title: "Test Card 634",
|
||||
BucketID: 3,
|
||||
KanbanPosition: 123,
|
||||
Task: models.Task{
|
||||
Title: "Test Card 634",
|
||||
BucketID: 3,
|
||||
KanbanPosition: 123,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Title: "TestBoard Archived",
|
||||
IsArchived: true,
|
||||
List: models.List{
|
||||
Title: "TestBoard Archived",
|
||||
IsArchived: true,
|
||||
},
|
||||
Buckets: []*models.Bucket{
|
||||
{
|
||||
ID: 4,
|
||||
Title: "Test List 5",
|
||||
},
|
||||
},
|
||||
Tasks: []*models.Task{
|
||||
Tasks: []*models.TaskWithComments{
|
||||
{
|
||||
Title: "Test Card 63423",
|
||||
BucketID: 4,
|
||||
KanbanPosition: 123,
|
||||
Task: models.Task{
|
||||
Title: "Test Card 63423",
|
||||
BucketID: 4,
|
||||
KanbanPosition: 123,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -333,7 +359,7 @@ func TestConvertTrelloToVikunja(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
hierachie, err := convertTrelloDataToVikunja(trelloData)
|
||||
hierachie, err := convertTrelloDataToVikunja(trelloData, "")
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, hierachie)
|
||||
if diff, equal := messagediff.PrettyDiff(hierachie, expectedHierachie); !equal {
|
||||
|
|
Binary file not shown.
|
@ -0,0 +1,44 @@
|
|||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public Licensee as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public Licensee
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package vikunjafile
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"code.vikunja.io/api/pkg/events"
|
||||
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/files"
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
)
|
||||
|
||||
// TestMain is the main test function used to bootstrap the test env
|
||||
func TestMain(m *testing.M) {
|
||||
// Set default config
|
||||
config.InitDefaultConfig()
|
||||
// We need to set the root path even if we're not using the config, otherwise fixtures are not loaded correctly
|
||||
config.ServiceRootpath.Set(os.Getenv("VIKUNJA_SERVICE_ROOTPATH"))
|
||||
|
||||
// Some tests use the file engine, so we'll need to initialize that
|
||||
files.InitTests()
|
||||
user.InitTests()
|
||||
models.SetupTests()
|
||||
events.Fake()
|
||||
os.Exit(m.Run())
|
||||
}
|
|
@ -0,0 +1,204 @@
|
|||
// 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 vikunjafile
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"code.vikunja.io/api/pkg/db"
|
||||
"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"
|
||||
)
|
||||
|
||||
const logPrefix = "[Vikunja File Import] "
|
||||
|
||||
type FileMigrator struct {
|
||||
}
|
||||
|
||||
// Name is used to get the name of the vikunja-file migration - we're using the docs here to annotate the status route.
|
||||
// @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/vikunja-file/status [get]
|
||||
func (v *FileMigrator) Name() string {
|
||||
return "vikunja-file"
|
||||
}
|
||||
|
||||
// Migrate takes a vikunja file export, parses it and imports everything in it into Vikunja.
|
||||
// @Summary Import all lists, tasks etc. from a Vikunja data export
|
||||
// @Description Imports all projects, tasks, notes, reminders, subtasks and files from a Vikunjda data export into Vikunja.
|
||||
// @tags migration
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security JWTKeyAuth
|
||||
// @Param import formData string true "The Vikunja export zip file."
|
||||
// @Success 200 {object} models.Message "A message telling you everything was migrated successfully."
|
||||
// @Failure 500 {object} models.Message "Internal server error"
|
||||
// @Router /migration/vikunja-file/migrate [post]
|
||||
func (v *FileMigrator) Migrate(user *user.User, file io.ReaderAt, size int64) error {
|
||||
r, err := zip.NewReader(file, size)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not open import file: %s", err)
|
||||
}
|
||||
|
||||
log.Debugf(logPrefix+"Importing a zip file containing %d files", len(r.File))
|
||||
|
||||
var dataFile *zip.File
|
||||
var filterFile *zip.File
|
||||
storedFiles := make(map[int64]*zip.File)
|
||||
for _, f := range r.File {
|
||||
if strings.HasPrefix(f.Name, "files/") {
|
||||
fname := strings.ReplaceAll(f.Name, "files/", "")
|
||||
id, err := strconv.ParseInt(fname, 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not convert file id: %s", err)
|
||||
}
|
||||
storedFiles[id] = f
|
||||
log.Debugf(logPrefix + "Found a blob file")
|
||||
continue
|
||||
}
|
||||
if f.Name == "data.json" {
|
||||
dataFile = f
|
||||
log.Debugf(logPrefix + "Found a data file")
|
||||
continue
|
||||
}
|
||||
if f.Name == "filters.json" {
|
||||
filterFile = f
|
||||
log.Debugf(logPrefix + "Found a filter file")
|
||||
}
|
||||
}
|
||||
|
||||
if dataFile == nil {
|
||||
return fmt.Errorf("no data file provided")
|
||||
}
|
||||
|
||||
log.Debugf(logPrefix + "")
|
||||
|
||||
//////
|
||||
// Import the bulk of Vikunja data
|
||||
df, err := dataFile.Open()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not open data file: %s", err)
|
||||
}
|
||||
defer df.Close()
|
||||
|
||||
var bufData bytes.Buffer
|
||||
if _, err := bufData.ReadFrom(df); err != nil {
|
||||
return fmt.Errorf("could not read data file: %s", err)
|
||||
}
|
||||
|
||||
namespaces := []*models.NamespaceWithListsAndTasks{}
|
||||
if err := json.Unmarshal(bufData.Bytes(), &namespaces); err != nil {
|
||||
return fmt.Errorf("could not read data: %s", err)
|
||||
}
|
||||
|
||||
for _, n := range namespaces {
|
||||
for _, l := range n.Lists {
|
||||
if b, exists := storedFiles[l.BackgroundFileID]; exists {
|
||||
bf, err := b.Open()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not open list background file %d for reading: %s", l.BackgroundFileID, err)
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
if _, err := buf.ReadFrom(bf); err != nil {
|
||||
return fmt.Errorf("could not read list background file %d: %s", l.BackgroundFileID, err)
|
||||
}
|
||||
|
||||
l.BackgroundInformation = &buf
|
||||
}
|
||||
|
||||
for _, t := range l.Tasks {
|
||||
for _, label := range t.Labels {
|
||||
label.ID = 0
|
||||
}
|
||||
for _, comment := range t.Comments {
|
||||
comment.ID = 0
|
||||
}
|
||||
for _, attachment := range t.Attachments {
|
||||
af, err := storedFiles[attachment.File.ID].Open()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not open attachment %d for reading: %s", attachment.ID, err)
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
if _, err := buf.ReadFrom(af); err != nil {
|
||||
return fmt.Errorf("could not read attachment %d: %s", attachment.ID, err)
|
||||
}
|
||||
|
||||
attachment.ID = 0
|
||||
attachment.File.ID = 0
|
||||
attachment.File.FileContent = buf.Bytes()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = migration.InsertFromStructure(namespaces, user)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not insert data: %s", err)
|
||||
}
|
||||
|
||||
if filterFile == nil {
|
||||
log.Debugf(logPrefix + "No filter file found")
|
||||
return nil
|
||||
}
|
||||
|
||||
///////
|
||||
// Import filters
|
||||
ff, err := filterFile.Open()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not open filters file: %s", err)
|
||||
}
|
||||
defer ff.Close()
|
||||
|
||||
var bufFilter bytes.Buffer
|
||||
if _, err := bufFilter.ReadFrom(ff); err != nil {
|
||||
return fmt.Errorf("could not read filters file: %s", err)
|
||||
}
|
||||
|
||||
filters := []*models.SavedFilter{}
|
||||
if err := json.Unmarshal(bufFilter.Bytes(), &filters); err != nil {
|
||||
return fmt.Errorf("could not read filter data: %s", err)
|
||||
}
|
||||
|
||||
log.Debugf(logPrefix+"Importing %d saved filters", len(filters))
|
||||
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
for _, f := range filters {
|
||||
f.ID = 0
|
||||
err = f.Create(s, user)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return s.Commit()
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
// 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 vikunjafile
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/db"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestVikunjaFileMigrator_Migrate(t *testing.T) {
|
||||
db.LoadAndAssertFixtures(t)
|
||||
|
||||
m := &FileMigrator{}
|
||||
u := &user.User{ID: 1}
|
||||
|
||||
f, err := os.Open(config.ServiceRootpath.GetString() + "/pkg/modules/migration/vikunja-file/export.zip")
|
||||
if err != nil {
|
||||
t.Fatalf("Could not open file: %s", err)
|
||||
}
|
||||
defer f.Close()
|
||||
s, err := f.Stat()
|
||||
if err != nil {
|
||||
t.Fatalf("Could not stat file: %s", err)
|
||||
}
|
||||
|
||||
err = m.Migrate(u, f, s.Size())
|
||||
assert.NoError(t, err)
|
||||
db.AssertExists(t, "namespaces", map[string]interface{}{
|
||||
"title": "test",
|
||||
"owner_id": u.ID,
|
||||
}, false)
|
||||
db.AssertExists(t, "lists", map[string]interface{}{
|
||||
"title": "Test list",
|
||||
"owner_id": u.ID,
|
||||
}, false)
|
||||
db.AssertExists(t, "lists", map[string]interface{}{
|
||||
"title": "A list with a background",
|
||||
"owner_id": u.ID,
|
||||
}, false)
|
||||
db.AssertExists(t, "tasks", map[string]interface{}{
|
||||
"title": "Some other task",
|
||||
"created_by_id": u.ID,
|
||||
}, false)
|
||||
db.AssertExists(t, "task_comments", map[string]interface{}{
|
||||
"comment": "This is a comment",
|
||||
"author_id": u.ID,
|
||||
}, false)
|
||||
db.AssertExists(t, "files", map[string]interface{}{
|
||||
"name": "cristiano-mozzillo-v3d5uBB26yA-unsplash.jpg",
|
||||
"created_by_id": u.ID,
|
||||
}, false)
|
||||
db.AssertExists(t, "labels", map[string]interface{}{
|
||||
"title": "test",
|
||||
"created_by_id": u.ID,
|
||||
}, false)
|
||||
db.AssertExists(t, "buckets", map[string]interface{}{
|
||||
"title": "Test Bucket",
|
||||
"created_by_id": u.ID,
|
||||
}, false)
|
||||
}
|
|
@ -142,11 +142,13 @@ type wunderlistContents struct {
|
|||
subtasks []*subtask
|
||||
}
|
||||
|
||||
func convertListForFolder(listID int, list *list, content *wunderlistContents) (*models.List, error) {
|
||||
func convertListForFolder(listID int, list *list, content *wunderlistContents) (*models.ListWithTasksAndBuckets, error) {
|
||||
|
||||
l := &models.List{
|
||||
Title: list.Title,
|
||||
Created: list.CreatedAt,
|
||||
l := &models.ListWithTasksAndBuckets{
|
||||
List: models.List{
|
||||
Title: list.Title,
|
||||
Created: list.CreatedAt,
|
||||
},
|
||||
}
|
||||
|
||||
// Find all tasks belonging to this list and put them in
|
||||
|
@ -233,13 +235,13 @@ func convertListForFolder(listID int, list *list, content *wunderlistContents) (
|
|||
}
|
||||
}
|
||||
|
||||
l.Tasks = append(l.Tasks, newTask)
|
||||
l.Tasks = append(l.Tasks, &models.TaskWithComments{Task: *newTask})
|
||||
}
|
||||
}
|
||||
return l, nil
|
||||
}
|
||||
|
||||
func convertWunderlistToVikunja(content *wunderlistContents) (fullVikunjaHierachie []*models.NamespaceWithLists, err error) {
|
||||
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))
|
||||
|
@ -249,7 +251,7 @@ func convertWunderlistToVikunja(content *wunderlistContents) (fullVikunjaHierach
|
|||
|
||||
// First, we look through all folders and create namespaces for them.
|
||||
for _, folder := range content.folders {
|
||||
namespace := &models.NamespaceWithLists{
|
||||
namespace := &models.NamespaceWithListsAndTasks{
|
||||
Namespace: models.Namespace{
|
||||
Title: folder.Title,
|
||||
Created: folder.CreatedAt,
|
||||
|
@ -276,7 +278,7 @@ func convertWunderlistToVikunja(content *wunderlistContents) (fullVikunjaHierach
|
|||
|
||||
// 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.NamespaceWithLists{
|
||||
newNamespace := &models.NamespaceWithListsAndTasks{
|
||||
Namespace: models.Namespace{
|
||||
Title: "Migrated from wunderlist",
|
||||
},
|
||||
|
|
|
@ -194,49 +194,55 @@ func TestWunderlistParsing(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
expectedHierachie := []*models.NamespaceWithLists{
|
||||
expectedHierachie := []*models.NamespaceWithListsAndTasks{
|
||||
{
|
||||
Namespace: models.Namespace{
|
||||
Title: "Lorem Ipsum",
|
||||
Created: time1,
|
||||
Updated: time2,
|
||||
},
|
||||
Lists: []*models.List{
|
||||
Lists: []*models.ListWithTasksAndBuckets{
|
||||
{
|
||||
Created: time1,
|
||||
Title: "Lorem1",
|
||||
Tasks: []*models.Task{
|
||||
List: models.List{
|
||||
Created: time1,
|
||||
Title: "Lorem1",
|
||||
},
|
||||
Tasks: []*models.TaskWithComments{
|
||||
{
|
||||
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,
|
||||
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,
|
||||
},
|
||||
Created: time2,
|
||||
},
|
||||
Reminders: []time.Time{time4},
|
||||
},
|
||||
Reminders: []time.Time{time4},
|
||||
},
|
||||
{
|
||||
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",
|
||||
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",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -244,38 +250,44 @@ func TestWunderlistParsing(t *testing.T) {
|
|||
},
|
||||
},
|
||||
{
|
||||
Created: time1,
|
||||
Title: "Lorem2",
|
||||
Tasks: []*models.Task{
|
||||
List: models.List{
|
||||
Created: time1,
|
||||
Title: "Lorem2",
|
||||
},
|
||||
Tasks: []*models.TaskWithComments{
|
||||
{
|
||||
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,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Title: "Ipsum4",
|
||||
DueDate: time.Unix(1378339200, 0).In(config.GetTimeZone()),
|
||||
Created: time1,
|
||||
Reminders: []time.Time{time3},
|
||||
RelatedTasks: map[models.RelationKind][]*models.Task{
|
||||
models.RelationKindSubtask: {
|
||||
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{
|
||||
{
|
||||
Title: "LoremSub3",
|
||||
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",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -283,52 +295,68 @@ func TestWunderlistParsing(t *testing.T) {
|
|||
},
|
||||
},
|
||||
{
|
||||
Created: time1,
|
||||
Title: "Lorem3",
|
||||
Tasks: []*models.Task{
|
||||
List: models.List{
|
||||
Created: time1,
|
||||
Title: "Lorem3",
|
||||
},
|
||||
Tasks: []*models.TaskWithComments{
|
||||
{
|
||||
Title: "Ipsum5",
|
||||
DueDate: time.Unix(1378339200, 0).In(config.GetTimeZone()),
|
||||
Created: time1,
|
||||
Task: models.Task{
|
||||
Title: "Ipsum5",
|
||||
DueDate: time.Unix(1378339200, 0).In(config.GetTimeZone()),
|
||||
Created: time1,
|
||||
},
|
||||
},
|
||||
{
|
||||
Title: "Ipsum6",
|
||||
DueDate: time.Unix(1378339200, 0).In(config.GetTimeZone()),
|
||||
Created: time1,
|
||||
Done: true,
|
||||
DoneAt: time1,
|
||||
Task: models.Task{
|
||||
Title: "Ipsum6",
|
||||
DueDate: time.Unix(1378339200, 0).In(config.GetTimeZone()),
|
||||
Created: time1,
|
||||
Done: true,
|
||||
DoneAt: time1,
|
||||
},
|
||||
},
|
||||
{
|
||||
Title: "Ipsum7",
|
||||
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,
|
||||
},
|
||||
},
|
||||
{
|
||||
Title: "Ipsum8",
|
||||
DueDate: time.Unix(1378339200, 0).In(config.GetTimeZone()),
|
||||
Created: time1,
|
||||
Task: models.Task{
|
||||
Title: "Ipsum8",
|
||||
DueDate: time.Unix(1378339200, 0).In(config.GetTimeZone()),
|
||||
Created: time1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Created: time1,
|
||||
Title: "Lorem4",
|
||||
Tasks: []*models.Task{
|
||||
List: models.List{
|
||||
Created: time1,
|
||||
Title: "Lorem4",
|
||||
},
|
||||
Tasks: []*models.TaskWithComments{
|
||||
{
|
||||
Title: "Ipsum9",
|
||||
DueDate: time.Unix(1378339200, 0).In(config.GetTimeZone()),
|
||||
Created: time1,
|
||||
Done: true,
|
||||
DoneAt: time1,
|
||||
Task: models.Task{
|
||||
Title: "Ipsum9",
|
||||
DueDate: time.Unix(1378339200, 0).In(config.GetTimeZone()),
|
||||
Created: time1,
|
||||
Done: true,
|
||||
DoneAt: time1,
|
||||
},
|
||||
},
|
||||
{
|
||||
Title: "Ipsum10",
|
||||
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,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -338,10 +366,12 @@ func TestWunderlistParsing(t *testing.T) {
|
|||
Namespace: models.Namespace{
|
||||
Title: "Migrated from wunderlist",
|
||||
},
|
||||
Lists: []*models.List{
|
||||
Lists: []*models.ListWithTasksAndBuckets{
|
||||
{
|
||||
Created: time4,
|
||||
Title: "List without a namespace",
|
||||
List: models.List{
|
||||
Created: time4,
|
||||
Title: "List without a namespace",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -19,6 +19,8 @@ package v1
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
vikunja_file "code.vikunja.io/api/pkg/modules/migration/vikunja-file"
|
||||
|
||||
microsofttodo "code.vikunja.io/api/pkg/modules/migration/microsoft-todo"
|
||||
|
||||
"code.vikunja.io/api/pkg/modules/migration/trello"
|
||||
|
@ -49,6 +51,7 @@ type vikunjaInfos struct {
|
|||
AuthInfo authInfo `json:"auth"`
|
||||
EmailRemindersEnabled bool `json:"email_reminders_enabled"`
|
||||
UserDeletionEnabled bool `json:"user_deletion_enabled"`
|
||||
TaskCommentsEnabled bool `json:"task_comments_enabled"`
|
||||
}
|
||||
|
||||
type authInfo struct {
|
||||
|
@ -91,6 +94,10 @@ func Info(c echo.Context) error {
|
|||
CaldavEnabled: config.ServiceEnableCaldav.GetBool(),
|
||||
EmailRemindersEnabled: config.ServiceEnableEmailReminders.GetBool(),
|
||||
UserDeletionEnabled: config.ServiceEnableUserDeletion.GetBool(),
|
||||
TaskCommentsEnabled: config.ServiceEnableTaskComments.GetBool(),
|
||||
AvailableMigrators: []string{
|
||||
(&vikunja_file.FileMigrator{}).Name(),
|
||||
},
|
||||
Legal: legalInfo{
|
||||
ImprintURL: config.LegalImprintURL.GetString(),
|
||||
PrivacyPolicyURL: config.LegalPrivacyURL.GetString(),
|
||||
|
|
|
@ -27,7 +27,7 @@ import (
|
|||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
type UserDeletionRequest struct {
|
||||
type UserPasswordConfirmation struct {
|
||||
Password string `json:"password" valid:"required"`
|
||||
}
|
||||
|
||||
|
@ -41,13 +41,13 @@ type UserDeletionRequestConfirm struct {
|
|||
// @tags user
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param credentials body v1.UserDeletionRequest true "The user password."
|
||||
// @Param credentials body v1.UserPasswordConfirmation true "The user password."
|
||||
// @Success 200 {object} models.Message
|
||||
// @Failure 412 {object} web.HTTPError "Bad password provided."
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /user/deletion/request [post]
|
||||
func UserRequestDeletion(c echo.Context) error {
|
||||
var deletionRequest UserDeletionRequest
|
||||
var deletionRequest UserPasswordConfirmation
|
||||
if err := c.Bind(&deletionRequest); err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "No password provided.")
|
||||
}
|
||||
|
@ -149,13 +149,13 @@ func UserConfirmDeletion(c echo.Context) error {
|
|||
// @tags user
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param credentials body v1.UserDeletionRequest true "The user password to confirm."
|
||||
// @Param credentials body v1.UserPasswordConfirmation true "The user password to confirm."
|
||||
// @Success 200 {object} models.Message
|
||||
// @Failure 412 {object} web.HTTPError "Bad password provided."
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /user/deletion/cancel [post]
|
||||
func UserCancelDeletion(c echo.Context) error {
|
||||
var deletionRequest UserDeletionRequest
|
||||
var deletionRequest UserPasswordConfirmation
|
||||
if err := c.Bind(&deletionRequest); err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "No password provided.")
|
||||
}
|
||||
|
|
|
@ -0,0 +1,141 @@
|
|||
// 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 v1
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"code.vikunja.io/api/pkg/db"
|
||||
"code.vikunja.io/api/pkg/events"
|
||||
"code.vikunja.io/api/pkg/files"
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"code.vikunja.io/web/handler"
|
||||
"github.com/labstack/echo/v4"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func checkExportRequest(c echo.Context) (s *xorm.Session, u *user.User, err error) {
|
||||
s = db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
err = s.Begin()
|
||||
if err != nil {
|
||||
return nil, nil, handler.HandleHTTPError(err, c)
|
||||
}
|
||||
|
||||
u, err = user.GetCurrentUserFromDB(s, c)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return nil, nil, handler.HandleHTTPError(err, c)
|
||||
}
|
||||
|
||||
// Users authenticated with a third-party are unable to provide their password.
|
||||
if u.Issuer != user.IssuerLocal {
|
||||
return
|
||||
}
|
||||
|
||||
var pass UserPasswordConfirmation
|
||||
if err := c.Bind(&pass); err != nil {
|
||||
return nil, nil, echo.NewHTTPError(http.StatusBadRequest, "No password provided.")
|
||||
}
|
||||
|
||||
err = c.Validate(pass)
|
||||
if err != nil {
|
||||
return nil, nil, echo.NewHTTPError(http.StatusBadRequest, err)
|
||||
}
|
||||
|
||||
err = user.CheckUserPassword(u, pass.Password)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return nil, nil, handler.HandleHTTPError(err, c)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// RequestUserDataExport is the handler to request a user data export
|
||||
// @Summary Request a user data export.
|
||||
// @tags user
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security JWTKeyAuth
|
||||
// @Param password body v1.UserPasswordConfirmation true "User password to confirm the data export request."
|
||||
// @Success 200 {object} models.Message
|
||||
// @Failure 400 {object} web.HTTPError "Something's invalid."
|
||||
// @Failure 500 {object} models.Message "Internal server error."
|
||||
// @Router /user/export/request [post]
|
||||
func RequestUserDataExport(c echo.Context) error {
|
||||
s, u, err := checkExportRequest(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = events.Dispatch(&models.UserDataExportRequestedEvent{
|
||||
User: u,
|
||||
})
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err, c)
|
||||
}
|
||||
|
||||
err = s.Commit()
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err, c)
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, models.Message{Message: "Successfully requested data export. We will send you an email when it's ready."})
|
||||
}
|
||||
|
||||
// DownloadUserDataExport is the handler to download a created user data export
|
||||
// @Summary Download a user data export.
|
||||
// @tags user
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security JWTKeyAuth
|
||||
// @Param password body v1.UserPasswordConfirmation true "User password to confirm the download."
|
||||
// @Success 200 {object} models.Message
|
||||
// @Failure 400 {object} web.HTTPError "Something's invalid."
|
||||
// @Failure 500 {object} models.Message "Internal server error."
|
||||
// @Router /user/export/download [post]
|
||||
func DownloadUserDataExport(c echo.Context) error {
|
||||
s, u, err := checkExportRequest(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.Commit()
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return handler.HandleHTTPError(err, c)
|
||||
}
|
||||
|
||||
// Download
|
||||
exportFile := &files.File{ID: u.ExportFileID}
|
||||
err = exportFile.LoadFileMetaByID()
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err, c)
|
||||
}
|
||||
err = exportFile.LoadFileByID()
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err, c)
|
||||
}
|
||||
|
||||
http.ServeContent(c.Response(), c.Request(), exportFile.Name, exportFile.Created, exportFile.File)
|
||||
return nil
|
||||
}
|
|
@ -35,6 +35,7 @@ type userWithSettings struct {
|
|||
user.User
|
||||
Settings *UserSettings `json:"settings"`
|
||||
DeletionScheduledAt time.Time `json:"deletion_scheduled_at"`
|
||||
IsLocalUser bool `json:"is_local_user"`
|
||||
}
|
||||
|
||||
// UserShow gets all informations about the current user
|
||||
|
@ -74,6 +75,7 @@ func UserShow(c echo.Context) error {
|
|||
WeekStart: u.WeekStart,
|
||||
},
|
||||
DeletionScheduledAt: u.DeletionScheduledAt,
|
||||
IsLocalUser: u.Issuer == user.IssuerLocal,
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, us)
|
||||
|
|
|
@ -56,7 +56,7 @@ func ListHandler(c echo.Context) error {
|
|||
}
|
||||
|
||||
storage := &VikunjaCaldavListStorage{
|
||||
list: &models.List{ID: listID},
|
||||
list: &models.ListWithTasksAndBuckets{List: models.List{ID: listID}},
|
||||
user: u,
|
||||
}
|
||||
|
||||
|
@ -102,7 +102,7 @@ func TaskHandler(c echo.Context) error {
|
|||
taskUID := strings.TrimSuffix(c.Param("task"), ".ics")
|
||||
|
||||
storage := &VikunjaCaldavListStorage{
|
||||
list: &models.List{ID: listID},
|
||||
list: &models.ListWithTasksAndBuckets{List: models.List{ID: listID}},
|
||||
task: &models.Task{UID: taskUID},
|
||||
user: u,
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ const ListBasePath = DavBasePath + `lists`
|
|||
// VikunjaCaldavListStorage represents a list storage
|
||||
type VikunjaCaldavListStorage struct {
|
||||
// Used when handling a list
|
||||
list *models.List
|
||||
list *models.ListWithTasksAndBuckets
|
||||
// Used when handling a single task, like updating
|
||||
task *models.Task
|
||||
// The current user
|
||||
|
@ -109,7 +109,9 @@ func (vcls *VikunjaCaldavListStorage) GetResources(rpath string, withChildren bo
|
|||
var resources []data.Resource
|
||||
for _, l := range lists {
|
||||
rr := VikunjaListResourceAdapter{
|
||||
list: l,
|
||||
list: &models.ListWithTasksAndBuckets{
|
||||
List: *l,
|
||||
},
|
||||
isCollection: true,
|
||||
}
|
||||
r := data.NewResource(ListBasePath+"/"+strconv.FormatInt(l.ID, 10), &rr)
|
||||
|
@ -172,10 +174,10 @@ func (vcls *VikunjaCaldavListStorage) GetResourcesByFilters(rpath string, filter
|
|||
for _, t := range vcls.list.Tasks {
|
||||
rr := VikunjaListResourceAdapter{
|
||||
list: vcls.list,
|
||||
task: t,
|
||||
task: &t.Task,
|
||||
isCollection: false,
|
||||
}
|
||||
r := data.NewResource(getTaskURL(t), &rr)
|
||||
r := data.NewResource(getTaskURL(&t.Task), &rr)
|
||||
r.Name = t.Title
|
||||
resources = append(resources, r)
|
||||
}
|
||||
|
@ -368,8 +370,8 @@ func (vcls *VikunjaCaldavListStorage) DeleteResource(rpath string) error {
|
|||
|
||||
// VikunjaListResourceAdapter holds the actual resource
|
||||
type VikunjaListResourceAdapter struct {
|
||||
list *models.List
|
||||
listTasks []*models.Task
|
||||
list *models.ListWithTasksAndBuckets
|
||||
listTasks []*models.TaskWithComments
|
||||
task *models.Task
|
||||
|
||||
isPrincipal bool
|
||||
|
@ -415,7 +417,7 @@ func (vlra *VikunjaListResourceAdapter) GetContent() string {
|
|||
}
|
||||
|
||||
if vlra.task != nil {
|
||||
list := models.List{Tasks: []*models.Task{vlra.task}}
|
||||
list := models.ListWithTasksAndBuckets{Tasks: []*models.TaskWithComments{{Task: *vlra.task}}}
|
||||
return caldav.GetCaldavTodosForTasks(&list, list.Tasks)
|
||||
}
|
||||
|
||||
|
@ -479,8 +481,10 @@ func (vcls *VikunjaCaldavListStorage) getListRessource(isCollection bool) (rr Vi
|
|||
panic("Tasks returned from TaskCollection.ReadAll are not []*models.Task!")
|
||||
}
|
||||
|
||||
listTasks = tasks
|
||||
vcls.list.Tasks = tasks
|
||||
for _, t := range tasks {
|
||||
listTasks = append(listTasks, &models.TaskWithComments{Task: *t})
|
||||
}
|
||||
vcls.list.Tasks = listTasks
|
||||
}
|
||||
|
||||
if err := s.Commit(); err != nil {
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
// 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 routes
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
// HealthcheckHandler handles healthckeck 'OK' response
|
||||
func HealthcheckHandler(c echo.Context) error {
|
||||
return c.String(http.StatusOK, "OK")
|
||||
}
|
|
@ -74,29 +74,33 @@ func RateLimit(rateLimiter *limiter.Limiter, rateLimitKind string) echo.Middlewa
|
|||
}
|
||||
}
|
||||
|
||||
func createRateLimiter(rate limiter.Rate) *limiter.Limiter {
|
||||
var store limiter.Store
|
||||
var err error
|
||||
switch config.RateLimitStore.GetString() {
|
||||
case "memory":
|
||||
store = memory.NewStore()
|
||||
case "redis":
|
||||
if !config.RedisEnabled.GetBool() {
|
||||
log.Fatal("Redis is configured for rate limiting, but not enabled!")
|
||||
}
|
||||
store, err = redis.NewStore(red.GetRedis())
|
||||
if err != nil {
|
||||
log.Fatalf("Error while creating rate limit redis store: %s", err)
|
||||
}
|
||||
default:
|
||||
log.Fatalf("Unknown Rate limit store \"%s\"", config.RateLimitStore.GetString())
|
||||
}
|
||||
return limiter.New(store, rate)
|
||||
}
|
||||
|
||||
func setupRateLimit(a *echo.Group, rateLimitKind string) {
|
||||
if config.RateLimitEnabled.GetBool() {
|
||||
rate := limiter.Rate{
|
||||
Period: config.RateLimitPeriod.GetDuration() * time.Second,
|
||||
Limit: config.RateLimitLimit.GetInt64(),
|
||||
}
|
||||
var store limiter.Store
|
||||
var err error
|
||||
switch config.RateLimitStore.GetString() {
|
||||
case "memory":
|
||||
store = memory.NewStore()
|
||||
case "redis":
|
||||
if !config.RedisEnabled.GetBool() {
|
||||
log.Fatal("Redis is configured for rate limiting, but not enabled!")
|
||||
}
|
||||
store, err = redis.NewStore(red.GetRedis())
|
||||
if err != nil {
|
||||
log.Fatalf("Error while creating rate limit redis store: %s", err)
|
||||
}
|
||||
default:
|
||||
log.Fatalf("Unknown Rate limit store \"%s\"", config.RateLimitStore.GetString())
|
||||
}
|
||||
rateLimiter := limiter.New(store, rate)
|
||||
rateLimiter := createRateLimiter(rate)
|
||||
log.Debugf("Rate limit configured with %s and %v requests per %v", config.RateLimitStore.GetString(), rate.Limit, rate.Period)
|
||||
a.Use(RateLimit(rateLimiter, rateLimitKind))
|
||||
}
|
||||
|
|
|
@ -52,6 +52,10 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ulule/limiter/v3"
|
||||
|
||||
vikunja_file "code.vikunja.io/api/pkg/modules/migration/vikunja-file"
|
||||
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/db"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
|
@ -196,6 +200,9 @@ func RegisterRoutes(e *echo.Echo) {
|
|||
registerCalDavRoutes(c)
|
||||
}
|
||||
|
||||
// healthcheck
|
||||
e.GET("/health", HealthcheckHandler)
|
||||
|
||||
// CORS_SHIT
|
||||
if config.CorsEnable.GetBool() {
|
||||
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
|
||||
|
@ -230,17 +237,26 @@ func registerAPIRoutes(a *echo.Group) {
|
|||
// Prometheus endpoint
|
||||
setupMetrics(n)
|
||||
|
||||
// Separate route for unauthenticated routes to enable rate limits for it
|
||||
ur := a.Group("")
|
||||
rate := limiter.Rate{
|
||||
Period: 60 * time.Second,
|
||||
Limit: 10,
|
||||
}
|
||||
rateLimiter := createRateLimiter(rate)
|
||||
ur.Use(RateLimit(rateLimiter, "ip"))
|
||||
|
||||
if config.AuthLocalEnabled.GetBool() {
|
||||
// User stuff
|
||||
n.POST("/login", apiv1.Login)
|
||||
n.POST("/register", apiv1.RegisterUser)
|
||||
n.POST("/user/password/token", apiv1.UserRequestResetPasswordToken)
|
||||
n.POST("/user/password/reset", apiv1.UserResetPassword)
|
||||
n.POST("/user/confirm", apiv1.UserConfirmEmail)
|
||||
ur.POST("/login", apiv1.Login)
|
||||
ur.POST("/register", apiv1.RegisterUser)
|
||||
ur.POST("/user/password/token", apiv1.UserRequestResetPasswordToken)
|
||||
ur.POST("/user/password/reset", apiv1.UserResetPassword)
|
||||
ur.POST("/user/confirm", apiv1.UserConfirmEmail)
|
||||
}
|
||||
|
||||
if config.AuthOpenIDEnabled.GetBool() {
|
||||
n.POST("/auth/openid/:provider/callback", openid.HandleCallback)
|
||||
ur.POST("/auth/openid/:provider/callback", openid.HandleCallback)
|
||||
}
|
||||
|
||||
// Testing
|
||||
|
@ -256,7 +272,7 @@ func registerAPIRoutes(a *echo.Group) {
|
|||
|
||||
// Link share auth
|
||||
if config.ServiceEnableLinkSharing.GetBool() {
|
||||
n.POST("/shares/:share/auth", apiv1.AuthenticateLinkShare)
|
||||
ur.POST("/shares/:share/auth", apiv1.AuthenticateLinkShare)
|
||||
}
|
||||
|
||||
// ===== Routes with Authetication =====
|
||||
|
@ -303,6 +319,8 @@ func registerAPIRoutes(a *echo.Group) {
|
|||
u.POST("/settings/avatar", apiv1.ChangeUserAvatarProvider)
|
||||
u.PUT("/settings/avatar/upload", apiv1.UploadAvatar)
|
||||
u.POST("/settings/general", apiv1.UpdateGeneralUserSettings)
|
||||
u.POST("/export/request", apiv1.RequestUserDataExport)
|
||||
u.POST("/export/download", apiv1.DownloadUserDataExport)
|
||||
|
||||
if config.ServiceEnableTotp.GetBool() {
|
||||
u.GET("/settings/totp", apiv1.UserTOTP)
|
||||
|
@ -563,7 +581,35 @@ func registerAPIRoutes(a *echo.Group) {
|
|||
|
||||
// Migrations
|
||||
m := a.Group("/migration")
|
||||
registerMigrations(m)
|
||||
|
||||
// List Backgrounds
|
||||
if config.BackgroundsEnabled.GetBool() {
|
||||
a.GET("/lists/:list/background", backgroundHandler.GetListBackground)
|
||||
a.DELETE("/lists/:list/background", backgroundHandler.RemoveListBackground)
|
||||
if config.BackgroundsUploadEnabled.GetBool() {
|
||||
uploadBackgroundProvider := &backgroundHandler.BackgroundProvider{
|
||||
Provider: func() background.Provider {
|
||||
return &upload.Provider{}
|
||||
},
|
||||
}
|
||||
a.PUT("/lists/:list/backgrounds/upload", uploadBackgroundProvider.UploadBackground)
|
||||
}
|
||||
if config.BackgroundsUnsplashEnabled.GetBool() {
|
||||
unsplashBackgroundProvider := &backgroundHandler.BackgroundProvider{
|
||||
Provider: func() background.Provider {
|
||||
return &unsplash.Provider{}
|
||||
},
|
||||
}
|
||||
a.GET("/backgrounds/unsplash/search", unsplashBackgroundProvider.SearchBackgrounds)
|
||||
a.POST("/lists/:list/backgrounds/unsplash", unsplashBackgroundProvider.SetBackground)
|
||||
a.GET("/backgrounds/unsplash/images/:image/thumb", unsplash.ProxyUnsplashThumb)
|
||||
a.GET("/backgrounds/unsplash/images/:image", unsplash.ProxyUnsplashImage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func registerMigrations(m *echo.Group) {
|
||||
// Wunderlist
|
||||
if config.MigrationWunderlistEnable.GetBool() {
|
||||
wunderlistMigrationHandler := &migrationHandler.MigrationWeb{
|
||||
|
@ -604,30 +650,12 @@ func registerAPIRoutes(a *echo.Group) {
|
|||
microsoftTodoMigrationHandler.RegisterRoutes(m)
|
||||
}
|
||||
|
||||
// List Backgrounds
|
||||
if config.BackgroundsEnabled.GetBool() {
|
||||
a.GET("/lists/:list/background", backgroundHandler.GetListBackground)
|
||||
a.DELETE("/lists/:list/background", backgroundHandler.RemoveListBackground)
|
||||
if config.BackgroundsUploadEnabled.GetBool() {
|
||||
uploadBackgroundProvider := &backgroundHandler.BackgroundProvider{
|
||||
Provider: func() background.Provider {
|
||||
return &upload.Provider{}
|
||||
},
|
||||
}
|
||||
a.PUT("/lists/:list/backgrounds/upload", uploadBackgroundProvider.UploadBackground)
|
||||
}
|
||||
if config.BackgroundsUnsplashEnabled.GetBool() {
|
||||
unsplashBackgroundProvider := &backgroundHandler.BackgroundProvider{
|
||||
Provider: func() background.Provider {
|
||||
return &unsplash.Provider{}
|
||||
},
|
||||
}
|
||||
a.GET("/backgrounds/unsplash/search", unsplashBackgroundProvider.SearchBackgrounds)
|
||||
a.POST("/lists/:list/backgrounds/unsplash", unsplashBackgroundProvider.SetBackground)
|
||||
a.GET("/backgrounds/unsplash/images/:image/thumb", unsplash.ProxyUnsplashThumb)
|
||||
a.GET("/backgrounds/unsplash/images/:image", unsplash.ProxyUnsplashImage)
|
||||
}
|
||||
vikunjaFileMigrationHandler := &migrationHandler.FileMigratorWeb{
|
||||
MigrationStruct: func() migration.FileMigrator {
|
||||
return &vikunja_file.FileMigrator{}
|
||||
},
|
||||
}
|
||||
vikunjaFileMigrationHandler.RegisterRoutes(m)
|
||||
}
|
||||
|
||||
func registerCalDavRoutes(c *echo.Group) {
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
// GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
|
||||
// Package swagger GENERATED BY THE COMMAND ABOVE; DO NOT EDIT
|
||||
// This file was generated by swaggo/swag
|
||||
|
||||
package swagger
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/alecthomas/template"
|
||||
"github.com/swaggo/swag"
|
||||
)
|
||||
|
||||
|
@ -16,7 +15,7 @@ var doc = `{
|
|||
"schemes": {{ marshal .Schemes }},
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"description": "{{.Description}}",
|
||||
"description": "{{escape .Description}}",
|
||||
"title": "{{.Title}}",
|
||||
"contact": {
|
||||
"name": "General Vikunja contact",
|
||||
|
@ -2921,6 +2920,80 @@ var doc = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"/migration/vikunja-file/migrate": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"JWTKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Imports all projects, tasks, notes, reminders, subtasks and files from a Vikunjda data export into Vikunja.",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"migration"
|
||||
],
|
||||
"summary": "Import all lists, tasks etc. from a Vikunja data export",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "The Vikunja export zip file.",
|
||||
"name": "import",
|
||||
"in": "formData",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"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/vikunja-file/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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/migration/wunderlist/auth": {
|
||||
"get": {
|
||||
"security": [
|
||||
|
@ -6335,7 +6408,7 @@ var doc = `{
|
|||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/v1.UserDeletionRequest"
|
||||
"$ref": "#/definitions/v1.UserPasswordConfirmation"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
@ -6427,7 +6500,7 @@ var doc = `{
|
|||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/v1.UserDeletionRequest"
|
||||
"$ref": "#/definitions/v1.UserPasswordConfirmation"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
@ -6453,6 +6526,106 @@ var doc = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"/user/export/download": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"JWTKeyAuth": []
|
||||
}
|
||||
],
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"user"
|
||||
],
|
||||
"summary": "Download a user data export.",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "User password to confirm the download.",
|
||||
"name": "password",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/v1.UserPasswordConfirmation"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Message"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Something's invalid.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/web.HTTPError"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Message"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/user/export/request": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"JWTKeyAuth": []
|
||||
}
|
||||
],
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"user"
|
||||
],
|
||||
"summary": "Request a user data export.",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "User password to confirm the data export request.",
|
||||
"name": "password",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/v1.UserPasswordConfirmation"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Message"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Something's invalid.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/web.HTTPError"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Message"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/user/password": {
|
||||
"post": {
|
||||
"security": [
|
||||
|
@ -7231,8 +7404,7 @@ var doc = `{
|
|||
"type": "string"
|
||||
},
|
||||
"info": {
|
||||
"description": "This can be used to supply extra information from an image provider to clients",
|
||||
"type": "object"
|
||||
"description": "This can be used to supply extra information from an image provider to clients"
|
||||
},
|
||||
"thumb": {
|
||||
"type": "string"
|
||||
|
@ -7382,7 +7554,9 @@ var doc = `{
|
|||
"updated": {
|
||||
"description": "A timestamp when this bucket was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.BulkAssignees": {
|
||||
|
@ -7394,7 +7568,9 @@ var doc = `{
|
|||
"items": {
|
||||
"$ref": "#/definitions/user.User"
|
||||
}
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.BulkTask": {
|
||||
|
@ -7536,7 +7712,9 @@ var doc = `{
|
|||
"updated": {
|
||||
"description": "A timestamp when this task was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.DatabaseNotifications": {
|
||||
|
@ -7555,8 +7733,7 @@ var doc = `{
|
|||
"type": "string"
|
||||
},
|
||||
"notification": {
|
||||
"description": "The actual content of the notification.",
|
||||
"type": "object"
|
||||
"description": "The actual content of the notification."
|
||||
},
|
||||
"read": {
|
||||
"description": "Whether or not to mark this notification as read or unread.\nTrue is read, false is unread.",
|
||||
|
@ -7565,7 +7742,9 @@ var doc = `{
|
|||
"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": {
|
||||
|
@ -7601,7 +7780,9 @@ var doc = `{
|
|||
"updated": {
|
||||
"description": "A timestamp when this label was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.LabelTask": {
|
||||
|
@ -7614,7 +7795,9 @@ var doc = `{
|
|||
"label_id": {
|
||||
"description": "The label id you want to associate with a task.",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.LabelTaskBulk": {
|
||||
|
@ -7626,7 +7809,9 @@ var doc = `{
|
|||
"items": {
|
||||
"$ref": "#/definitions/models.Label"
|
||||
}
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.LinkSharing": {
|
||||
|
@ -7671,15 +7856,16 @@ var doc = `{
|
|||
"updated": {
|
||||
"description": "A timestamp when this share was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.List": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"background_information": {
|
||||
"description": "Holds extra information about the background set since some background providers require attribution or similar. If not null, the background can be accessed at /lists/{listID}/background",
|
||||
"type": "object"
|
||||
"description": "Holds extra information about the background set since some background providers require attribution or similar. If not null, the background can be accessed at /lists/{listID}/background"
|
||||
},
|
||||
"created": {
|
||||
"description": "A timestamp when this list was created. You cannot change this value.",
|
||||
|
@ -7736,7 +7922,9 @@ var doc = `{
|
|||
"updated": {
|
||||
"description": "A timestamp when this list was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.ListDuplicate": {
|
||||
|
@ -7749,7 +7937,9 @@ var doc = `{
|
|||
"namespace_id": {
|
||||
"description": "The target namespace ID",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.ListUser": {
|
||||
|
@ -7776,7 +7966,9 @@ var doc = `{
|
|||
"user_id": {
|
||||
"description": "The username.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.Message": {
|
||||
|
@ -7829,7 +8021,9 @@ var doc = `{
|
|||
"updated": {
|
||||
"description": "A timestamp when this namespace was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.NamespaceUser": {
|
||||
|
@ -7856,7 +8050,9 @@ var doc = `{
|
|||
"user_id": {
|
||||
"description": "The username.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.NamespaceWithLists": {
|
||||
|
@ -7906,7 +8102,9 @@ var doc = `{
|
|||
"updated": {
|
||||
"description": "A timestamp when this namespace was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.RelatedTaskMap": {
|
||||
|
@ -7954,7 +8152,9 @@ var doc = `{
|
|||
"updated": {
|
||||
"description": "A timestamp when this filter was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.Subscription": {
|
||||
|
@ -7978,7 +8178,9 @@ var doc = `{
|
|||
"user": {
|
||||
"description": "The user who made this subscription",
|
||||
"$ref": "#/definitions/user.User"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.Task": {
|
||||
|
@ -8113,7 +8315,9 @@ var doc = `{
|
|||
"updated": {
|
||||
"description": "A timestamp when this task was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.TaskAssginee": {
|
||||
|
@ -8124,7 +8328,9 @@ var doc = `{
|
|||
},
|
||||
"user_id": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.TaskAttachment": {
|
||||
|
@ -8144,7 +8350,9 @@ var doc = `{
|
|||
},
|
||||
"task_id": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.TaskCollection": {
|
||||
|
@ -8192,7 +8400,9 @@ var doc = `{
|
|||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.TaskComment": {
|
||||
|
@ -8212,7 +8422,9 @@ var doc = `{
|
|||
},
|
||||
"updated": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.TaskRelation": {
|
||||
|
@ -8237,7 +8449,9 @@ var doc = `{
|
|||
"task_id": {
|
||||
"description": "The ID of the \"base\" task, the task which has a relation to another.",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.Team": {
|
||||
|
@ -8275,7 +8489,9 @@ var doc = `{
|
|||
"updated": {
|
||||
"description": "A timestamp when this relation was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.TeamList": {
|
||||
|
@ -8302,7 +8518,9 @@ var doc = `{
|
|||
"updated": {
|
||||
"description": "A timestamp when this relation was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.TeamMember": {
|
||||
|
@ -8323,7 +8541,9 @@ var doc = `{
|
|||
"username": {
|
||||
"description": "The username of the member. We use this to prevent automated user id entering.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.TeamNamespace": {
|
||||
|
@ -8350,7 +8570,9 @@ var doc = `{
|
|||
"updated": {
|
||||
"description": "A timestamp when this relation was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.TeamUser": {
|
||||
|
@ -8386,7 +8608,8 @@ var doc = `{
|
|||
"type": "string",
|
||||
"maxLength": 250,
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"web.Auth": {}
|
||||
}
|
||||
},
|
||||
"models.TeamWithRight": {
|
||||
|
@ -8427,7 +8650,9 @@ var doc = `{
|
|||
"updated": {
|
||||
"description": "A timestamp when this relation was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.UserWithRight": {
|
||||
|
@ -8462,7 +8687,8 @@ var doc = `{
|
|||
"type": "string",
|
||||
"maxLength": 250,
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"web.Auth": {}
|
||||
}
|
||||
},
|
||||
"notifications.DatabaseNotification": {
|
||||
|
@ -8481,8 +8707,7 @@ var doc = `{
|
|||
"type": "string"
|
||||
},
|
||||
"notification": {
|
||||
"description": "The actual content of the notification.",
|
||||
"type": "object"
|
||||
"description": "The actual content of the notification."
|
||||
},
|
||||
"read_at": {
|
||||
"description": "When this notification is marked as read, this will be updated with the current timestamp.",
|
||||
|
@ -8674,7 +8899,8 @@ var doc = `{
|
|||
"type": "string",
|
||||
"maxLength": 250,
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"web.Auth": {}
|
||||
}
|
||||
},
|
||||
"v1.LinkShareAuth": {
|
||||
|
@ -8694,14 +8920,6 @@ var doc = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"v1.UserDeletionRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"password": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1.UserDeletionRequestConfirm": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -8721,6 +8939,14 @@ var doc = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"v1.UserPasswordConfirmation": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"password": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1.UserSettings": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -8846,6 +9072,9 @@ var doc = `{
|
|||
"task_attachments_enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"task_comments_enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"totp_enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
|
@ -8920,6 +9149,13 @@ func (s *s) ReadDoc() string {
|
|||
a, _ := json.Marshal(v)
|
||||
return string(a)
|
||||
},
|
||||
"escape": func(v interface{}) string {
|
||||
// escape tabs
|
||||
str := strings.Replace(v.(string), "\t", "\\t", -1)
|
||||
// replace " with \", and if that results in \\", replace that with \\\"
|
||||
str = strings.Replace(str, "\"", "\\\"", -1)
|
||||
return strings.Replace(str, "\\\\\"", "\\\\\\\"", -1)
|
||||
},
|
||||
}).Parse(doc)
|
||||
if err != nil {
|
||||
return doc
|
||||
|
|
|
@ -2904,6 +2904,80 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/migration/vikunja-file/migrate": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"JWTKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Imports all projects, tasks, notes, reminders, subtasks and files from a Vikunjda data export into Vikunja.",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"migration"
|
||||
],
|
||||
"summary": "Import all lists, tasks etc. from a Vikunja data export",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "The Vikunja export zip file.",
|
||||
"name": "import",
|
||||
"in": "formData",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"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/vikunja-file/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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/migration/wunderlist/auth": {
|
||||
"get": {
|
||||
"security": [
|
||||
|
@ -6318,7 +6392,7 @@
|
|||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/v1.UserDeletionRequest"
|
||||
"$ref": "#/definitions/v1.UserPasswordConfirmation"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
@ -6410,7 +6484,7 @@
|
|||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/v1.UserDeletionRequest"
|
||||
"$ref": "#/definitions/v1.UserPasswordConfirmation"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
@ -6436,6 +6510,106 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/user/export/download": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"JWTKeyAuth": []
|
||||
}
|
||||
],
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"user"
|
||||
],
|
||||
"summary": "Download a user data export.",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "User password to confirm the download.",
|
||||
"name": "password",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/v1.UserPasswordConfirmation"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Message"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Something's invalid.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/web.HTTPError"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Message"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/user/export/request": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"JWTKeyAuth": []
|
||||
}
|
||||
],
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"user"
|
||||
],
|
||||
"summary": "Request a user data export.",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "User password to confirm the data export request.",
|
||||
"name": "password",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/v1.UserPasswordConfirmation"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Message"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Something's invalid.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/web.HTTPError"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Message"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/user/password": {
|
||||
"post": {
|
||||
"security": [
|
||||
|
@ -7214,8 +7388,7 @@
|
|||
"type": "string"
|
||||
},
|
||||
"info": {
|
||||
"description": "This can be used to supply extra information from an image provider to clients",
|
||||
"type": "object"
|
||||
"description": "This can be used to supply extra information from an image provider to clients"
|
||||
},
|
||||
"thumb": {
|
||||
"type": "string"
|
||||
|
@ -7365,7 +7538,9 @@
|
|||
"updated": {
|
||||
"description": "A timestamp when this bucket was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.BulkAssignees": {
|
||||
|
@ -7377,7 +7552,9 @@
|
|||
"items": {
|
||||
"$ref": "#/definitions/user.User"
|
||||
}
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.BulkTask": {
|
||||
|
@ -7519,7 +7696,9 @@
|
|||
"updated": {
|
||||
"description": "A timestamp when this task was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.DatabaseNotifications": {
|
||||
|
@ -7538,8 +7717,7 @@
|
|||
"type": "string"
|
||||
},
|
||||
"notification": {
|
||||
"description": "The actual content of the notification.",
|
||||
"type": "object"
|
||||
"description": "The actual content of the notification."
|
||||
},
|
||||
"read": {
|
||||
"description": "Whether or not to mark this notification as read or unread.\nTrue is read, false is unread.",
|
||||
|
@ -7548,7 +7726,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": {
|
||||
|
@ -7584,7 +7764,9 @@
|
|||
"updated": {
|
||||
"description": "A timestamp when this label was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.LabelTask": {
|
||||
|
@ -7597,7 +7779,9 @@
|
|||
"label_id": {
|
||||
"description": "The label id you want to associate with a task.",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.LabelTaskBulk": {
|
||||
|
@ -7609,7 +7793,9 @@
|
|||
"items": {
|
||||
"$ref": "#/definitions/models.Label"
|
||||
}
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.LinkSharing": {
|
||||
|
@ -7654,15 +7840,16 @@
|
|||
"updated": {
|
||||
"description": "A timestamp when this share was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.List": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"background_information": {
|
||||
"description": "Holds extra information about the background set since some background providers require attribution or similar. If not null, the background can be accessed at /lists/{listID}/background",
|
||||
"type": "object"
|
||||
"description": "Holds extra information about the background set since some background providers require attribution or similar. If not null, the background can be accessed at /lists/{listID}/background"
|
||||
},
|
||||
"created": {
|
||||
"description": "A timestamp when this list was created. You cannot change this value.",
|
||||
|
@ -7719,7 +7906,9 @@
|
|||
"updated": {
|
||||
"description": "A timestamp when this list was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.ListDuplicate": {
|
||||
|
@ -7732,7 +7921,9 @@
|
|||
"namespace_id": {
|
||||
"description": "The target namespace ID",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.ListUser": {
|
||||
|
@ -7759,7 +7950,9 @@
|
|||
"user_id": {
|
||||
"description": "The username.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.Message": {
|
||||
|
@ -7812,7 +8005,9 @@
|
|||
"updated": {
|
||||
"description": "A timestamp when this namespace was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.NamespaceUser": {
|
||||
|
@ -7839,7 +8034,9 @@
|
|||
"user_id": {
|
||||
"description": "The username.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.NamespaceWithLists": {
|
||||
|
@ -7889,7 +8086,9 @@
|
|||
"updated": {
|
||||
"description": "A timestamp when this namespace was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.RelatedTaskMap": {
|
||||
|
@ -7937,7 +8136,9 @@
|
|||
"updated": {
|
||||
"description": "A timestamp when this filter was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.Subscription": {
|
||||
|
@ -7961,7 +8162,9 @@
|
|||
"user": {
|
||||
"description": "The user who made this subscription",
|
||||
"$ref": "#/definitions/user.User"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.Task": {
|
||||
|
@ -8096,7 +8299,9 @@
|
|||
"updated": {
|
||||
"description": "A timestamp when this task was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.TaskAssginee": {
|
||||
|
@ -8107,7 +8312,9 @@
|
|||
},
|
||||
"user_id": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.TaskAttachment": {
|
||||
|
@ -8127,7 +8334,9 @@
|
|||
},
|
||||
"task_id": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.TaskCollection": {
|
||||
|
@ -8175,7 +8384,9 @@
|
|||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.TaskComment": {
|
||||
|
@ -8195,7 +8406,9 @@
|
|||
},
|
||||
"updated": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.TaskRelation": {
|
||||
|
@ -8220,7 +8433,9 @@
|
|||
"task_id": {
|
||||
"description": "The ID of the \"base\" task, the task which has a relation to another.",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.Team": {
|
||||
|
@ -8258,7 +8473,9 @@
|
|||
"updated": {
|
||||
"description": "A timestamp when this relation was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.TeamList": {
|
||||
|
@ -8285,7 +8502,9 @@
|
|||
"updated": {
|
||||
"description": "A timestamp when this relation was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.TeamMember": {
|
||||
|
@ -8306,7 +8525,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": {
|
||||
|
@ -8333,7 +8554,9 @@
|
|||
"updated": {
|
||||
"description": "A timestamp when this relation was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.TeamUser": {
|
||||
|
@ -8369,7 +8592,8 @@
|
|||
"type": "string",
|
||||
"maxLength": 250,
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"web.Auth": {}
|
||||
}
|
||||
},
|
||||
"models.TeamWithRight": {
|
||||
|
@ -8410,7 +8634,9 @@
|
|||
"updated": {
|
||||
"description": "A timestamp when this relation was last updated. You cannot change this value.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"web.CRUDable": {},
|
||||
"web.Rights": {}
|
||||
}
|
||||
},
|
||||
"models.UserWithRight": {
|
||||
|
@ -8445,7 +8671,8 @@
|
|||
"type": "string",
|
||||
"maxLength": 250,
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"web.Auth": {}
|
||||
}
|
||||
},
|
||||
"notifications.DatabaseNotification": {
|
||||
|
@ -8464,8 +8691,7 @@
|
|||
"type": "string"
|
||||
},
|
||||
"notification": {
|
||||
"description": "The actual content of the notification.",
|
||||
"type": "object"
|
||||
"description": "The actual content of the notification."
|
||||
},
|
||||
"read_at": {
|
||||
"description": "When this notification is marked as read, this will be updated with the current timestamp.",
|
||||
|
@ -8657,7 +8883,8 @@
|
|||
"type": "string",
|
||||
"maxLength": 250,
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"web.Auth": {}
|
||||
}
|
||||
},
|
||||
"v1.LinkShareAuth": {
|
||||
|
@ -8677,14 +8904,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"v1.UserDeletionRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"password": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1.UserDeletionRequestConfirm": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -8704,6 +8923,14 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"v1.UserPasswordConfirmation": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"password": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1.UserSettings": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -8829,6 +9056,9 @@
|
|||
"task_attachments_enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"task_comments_enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"totp_enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
|
|
|
@ -12,7 +12,6 @@ definitions:
|
|||
info:
|
||||
description: This can be used to supply extra information from an image provider
|
||||
to clients
|
||||
type: object
|
||||
thumb:
|
||||
type: string
|
||||
url:
|
||||
|
@ -125,6 +124,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:
|
||||
|
@ -133,6 +134,8 @@ definitions:
|
|||
items:
|
||||
$ref: '#/definitions/user.User'
|
||||
type: array
|
||||
web.CRUDable: {}
|
||||
web.Rights: {}
|
||||
type: object
|
||||
models.BulkTask:
|
||||
properties:
|
||||
|
@ -259,6 +262,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:
|
||||
|
@ -274,7 +279,6 @@ definitions:
|
|||
type: string
|
||||
notification:
|
||||
description: The actual content of the notification.
|
||||
type: object
|
||||
read:
|
||||
description: |-
|
||||
Whether or not to mark this notification as read or unread.
|
||||
|
@ -284,6 +288,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:
|
||||
|
@ -314,6 +320,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:
|
||||
|
@ -324,6 +332,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:
|
||||
|
@ -332,6 +342,8 @@ definitions:
|
|||
items:
|
||||
$ref: '#/definitions/models.Label'
|
||||
type: array
|
||||
web.CRUDable: {}
|
||||
web.Rights: {}
|
||||
type: object
|
||||
models.LinkSharing:
|
||||
properties:
|
||||
|
@ -372,6 +384,8 @@ definitions:
|
|||
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:
|
||||
|
@ -379,7 +393,6 @@ definitions:
|
|||
description: Holds extra information about the background set since some background
|
||||
providers require attribution or similar. If not null, the background can
|
||||
be accessed at /lists/{listID}/background
|
||||
type: object
|
||||
created:
|
||||
description: A timestamp when this list was created. You cannot change this
|
||||
value.
|
||||
|
@ -429,6 +442,8 @@ 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:
|
||||
|
@ -438,6 +453,8 @@ definitions:
|
|||
namespace_id:
|
||||
description: The target namespace ID
|
||||
type: integer
|
||||
web.CRUDable: {}
|
||||
web.Rights: {}
|
||||
type: object
|
||||
models.ListUser:
|
||||
properties:
|
||||
|
@ -461,6 +478,8 @@ definitions:
|
|||
user_id:
|
||||
description: The username.
|
||||
type: string
|
||||
web.CRUDable: {}
|
||||
web.Rights: {}
|
||||
type: object
|
||||
models.Message:
|
||||
properties:
|
||||
|
@ -504,6 +523,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:
|
||||
|
@ -527,6 +548,8 @@ definitions:
|
|||
user_id:
|
||||
description: The username.
|
||||
type: string
|
||||
web.CRUDable: {}
|
||||
web.Rights: {}
|
||||
type: object
|
||||
models.NamespaceWithLists:
|
||||
properties:
|
||||
|
@ -568,6 +591,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:
|
||||
|
@ -606,6 +631,8 @@ definitions:
|
|||
description: A timestamp when this filter was last updated. You cannot change
|
||||
this value.
|
||||
type: string
|
||||
web.CRUDable: {}
|
||||
web.Rights: {}
|
||||
type: object
|
||||
models.Subscription:
|
||||
properties:
|
||||
|
@ -624,6 +651,8 @@ definitions:
|
|||
user:
|
||||
$ref: '#/definitions/user.User'
|
||||
description: The user who made this subscription
|
||||
web.CRUDable: {}
|
||||
web.Rights: {}
|
||||
type: object
|
||||
models.Task:
|
||||
properties:
|
||||
|
@ -745,6 +774,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:
|
||||
|
@ -752,6 +783,8 @@ definitions:
|
|||
type: string
|
||||
user_id:
|
||||
type: integer
|
||||
web.CRUDable: {}
|
||||
web.Rights: {}
|
||||
type: object
|
||||
models.TaskAttachment:
|
||||
properties:
|
||||
|
@ -765,6 +798,8 @@ definitions:
|
|||
type: integer
|
||||
task_id:
|
||||
type: integer
|
||||
web.CRUDable: {}
|
||||
web.Rights: {}
|
||||
type: object
|
||||
models.TaskCollection:
|
||||
properties:
|
||||
|
@ -802,6 +837,8 @@ definitions:
|
|||
items:
|
||||
type: string
|
||||
type: array
|
||||
web.CRUDable: {}
|
||||
web.Rights: {}
|
||||
type: object
|
||||
models.TaskComment:
|
||||
properties:
|
||||
|
@ -815,6 +852,8 @@ definitions:
|
|||
type: integer
|
||||
updated:
|
||||
type: string
|
||||
web.CRUDable: {}
|
||||
web.Rights: {}
|
||||
type: object
|
||||
models.TaskRelation:
|
||||
properties:
|
||||
|
@ -834,6 +873,8 @@ definitions:
|
|||
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.Team:
|
||||
properties:
|
||||
|
@ -864,6 +905,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:
|
||||
|
@ -887,6 +930,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:
|
||||
|
@ -905,6 +950,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:
|
||||
|
@ -928,6 +975,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:
|
||||
|
@ -958,6 +1007,7 @@ definitions:
|
|||
maxLength: 250
|
||||
minLength: 1
|
||||
type: string
|
||||
web.Auth: {}
|
||||
type: object
|
||||
models.TeamWithRight:
|
||||
properties:
|
||||
|
@ -990,6 +1040,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.UserWithRight:
|
||||
properties:
|
||||
|
@ -1018,6 +1070,7 @@ definitions:
|
|||
maxLength: 250
|
||||
minLength: 1
|
||||
type: string
|
||||
web.Auth: {}
|
||||
type: object
|
||||
notifications.DatabaseNotification:
|
||||
properties:
|
||||
|
@ -1033,7 +1086,6 @@ definitions:
|
|||
type: string
|
||||
notification:
|
||||
description: The actual content of the notification.
|
||||
type: object
|
||||
read_at:
|
||||
description: When this notification is marked as read, this will be updated
|
||||
with the current timestamp.
|
||||
|
@ -1172,6 +1224,7 @@ definitions:
|
|||
maxLength: 250
|
||||
minLength: 1
|
||||
type: string
|
||||
web.Auth: {}
|
||||
type: object
|
||||
v1.LinkShareAuth:
|
||||
properties:
|
||||
|
@ -1185,11 +1238,6 @@ definitions:
|
|||
email), `upload`, `initials`, `default`.
|
||||
type: string
|
||||
type: object
|
||||
v1.UserDeletionRequest:
|
||||
properties:
|
||||
password:
|
||||
type: string
|
||||
type: object
|
||||
v1.UserDeletionRequestConfirm:
|
||||
properties:
|
||||
token:
|
||||
|
@ -1202,6 +1250,11 @@ definitions:
|
|||
old_password:
|
||||
type: string
|
||||
type: object
|
||||
v1.UserPasswordConfirmation:
|
||||
properties:
|
||||
password:
|
||||
type: string
|
||||
type: object
|
||||
v1.UserSettings:
|
||||
properties:
|
||||
default_list_id:
|
||||
|
@ -1292,6 +1345,8 @@ definitions:
|
|||
type: boolean
|
||||
task_attachments_enabled:
|
||||
type: boolean
|
||||
task_comments_enabled:
|
||||
type: boolean
|
||||
totp_enabled:
|
||||
type: boolean
|
||||
user_deletion_enabled:
|
||||
|
@ -3287,6 +3342,55 @@ paths:
|
|||
summary: Get migration status
|
||||
tags:
|
||||
- migration
|
||||
/migration/vikunja-file/migrate:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Imports all projects, tasks, notes, reminders, subtasks and files
|
||||
from a Vikunjda data export into Vikunja.
|
||||
parameters:
|
||||
- description: The Vikunja export zip file.
|
||||
in: formData
|
||||
name: import
|
||||
required: true
|
||||
type: string
|
||||
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: Import all lists, tasks etc. from a Vikunja data export
|
||||
tags:
|
||||
- migration
|
||||
/migration/vikunja-file/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
|
||||
/migration/wunderlist/auth:
|
||||
get:
|
||||
description: Returns the auth url where the user needs to get its auth code.
|
||||
|
@ -5554,7 +5658,7 @@ paths:
|
|||
name: credentials
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/v1.UserDeletionRequest'
|
||||
$ref: '#/definitions/v1.UserPasswordConfirmation'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
|
@ -5615,7 +5719,7 @@ paths:
|
|||
name: credentials
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/v1.UserDeletionRequest'
|
||||
$ref: '#/definitions/v1.UserPasswordConfirmation'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
|
@ -5634,6 +5738,68 @@ paths:
|
|||
summary: Request the deletion of the user
|
||||
tags:
|
||||
- user
|
||||
/user/export/download:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: User password to confirm the download.
|
||||
in: body
|
||||
name: password
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/v1.UserPasswordConfirmation'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/models.Message'
|
||||
"400":
|
||||
description: Something's invalid.
|
||||
schema:
|
||||
$ref: '#/definitions/web.HTTPError'
|
||||
"500":
|
||||
description: Internal server error.
|
||||
schema:
|
||||
$ref: '#/definitions/models.Message'
|
||||
security:
|
||||
- JWTKeyAuth: []
|
||||
summary: Download a user data export.
|
||||
tags:
|
||||
- user
|
||||
/user/export/request:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
parameters:
|
||||
- description: User password to confirm the data export request.
|
||||
in: body
|
||||
name: password
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/v1.UserPasswordConfirmation'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/models.Message'
|
||||
"400":
|
||||
description: Something's invalid.
|
||||
schema:
|
||||
$ref: '#/definitions/web.HTTPError'
|
||||
"500":
|
||||
description: Internal server error.
|
||||
schema:
|
||||
$ref: '#/definitions/models.Message'
|
||||
security:
|
||||
- JWTKeyAuth: []
|
||||
summary: Request a user data export.
|
||||
tags:
|
||||
- user
|
||||
/user/password:
|
||||
post:
|
||||
consumes:
|
||||
|
|
|
@ -49,7 +49,7 @@ func notifyUsersScheduledForDeletion() {
|
|||
return
|
||||
}
|
||||
|
||||
log.Debugf("Found %d users scheduled for deletion", len(users))
|
||||
log.Debugf("Found %d users scheduled for deletion to notify", len(users))
|
||||
|
||||
for _, user := range users {
|
||||
if time.Since(user.DeletionLastReminderSent) < time.Hour*24 {
|
||||
|
@ -64,6 +64,8 @@ func notifyUsersScheduledForDeletion() {
|
|||
number = 3
|
||||
}
|
||||
|
||||
log.Debugf("Notifying user %d of the deletion of their account...", user.ID)
|
||||
|
||||
err = notifications.Notify(user, &AccountDeletionNotification{
|
||||
User: user,
|
||||
NotificationNumber: number,
|
||||
|
|
|
@ -21,7 +21,7 @@ type CreatedEvent struct {
|
|||
User *User
|
||||
}
|
||||
|
||||
// TopicName defines the name for CreatedEvent
|
||||
// Name defines the name for CreatedEvent
|
||||
func (t *CreatedEvent) Name() string {
|
||||
return "user.created"
|
||||
}
|
||||
|
|
|
@ -227,11 +227,17 @@ type AccountDeletionNotification struct {
|
|||
|
||||
// ToMail returns the mail notification for AccountDeletionNotification
|
||||
func (n *AccountDeletionNotification) ToMail() *notifications.Mail {
|
||||
durationString := "in " + strconv.Itoa(n.NotificationNumber) + " days"
|
||||
|
||||
if n.NotificationNumber == 1 {
|
||||
durationString = "tomorrow"
|
||||
}
|
||||
|
||||
return notifications.NewMail().
|
||||
Subject("Your Vikunja account will be deleted in "+strconv.Itoa(n.NotificationNumber)+" days").
|
||||
Subject("Your Vikunja account will be deleted "+durationString).
|
||||
Greeting("Hi "+n.User.GetName()+",").
|
||||
Line("You recently requested the deletion of your Vikunja account.").
|
||||
Line("We will delete your account in "+strconv.Itoa(n.NotificationNumber)+" days.").
|
||||
Line("We will delete your account "+durationString+".").
|
||||
Line("If you changed your mind, simply click the link below to cancel the deletion and follow the instructions there:").
|
||||
Action("Abort the deletion", config.ServiceFrontendurl.GetString()).
|
||||
Line("Have a nice day!")
|
||||
|
|
|
@ -98,6 +98,8 @@ type User struct {
|
|||
DeletionScheduledAt time.Time `xorm:"datetime null" json:"-"`
|
||||
DeletionLastReminderSent time.Time `xorm:"datetime null" json:"-"`
|
||||
|
||||
ExportFileID int64 `xorm:"bigint null" json:"-"`
|
||||
|
||||
// A timestamp when this task was created. You cannot change this value.
|
||||
Created time.Time `xorm:"created not null" json:"created"`
|
||||
// A timestamp when this task was last updated. You cannot change this value.
|
||||
|
|
|
@ -24,13 +24,13 @@ import (
|
|||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
const issuerLocal = `local`
|
||||
const IssuerLocal = `local`
|
||||
|
||||
// CreateUser creates a new user and inserts it into the database
|
||||
func CreateUser(s *xorm.Session, user *User) (newUser *User, err error) {
|
||||
|
||||
if user.Issuer == "" {
|
||||
user.Issuer = issuerLocal
|
||||
user.Issuer = IssuerLocal
|
||||
}
|
||||
|
||||
// Check if we have all needed information
|
||||
|
@ -45,7 +45,7 @@ func CreateUser(s *xorm.Session, user *User) (newUser *User, err error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if user.Issuer == issuerLocal {
|
||||
if user.Issuer == IssuerLocal {
|
||||
// Hash the password
|
||||
user.Password, err = HashPassword(user.Password)
|
||||
if err != nil {
|
||||
|
@ -76,7 +76,7 @@ func CreateUser(s *xorm.Session, user *User) (newUser *User, err error) {
|
|||
}
|
||||
|
||||
// Dont send a mail if no mailer is configured
|
||||
if !config.MailerEnabled.GetBool() || user.Issuer != issuerLocal {
|
||||
if !config.MailerEnabled.GetBool() || user.Issuer != IssuerLocal {
|
||||
return newUserOut, err
|
||||
}
|
||||
|
||||
|
@ -112,8 +112,8 @@ func HashPassword(password string) (string, error) {
|
|||
|
||||
func checkIfUserIsValid(user *User) error {
|
||||
if user.Email == "" ||
|
||||
(user.Issuer != issuerLocal && user.Subject == "") ||
|
||||
(user.Issuer == issuerLocal && (user.Password == "" ||
|
||||
(user.Issuer != IssuerLocal && user.Subject == "") ||
|
||||
(user.Issuer == IssuerLocal && (user.Password == "" ||
|
||||
user.Username == "")) {
|
||||
return ErrNoUsernamePassword{}
|
||||
}
|
||||
|
@ -143,7 +143,7 @@ func checkIfUserExists(s *xorm.Session, user *User) (err error) {
|
|||
Subject: user.Subject,
|
||||
}
|
||||
|
||||
if user.Issuer != issuerLocal {
|
||||
if user.Issuer != IssuerLocal {
|
||||
userToCheck.Email = ""
|
||||
}
|
||||
|
||||
|
@ -155,7 +155,7 @@ func checkIfUserExists(s *xorm.Session, user *User) (err error) {
|
|||
return err
|
||||
}
|
||||
}
|
||||
if exists && user.Issuer == issuerLocal {
|
||||
if exists && user.Issuer == IssuerLocal {
|
||||
return ErrUserEmailExists{user.ID, user.Email}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public Licensee as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public Licensee
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Change to deflate to gain better compression
|
||||
// see http://golang.org/pkg/archive/zip/#pkg-constants
|
||||
const CompressionUsed = zip.Deflate
|
||||
|
||||
func WriteBytesToZip(filename string, data []byte, writer *zip.Writer) (err error) {
|
||||
header := &zip.FileHeader{
|
||||
Name: filename,
|
||||
Method: CompressionUsed,
|
||||
}
|
||||
w, err := writer.CreateHeader(header)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = w.Write(data)
|
||||
return
|
||||
}
|
||||
|
||||
// WriteFilesToZip writes a bunch of files from the db to a zip file. It exprects a map with the file id
|
||||
// as key and its content as io.ReadCloser.
|
||||
func WriteFilesToZip(files map[int64]io.ReadCloser, wr *zip.Writer) (err error) {
|
||||
for fid, file := range files {
|
||||
header := &zip.FileHeader{
|
||||
Name: "files/" + strconv.FormatInt(fid, 10),
|
||||
Method: CompressionUsed,
|
||||
}
|
||||
w, err := wr.CreateHeader(header)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = io.Copy(w, file)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error writing file %d: %s", fid, err)
|
||||
}
|
||||
_ = file.Close()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
Loading…
Reference in New Issue