Merge branch 'main' into feature/caldav-subtasks

This commit is contained in:
Erwan Martin 2023-09-10 17:49:46 +02:00
commit 5eb38b9ea3
32 changed files with 23778 additions and 381 deletions

View File

@ -121,23 +121,12 @@ steps:
when:
event: [ push, tag, pull_request ]
- name: prepare-build
image: vikunja/golang-build:latest
pull: always
environment:
GOPROXY: 'https://goproxy.kolaente.de'
depends_on: [ mage ]
commands:
- ./mage-static do-the-swag
when:
event: [ push, tag, pull_request ]
- name: build
image: vikunja/golang-build:latest
pull: always
environment:
GOPROXY: 'https://goproxy.kolaente.de'
depends_on: [ prepare-build ]
depends_on: [ mage ]
commands:
- ./mage-static build:build
when:
@ -230,7 +219,7 @@ steps:
GOPROXY: 'https://goproxy.kolaente.de'
commands:
- ./mage-static test:unit
depends_on: [ fetch-tags, prepare-build ]
depends_on: [ fetch-tags, mage ]
when:
event: [ push, tag, pull_request ]
@ -247,7 +236,7 @@ steps:
path: /db
commands:
- ./mage-static test:unit
depends_on: [ fetch-tags, prepare-build ]
depends_on: [ fetch-tags, mage ]
when:
event: [ push, tag, pull_request ]
@ -264,7 +253,7 @@ steps:
VIKUNJA_DATABASE_DATABASE: vikunjatest
commands:
- ./mage-static test:unit
depends_on: [ fetch-tags, prepare-build ]
depends_on: [ fetch-tags, mage ]
when:
event: [ push, tag, pull_request ]
@ -282,7 +271,7 @@ steps:
VIKUNJA_DATABASE_SSLMODE: disable
commands:
- ./mage-static test:unit
depends_on: [ fetch-tags, prepare-build ]
depends_on: [ fetch-tags, mage ]
when:
event: [ push, tag, pull_request ]
@ -293,7 +282,7 @@ steps:
GOPROXY: 'https://goproxy.kolaente.de'
commands:
- ./mage-static test:integration
depends_on: [ fetch-tags, prepare-build ]
depends_on: [ fetch-tags, mage ]
when:
event: [ push, tag, pull_request ]
@ -310,7 +299,7 @@ steps:
path: /db
commands:
- ./mage-static test:integration
depends_on: [ fetch-tags, prepare-build ]
depends_on: [ fetch-tags, mage ]
when:
event: [ push, tag, pull_request ]
@ -327,7 +316,7 @@ steps:
VIKUNJA_DATABASE_DATABASE: vikunjatest
commands:
- ./mage-static test:integration
depends_on: [ fetch-tags, prepare-build ]
depends_on: [ fetch-tags, mage ]
when:
event: [ push, tag, pull_request ]
@ -345,10 +334,54 @@ steps:
VIKUNJA_DATABASE_SSLMODE: disable
commands:
- ./mage-static test:integration
depends_on: [ fetch-tags, prepare-build ]
depends_on: [ fetch-tags, mage ]
when:
event: [ push, tag, pull_request ]
---
kind: pipeline
type: docker
name: generate-swagger-docs
depends_on:
- testing
workspace:
base: /go
path: src/code.vikunja.io/api
trigger:
branch:
include:
- main
event:
include:
- push
steps:
- name: generate-swagger-docs
image: vikunja/golang-build:latest
pull: always
environment:
GOPROXY: 'https://goproxy.kolaente.de'
commands:
- mage do-the-swag
- name: push
pull: always
image: appleboy/drone-git-push
depends_on:
- generate-swagger-docs
settings:
author_email: "frederik@vikunja.io"
author_name: Frederick [Bot]
branch: main
commit: true
commit_message: "[skip ci] Updated swagger docs"
remote: "ssh://git@kolaente.dev:9022/vikunja/api.git"
ssh_key:
from_secret: git_push_ssh_key
---
########
# Build a release when tagging
@ -396,7 +429,6 @@ steps:
- export PATH=$PATH:$GOPATH/bin
- go install github.com/magefile/mage
- ./mage-static release:dirs
- ./mage-static do-the-swag
depends_on: [ fetch-tags, mage ]
- name: static-build-windows
@ -743,6 +775,6 @@ steps:
- failure
---
kind: signature
hmac: d47bd1cf6f3e9be2ff3eed2039e65c8b6de2b16c1e636699f66382f941277411
hmac: b32ea5780ab6c4e57f201ec468357340349591a3026c96efd669a0b9c10f0e34
...

1
.gitignore vendored
View File

@ -27,4 +27,3 @@ vikunja-dump*
vendor/
os-packages/
mage_output_file.go
pkg/swagger/*

71
go.sum
View File

@ -82,8 +82,6 @@ github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/arran4/golang-ical v0.0.0-20230425234049-f69e132f2b0c h1:bmHPCBB1T8YZpQI+Ch0RuICrozVFmPAjiBQZvAjtpRI=
github.com/arran4/golang-ical v0.0.0-20230425234049-f69e132f2b0c/go.mod h1:BSTTrYHuM12oAL8jDdcmPdw02SBThKYWNFHQlvEG6b0=
github.com/arran4/golang-ical v0.1.0 h1:Oz0Rd5fpeNoHNFF9B9H5uYZyt1ubuZSZ3LVdHD5KvZI=
github.com/arran4/golang-ical v0.1.0/go.mod h1:BSTTrYHuM12oAL8jDdcmPdw02SBThKYWNFHQlvEG6b0=
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
@ -168,8 +166,6 @@ github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustinkirkland/golang-petname v0.0.0-20191129215211-8e5a1ed0cff0 h1:90Ly+6UfUypEF6vvvW5rQIv9opIL8CbmW9FT20LDQoY=
github.com/dustinkirkland/golang-petname v0.0.0-20191129215211-8e5a1ed0cff0/go.mod h1:V+Qd57rJe8gd4eiGzZyg4h54VLHmYVVw54iMnlAMrF8=
github.com/dustinkirkland/golang-petname v0.0.0-20230626224747-e794b9370d49 h1:6SNWi8VxQeCSwmLuTbEvJd7xvPmdS//zvMBWweZLgck=
github.com/dustinkirkland/golang-petname v0.0.0-20230626224747-e794b9370d49/go.mod h1:V+Qd57rJe8gd4eiGzZyg4h54VLHmYVVw54iMnlAMrF8=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
@ -193,8 +189,6 @@ github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
github.com/getsentry/sentry-go v0.22.0 h1:XNX9zKbv7baSEI65l+H1GEJgSeIC1c7EN5kluWaP6dM=
github.com/getsentry/sentry-go v0.22.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
github.com/getsentry/sentry-go v0.23.0 h1:dn+QRCeJv4pPt9OjVXiMcGIBIefaTJPw/h0bZWO05nE=
github.com/getsentry/sentry-go v0.23.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
@ -203,8 +197,6 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0=
github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk=
github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
@ -223,9 +215,7 @@ github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgO
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.20.0 h1:ESKJdU9ASRfaPNOPRx12IUyA1vn3R9GiE3KYD14BXdQ=
github.com/go-openapi/jsonpointer v0.20.0/go.mod h1:6PGzBjjIIumbLYysB73Klnms1mwnU4G3YHOECG3CedA=
@ -234,10 +224,10 @@ github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/a
github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M=
github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU=
github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
@ -257,8 +247,6 @@ github.com/gocarina/gocsv v0.0.0-20230616125104-99d496ca653d h1:KbPOUXFUDJxwZ04v
github.com/gocarina/gocsv v0.0.0-20230616125104-99d496ca653d/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI=
github.com/goccy/go-json v0.8.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA=
github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
@ -289,6 +277,7 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
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.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
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=
@ -343,7 +332,6 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@ -388,8 +376,6 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
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/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI=
github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
@ -463,7 +449,6 @@ github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
@ -476,15 +461,12 @@ github.com/jszwedko/go-datemath v0.1.1-0.20230526204004-640a500621d6/go.mod h1:W
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI=
github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
@ -507,10 +489,6 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/labstack/echo-jwt/v4 v4.2.0 h1:odSISV9JgcSCuhgQSV/6Io3i7nUmfM/QkBeR5GVJj5c=
github.com/labstack/echo-jwt/v4 v4.2.0/go.mod h1:MA2RqdXdEn4/uEglx0HcUOgQSyBaTh5JcaHIan3biwU=
github.com/labstack/echo/v4 v4.1.16/go.mod h1:awO+5TzAjvL8XpibdsfXxPgHr+orhtXZJZIQCVjogKI=
github.com/labstack/echo/v4 v4.10.2 h1:n1jAhnq/elIFTHr1EYpiYtyKgx4RW9ccVgkqByZaN2M=
github.com/labstack/echo/v4 v4.10.2/go.mod h1:OEyqf2//K1DFdE57vw2DRgWY0M7s65IVQO2FzvI4J5k=
github.com/labstack/echo/v4 v4.11.0 h1:4Dmi59tmrnFzOchz4EXuGjJhUfcEkU28iDKsiZVOQgw=
github.com/labstack/echo/v4 v4.11.0/go.mod h1:YuYRTSM3CHs2ybfrL8Px48bO6BAnYIN4l8wSTMP6BDQ=
github.com/labstack/echo/v4 v4.11.1 h1:dEpLU2FLg4UVmvCGPuk/APjlH6GDpbEPti61srUUUs4=
github.com/labstack/echo/v4 v4.11.1/go.mod h1:YuYRTSM3CHs2ybfrL8Px48bO6BAnYIN4l8wSTMP6BDQ=
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
@ -561,12 +539,9 @@ github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2y
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
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=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
@ -596,7 +571,6 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
@ -641,8 +615,6 @@ github.com/paulmach/orb v0.9.0 h1:MwA1DqOKtvCgm7u9RZ/pnYejTeDJPnr0+0oFajBbJqk=
github.com/paulmach/orb v0.9.0/go.mod h1:SudmOk85SXtmXAB3sLGyJ6tZy/8pdfrV0o6ef98Xc30=
github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
github.com/pelletier/go-toml/v2 v2.0.9 h1:uH2qQXheeefCCkuBBSLi7jCiSmj3VRh2+Goq2N7Xxu0=
github.com/pelletier/go-toml/v2 v2.0.9/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
@ -698,7 +670,6 @@ github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
@ -760,7 +731,6 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
@ -793,8 +763,6 @@ github.com/valyala/fasttemplate v1.2.0/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPU
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
github.com/wneessen/go-mail v0.3.9 h1:Q4DbCk3htT5DtDWKeMgNXCiHc4bBY/vv/XQPT6XDXzc=
github.com/wneessen/go-mail v0.3.9/go.mod h1:zxOlafWCP/r6FEhAaRgH4IC1vg2YXxO0Nar9u0IScZ8=
github.com/wneessen/go-mail v0.4.0 h1:Oo4HLIV8My7G9JuZkoOX6eipXQD+ACvIqURYeIzUc88=
github.com/wneessen/go-mail v0.4.0/go.mod h1:zxOlafWCP/r6FEhAaRgH4IC1vg2YXxO0Nar9u0IScZ8=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
@ -864,10 +832,6 @@ golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM=
golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I=
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@ -887,10 +851,6 @@ golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMe
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.8.0 h1:agUcRXV/+w6L9ryntYYsF2x9fQTMd4T8fiiYXAVW6Jg=
golang.org/x/image v0.8.0/go.mod h1:PwLxp3opCYg4WR2WO9P0L6ESnsD6bLTWcw8zanLMVFM=
golang.org/x/image v0.9.0 h1:QrzfX26snvCM20hIhBwuHI/ThTg18b/+kcKdXHvnR+g=
golang.org/x/image v0.9.0/go.mod h1:jtrku+n79PfroUbvDdeUWMAI+heR786BofxrbiSF+J0=
golang.org/x/image v0.11.0 h1:ds2RoQvBvYTiJkwpSFDwCcDFNX7DqjL2WsUgTNk0Ooo=
golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@ -916,8 +876,6 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs=
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -966,10 +924,6 @@ golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU=
golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@ -981,8 +935,6 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.9.0 h1:BPpt2kU7oMRq3kCHAA1tbSEshXRw1LpG2ztgDwrzuAs=
golang.org/x/oauth2 v0.9.0/go.mod h1:qYgFZaFiu6Wg24azG8bdV52QJXJGbZzIIsRCdVKzbLw=
golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8=
golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -1068,20 +1020,12 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28=
golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=
golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c=
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0=
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -1093,10 +1037,6 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -1168,8 +1108,6 @@ golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
golang.org/x/tools v0.11.1 h1:ojD5zOW8+7dOGzdnNgersm8aPfcDjhMp12UfG93NIMc=
golang.org/x/tools v0.11.1/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8=
golang.org/x/tools v0.12.1-0.20230815132531-74c255bcf846 h1:Vve/L0v7CXXuxUmaMGIEK/dEeq7uiqb5qBgQrZzIE7E=
@ -1282,8 +1220,6 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
@ -1441,15 +1377,12 @@ rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
src.techknowlogick.com/xgo v1.7.1-0.20230606181629-494bc06f804a h1:3UOdsZC8cRR4OVlNZS8YiChlp8g7eiNUYT+DAQmZt20=
src.techknowlogick.com/xgo v1.7.1-0.20230606181629-494bc06f804a/go.mod h1:31CE1YKtDOrKTk9PSnjTpe6YbO6W/0LTYZ1VskL09oU=
src.techknowlogick.com/xgo v1.7.1-0.20230711181658-617d3b65dd40 h1:elzESSqGnEJoseRrOqqdYcmiZrDo0geMdkMQqb98IaE=
src.techknowlogick.com/xgo v1.7.1-0.20230711181658-617d3b65dd40/go.mod h1:31CE1YKtDOrKTk9PSnjTpe6YbO6W/0LTYZ1VskL09oU=
src.techknowlogick.com/xormigrate v1.5.0 h1:6mWTh8d0sWjMTLUgJqiLe0e0Teu+1j+RgI7ErAeOEV0=
src.techknowlogick.com/xormigrate v1.5.0/go.mod h1:QOCnBeWralVncPn9eZlM4w/rglFK8o1vYpemzPenkBM=
xorm.io/builder v0.3.7/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
xorm.io/builder v0.3.12 h1:ASZYX7fQmy+o8UJdhlLHSW57JDOkM8DNhcAF5d0LiJM=
xorm.io/builder v0.3.12/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
xorm.io/builder v0.3.13 h1:a3jmiVVL19psGeXx8GIurTp7p0IIgqeDmwhcR6BAOAo=
xorm.io/builder v0.3.13/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=

View File

@ -177,9 +177,6 @@ func init() {
// Some variables have external dependencies (like git) which may not always be available.
func initVars() {
Tags = os.Getenv("TAGS")
if !strings.Contains(Tags, "swagger") {
Tags += " swagger"
}
setVersion()
setBinLocation()
setPkgVersion()
@ -349,10 +346,6 @@ const swaggerDocsFolderLocation = `./pkg/swagger/`
// Generates the swagger docs from the code annotations
func DoTheSwag() {
mg.Deps(initVars)
if _, err := os.Stat(swaggerDocsFolderLocation + "swagger.json"); err == nil {
fmt.Println("Swagger docs already generated, not generating. Remove the files in " + swaggerDocsFolderLocation + " and run this command again to regenerate them.")
return
}
checkAndInstallGoTool("swag", "github.com/swaggo/swag/cmd/swag")
runAndStreamOutput("swag", "init", "-g", "./pkg/routes/routes.go", "--parseDependency", "-d", RootPath, "-o", RootPath+"/pkg/swagger")
@ -469,7 +462,6 @@ func (Build) Clean() error {
// Builds a vikunja binary, ready to run
func (Build) Build() {
mg.Deps(initVars)
mg.Deps(DoTheSwag)
runAndStreamOutput("go", "build", Goflags[0], "-tags", Tags, "-ldflags", "-s -w "+Ldflags, "-o", Executable)
}
@ -479,7 +471,6 @@ type Release mg.Namespace
func (Release) Release(ctx context.Context) error {
mg.Deps(initVars)
mg.Deps(Release.Dirs)
mg.Deps(DoTheSwag)
// Run compiling in parallel to speed it up
errs, _ := errgroup.WithContext(ctx)
@ -521,7 +512,6 @@ func (Release) Dirs() error {
func runXgo(targets string) error {
mg.Deps(initVars)
mg.Deps(DoTheSwag)
checkAndInstallGoTool("xgo", "src.techknowlogick.com/xgo")
extraLdflags := `-linkmode external -extldflags "-static" `

View File

@ -23,6 +23,7 @@ import (
"time"
"code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/utils"
@ -98,8 +99,12 @@ func ParseTaskFromVTODO(content string) (vTask *models.Task, err error) {
var parsedProperties = vTodo.UnknownPropertiesIANAProperties()
// We put the vTodo details in a map to be able to handle them more easily
task := make(map[string]ics.IANAProperty)
for _, c := range parsedProperties {
var relation ics.IANAProperty
for _, c := range vTodo.UnknownPropertiesIANAProperties() {
task[c.IANAToken] = c
if strings.HasPrefix(c.IANAToken, "RELATED-TO") {
relation = c
}
}
// Parse the priority
@ -162,6 +167,19 @@ func ParseTaskFromVTODO(content string) (vTask *models.Task, err error) {
DoneAt: caldavTimeToTimestamp(task["COMPLETED"]),
}
if relation.Value != "" {
s := db.NewSession()
defer s.Close()
subtask, err := models.GetTaskSimpleByUUID(s, relation.Value)
if err != nil {
return nil, err
}
vTask.RelatedTasks = make(map[models.RelationKind][]*models.Task)
vTask.RelatedTasks[models.RelationKindSubtask] = []*models.Task{subtask}
}
if task["STATUS"].Value == "COMPLETED" {
vTask.Done = true
}

View File

@ -33,7 +33,7 @@ var dumpCmd = &cobra.Command{
Use: "dump",
Short: "Dump all vikunja data into a zip file. Includes config, files and db.",
PreRun: func(cmd *cobra.Command, args []string) {
initialize.FullInit()
initialize.FullInitWithoutAsync()
},
Run: func(cmd *cobra.Command, args []string) {
filename := "vikunja-dump_" + time.Now().Format("2006-01-02_15-03-05") + ".zip"

View File

@ -32,7 +32,7 @@ var indexCmd = &cobra.Command{
Use: "index",
Short: "Reindex all of Vikunja's data into Typesense. This will remove any existing index.",
PreRun: func(cmd *cobra.Command, args []string) {
initialize.FullInit()
initialize.FullInitWithoutAsync()
},
Run: func(cmd *cobra.Command, args []string) {
if !config.TypesenseEnabled.GetBool() {
@ -44,12 +44,12 @@ var indexCmd = &cobra.Command{
err := models.CreateTypesenseCollections()
if err != nil {
log.Critical(err.Error())
log.Criticalf("Could not create Typesense collections: %s", err.Error())
return
}
err = models.ReindexAllTasks()
if err != nil {
log.Critical(err.Error())
log.Criticalf("Could not reindex all tasks into Typesense: %s", err.Error())
return
}

View File

@ -32,7 +32,7 @@ var restoreCmd = &cobra.Command{
Short: "Restores all vikunja data from a vikunja dump.",
Args: cobra.ExactArgs(1),
PreRun: func(cmd *cobra.Command, args []string) {
initialize.FullInit()
initialize.FullInitWithoutAsync()
},
Run: func(cmd *cobra.Command, args []string) {
if err := dump.Restore(args[0]); err != nil {

View File

@ -18,7 +18,6 @@
title: testbucket3
project_id: 1
created_by_id: 1
is_done_bucket: 1
position: 3
created: 2020-04-18 21:13:52
updated: 2020-04-18 21:13:52
@ -26,7 +25,6 @@
title: testbucket4 - other project
project_id: 2
created_by_id: 1
is_done_bucket: 1
created: 2020-04-18 21:13:52
updated: 2020-04-18 21:13:52
# The following are not or only partly owned by user 1
@ -40,6 +38,7 @@
title: testbucket6
project_id: 6
created_by_id: 1
position: 1
created: 2020-04-18 21:13:52
updated: 2020-04-18 21:13:52
- id: 7
@ -137,6 +136,7 @@
title: testbucket22
project_id: 6
created_by_id: 1
position: 2
created: 2020-04-18 21:13:52
updated: 2020-04-18 21:13:52
- id: 23

View File

@ -5,6 +5,7 @@
identifier: test1
owner_id: 1
position: 3
done_bucket_id: 3
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
-
@ -14,6 +15,7 @@
identifier: test2
owner_id: 3
position: 2
done_bucket_id: 4
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
-
@ -50,6 +52,7 @@
identifier: test6
owner_id: 6
position: 6
default_bucket_id: 22
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
-

View File

@ -60,9 +60,8 @@ func InitEngines() {
}
}
// FullInit initializes all kinds of things in the right order
func FullInit() {
// FullInitWithoutAsync does a full init without any async handlers (cron or events)
func FullInitWithoutAsync() {
LightInit()
// Initialize the files handler
@ -79,6 +78,12 @@ func FullInit() {
// Start the mail daemon
mail.StartMailDaemon()
}
// FullInit initializes all kinds of things in the right order
func FullInit() {
FullInitWithoutAsync()
// Start the cron
cron.Init()

View File

@ -0,0 +1,118 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present 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"
"xorm.io/xorm/schemas"
)
type projects20230903143017 struct {
ID int64 `xorm:"bigint autoincr not null unique pk" json:"id" param:"project"`
DefaultBucketID int64 `xorm:"bigint INDEX null" json:"default_bucket_id"`
DoneBucketID int64 `xorm:"bigint INDEX null" json:"done_bucket_id"`
}
func (projects20230903143017) TableName() string {
return "projects"
}
type bucket20230903143017 struct {
ID int64 `xorm:"bigint autoincr not null unique pk" json:"id" param:"bucket"`
IsDoneBucket bool `xorm:"BOOL" json:"is_done_bucket"`
ProjectID int64 `xorm:"bigint not null" json:"project_id" param:"project"`
}
func (bucket20230903143017) TableName() string {
return "buckets"
}
const dropIsDoneBucketColSqlite20230903143017 = `
create table buckets_dg_tmp
(
id INTEGER not null
primary key autoincrement,
title TEXT not null,
project_id INTEGER not null,
"limit" INTEGER default 0,
position REAL,
created DATETIME not null,
updated DATETIME not null,
created_by_id INTEGER not null
);
insert into buckets_dg_tmp(id, title, project_id, "limit", position, created, updated, created_by_id)
select id,
title,
project_id,
"limit",
position,
created,
updated,
created_by_id
from buckets;
drop table buckets;
alter table buckets_dg_tmp
rename to buckets;
create unique index UQE_buckets_id
on buckets (id);
`
func init() {
migrations = append(migrations, &xormigrate.Migration{
ID: "20230903143017",
Description: "Move done bucket state to project + add default bucket setting",
Migrate: func(tx *xorm.Engine) (err error) {
err = tx.Sync2(projects20230903143017{})
if err != nil {
return
}
doneBuckets := []*bucket20230903143017{}
err = tx.Where("is_done_bucket = true").
Find(&doneBuckets)
if err != nil {
return
}
for _, bucket := range doneBuckets {
_, err = tx.Where("id = ?", bucket.ProjectID).
Cols("done_bucket_id").
Update(&projects20230903143017{
DoneBucketID: bucket.ID,
})
if err != nil {
return
}
}
if tx.Dialect().URI().DBType == schemas.SQLITE {
_, err = tx.Exec(dropIsDoneBucketColSqlite20230903143017)
return err
}
return dropTableColum(tx, "buckets", "is_done_bucket")
},
Rollback: func(tx *xorm.Engine) error {
return nil
},
})
}

View File

@ -55,6 +55,8 @@ func getRouteGroupName(path string) string {
finalName := strings.Join(filteredParts, "_")
switch finalName {
case "projects_tasks":
fallthrough
case "tasks_all":
return "tasks"
default:
@ -129,7 +131,13 @@ func GetAvailableAPIRoutesForToken(c echo.Context) error {
// CanDoAPIRoute checks if a token is allowed to use the current api route
func CanDoAPIRoute(c echo.Context, token *APIToken) (can bool) {
path := c.Request().URL.Path
path := c.Path()
if path == "" {
// c.Path() is empty during testing, but returns the path which the route used during registration
// which is what we need.
path = c.Request().URL.Path
}
routeGroupName := getRouteGroupName(path)
group, hasGroup := token.Permissions[routeGroupName]

View File

@ -38,8 +38,6 @@ type Bucket struct {
// How many tasks can be at the same time on this board max
Limit int64 `xorm:"default 0" json:"limit" minimum:"0" valid:"range(0|9223372036854775807)"`
// If this bucket is the "done bucket". All tasks moved into this bucket will automatically marked as done. All tasks marked as done from elsewhere will be moved into this bucket.
IsDoneBucket bool `xorm:"BOOL" json:"is_done_bucket"`
// The number of tasks currently in this bucket
Count int64 `xorm:"-" json:"count"`
@ -80,28 +78,21 @@ func getBucketByID(s *xorm.Session, id int64) (b *Bucket, err error) {
return
}
func getDefaultBucket(s *xorm.Session, projectID int64) (bucket *Bucket, err error) {
bucket = &Bucket{}
func getDefaultBucketID(s *xorm.Session, project *Project) (bucketID int64, err error) {
if project.DefaultBucketID != 0 {
return project.DefaultBucketID, nil
}
bucket := &Bucket{}
_, err = s.
Where("project_id = ?", projectID).
Where("project_id = ?", project.ID).
OrderBy("position asc").
Get(bucket)
return
}
func getDoneBucketForProject(s *xorm.Session, projectID int64) (bucket *Bucket, err error) {
bucket = &Bucket{}
exists, err := s.
Where("project_id = ? and is_done_bucket = ?", projectID, true).
Get(bucket)
if err != nil {
return nil, err
}
if !exists {
bucket = nil
return 0, err
}
return
return bucket.ID, nil
}
// ReadAll returns all buckets with their tasks for a certain project
@ -287,29 +278,11 @@ func (b *Bucket) Create(s *xorm.Session, a web.Auth) (err error) {
// @Failure 500 {object} models.Message "Internal error"
// @Router /projects/{projectID}/buckets/{bucketID} [post]
func (b *Bucket) Update(s *xorm.Session, _ web.Auth) (err error) {
doneBucket, err := getDoneBucketForProject(s, b.ProjectID)
if err != nil {
return err
}
if doneBucket != nil && doneBucket.IsDoneBucket && b.IsDoneBucket && doneBucket.ID != b.ID {
// When the current bucket will be the new done bucket, the old one should not be the done bucket anymore
doneBucket.IsDoneBucket = false
_, err = s.
Where("id = ?", doneBucket.ID).
Cols("is_done_bucket").
Update(doneBucket)
if err != nil {
return
}
}
_, err = s.
Where("id = ?", b.ID).
Cols(
"title",
"limit",
"is_done_bucket",
"position",
).
Update(b)
@ -350,15 +323,19 @@ func (b *Bucket) Delete(s *xorm.Session, _ web.Auth) (err error) {
}
// Get the default bucket
defaultBucket, err := getDefaultBucket(s, b.ProjectID)
p, err := GetProjectSimpleByID(s, b.ProjectID)
if err != nil {
return
}
defaultBucketID, err := getDefaultBucketID(s, p)
if err != nil {
return err
}
// Remove all associations of tasks to that bucket
_, err = s.
Where("bucket_id = ?", b.ID).
Cols("bucket_id").
Update(&Task{BucketID: defaultBucket.ID})
Update(&Task{BucketID: defaultBucketID})
return
}

View File

@ -217,28 +217,4 @@ func TestBucket_Update(t *testing.T) {
testAndAssertBucketUpdate(t, b, s)
})
t.Run("old done bucket should be unset", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
s := db.NewSession()
defer s.Close()
b := &Bucket{
ID: 1,
ProjectID: 1,
IsDoneBucket: true,
}
err := b.Update(s, &user.User{ID: 1})
assert.NoError(t, err)
db.AssertExists(t, "buckets", map[string]interface{}{
"id": 1,
"project_id": 1,
"is_done_bucket": true,
}, false)
db.AssertExists(t, "buckets", map[string]interface{}{
"id": 3,
"project_id": 1,
"is_done_bucket": false,
}, false)
})
}

View File

@ -51,6 +51,11 @@ type Project struct {
ParentProjectID int64 `xorm:"bigint INDEX null" json:"parent_project_id"`
ParentProject *Project `xorm:"-" json:"-"`
// The ID of the bucket where new tasks without a bucket are added to. By default, this is the leftmost bucket in a project.
DefaultBucketID int64 `xorm:"bigint INDEX null" json:"default_bucket_id"`
// If tasks are moved to the done bucket, they are marked as done. If they are marked as done individually, they are moved into the done bucket.
DoneBucketID int64 `xorm:"bigint INDEX null" json:"done_bucket_id"`
// The user who created this project.
Owner *user.User `xorm:"-" json:"owner" valid:"-"`
@ -363,18 +368,20 @@ func getUserProjectsStatement(parentProjectIDs []int64, userID int64, search str
}
var parentCondition builder.Cond
parentCondition = builder.Or(
builder.IsNull{"l.parent_project_id"},
builder.Eq{"l.parent_project_id": 0},
// else check for shared sub projects with a parent
builder.And(
builder.Or(
builder.NotNull{"tm2.user_id"},
builder.NotNull{"ul.user_id"},
if search == "" {
parentCondition = builder.Or(
builder.IsNull{"l.parent_project_id"},
builder.Eq{"l.parent_project_id": 0},
// else check for shared sub projects with a parent
builder.And(
builder.Or(
builder.NotNull{"tm2.user_id"},
builder.NotNull{"ul.user_id"},
),
builder.NotNull{"l.parent_project_id"},
),
builder.NotNull{"l.parent_project_id"},
),
)
)
}
projectCol := "id"
if len(parentProjectIDs) > 0 {
parentCondition = builder.In("l.parent_project_id", parentProjectIDs)
@ -778,6 +785,8 @@ func UpdateProject(s *xorm.Session, project *Project, auth web.Auth, updateProje
"hex_color",
"parent_project_id",
"position",
"done_bucket_id",
"default_bucket_id",
}
if project.Description != "" {
colsToUpdate = append(colsToUpdate, "description")

View File

@ -371,6 +371,11 @@ func (bt *BulkTask) GetTasksByIDs(s *xorm.Session) (err error) {
return
}
func GetTaskSimpleByUUID(s *xorm.Session, uid string) (task *Task, err error) {
_, err = s.In("uid", uid).Get(task)
return
}
// GetTasksByUIDs gets all tasks from a bunch of uids
func GetTasksByUIDs(s *xorm.Session, uids []string, a web.Auth) (tasks []*Task, err error) {
tasks = []*Task{}
@ -629,17 +634,18 @@ func checkBucketLimit(s *xorm.Session, t *Task, bucket *Bucket) (err error) {
}
// Contains all the task logic to figure out what bucket to use for this task.
func setTaskBucket(s *xorm.Session, task *Task, originalTask *Task, doCheckBucketLimit bool) (targetBucket *Bucket, err error) {
// Make sure we have a bucket
var bucket *Bucket
if task.Done && originalTask != nil && !originalTask.Done {
bucket, err := getDoneBucketForProject(s, task.ProjectID)
func setTaskBucket(s *xorm.Session, task *Task, originalTask *Task, doCheckBucketLimit bool, project *Project) (targetBucket *Bucket, err error) {
if project == nil {
project, err = GetProjectSimpleByID(s, task.ProjectID)
if err != nil {
return nil, err
}
if bucket != nil {
task.BucketID = bucket.ID
}
}
var bucket *Bucket
if task.Done && originalTask != nil && !originalTask.Done {
task.BucketID = project.DoneBucketID
}
if task.BucketID == 0 && originalTask != nil && originalTask.BucketID != 0 {
@ -648,11 +654,10 @@ func setTaskBucket(s *xorm.Session, task *Task, originalTask *Task, doCheckBucke
// Either no bucket was provided or the task was moved between projects
if task.BucketID == 0 || (originalTask != nil && task.ProjectID != 0 && originalTask.ProjectID != task.ProjectID) {
bucket, err = getDefaultBucket(s, task.ProjectID)
task.BucketID, err = getDefaultBucketID(s, project)
if err != nil {
return
}
task.BucketID = bucket.ID
}
if bucket == nil {
@ -676,7 +681,7 @@ func setTaskBucket(s *xorm.Session, task *Task, originalTask *Task, doCheckBucke
}
}
if bucket.IsDoneBucket && originalTask != nil && !originalTask.Done {
if bucket.ID == project.DoneBucketID && originalTask != nil && !originalTask.Done {
task.Done = true
}
@ -717,7 +722,7 @@ func getNextTaskIndex(s *xorm.Session, projectID int64) (nextIndex int64, err er
// @Failure 400 {object} web.HTTPError "Invalid task object provided."
// @Failure 403 {object} web.HTTPError "The user does not have access to the project"
// @Failure 500 {object} models.Message "Internal error"
// @Router /projects/{id} [put]
// @Router /projects/{id}/tasks [put]
func (t *Task) Create(s *xorm.Session, a web.Auth) (err error) {
return createTask(s, t, a, true)
}
@ -732,7 +737,7 @@ func createTask(s *xorm.Session, t *Task, a web.Auth, updateAssignees bool) (err
}
// Check if the project exists
l, err := GetProjectSimpleByID(s, t.ProjectID)
p, err := GetProjectSimpleByID(s, t.ProjectID)
if err != nil {
return err
}
@ -749,7 +754,7 @@ func createTask(s *xorm.Session, t *Task, a web.Auth, updateAssignees bool) (err
}
// Get the default bucket and move the task there
_, err = setTaskBucket(s, t, nil, true)
_, err = setTaskBucket(s, t, nil, true, nil)
if err != nil {
return
}
@ -781,7 +786,7 @@ func createTask(s *xorm.Session, t *Task, a web.Auth, updateAssignees bool) (err
return err
}
t.setIdentifier(l)
t.setIdentifier(p)
if t.IsFavorite {
if err := addToFavorites(s, t.ID, createdBy, FavoriteKindTask); err != nil {
@ -838,14 +843,18 @@ func (t *Task) Update(s *xorm.Session, a web.Auth) (err error) {
// Old task has the stored reminders
ot.Reminders = reminders
targetBucket, err := setTaskBucket(s, t, &ot, t.BucketID != 0 && t.BucketID != ot.BucketID)
targetBucket, err := setTaskBucket(s, t, &ot, t.BucketID != 0 && t.BucketID != ot.BucketID, nil)
if err != nil {
return err
}
// If the task was moved into the done bucket and the task has a repeating cycle we should not update
// the bucket.
if targetBucket.IsDoneBucket && t.RepeatAfter > 0 {
project, err := GetProjectSimpleByID(s, t.ProjectID)
if err != nil {
return err
}
if targetBucket.ID == project.DoneBucketID && t.RepeatAfter > 0 {
t.Done = true // This will trigger the correct re-scheduling of the task (happening in updateDone later)
t.BucketID = ot.BucketID
}

View File

@ -170,6 +170,23 @@ func TestTask_Create(t *testing.T) {
assert.Error(t, err)
assert.True(t, IsErrBucketLimitExceeded(err))
})
t.Run("default bucket different", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
s := db.NewSession()
defer s.Close()
task := &Task{
Title: "Lorem",
Description: "Lorem Ipsum Dolor",
ProjectID: 6,
}
err := task.Create(s, usr)
assert.NoError(t, err)
db.AssertExists(t, "tasks", map[string]interface{}{
"id": task.ID,
"bucket_id": 22, // default bucket of project 6 but with a position of 2
}, false)
})
}
func TestTask_Update(t *testing.T) {

View File

@ -20,17 +20,16 @@ import (
"fmt"
"time"
"code.vikunja.io/api/pkg/cron"
"code.vikunja.io/api/pkg/log"
"xorm.io/xorm"
"code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/cron"
"code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/user"
"github.com/typesense/typesense-go/typesense"
"github.com/typesense/typesense-go/typesense/api"
"github.com/typesense/typesense-go/typesense/api/pointer"
"xorm.io/xorm"
)
type TypesenseSync struct {
@ -208,46 +207,66 @@ func ReindexAllTasks() (err error) {
s := db.NewSession()
defer s.Close()
_, err = s.Where("collection = ?", "tasks").Delete(&TypesenseSync{})
if err != nil {
return fmt.Errorf("could not delete old sync status: %s", err.Error())
}
currentSync := &TypesenseSync{
Collection: "tasks",
SyncStartedAt: time.Now(),
}
_, err = s.Insert(currentSync)
if err != nil {
return err
return fmt.Errorf("could not update last sync: %s", err.Error())
}
err = s.Find(tasks)
if err != nil {
return err
return fmt.Errorf("could not get all tasks: %s", err.Error())
}
err = reindexTasks(s, tasks)
if err != nil {
return err
return fmt.Errorf("could not reindex all tasks: %s", err.Error())
}
currentSync.SyncFinishedAt = time.Now()
_, err = s.Where("collection = ?", "tasks").
Cols("sync_finished_at").
Update(currentSync)
if err != nil {
return fmt.Errorf("could update last sync state: %s", err.Error())
}
return
}
func reindexTasks(s *xorm.Session, tasks map[int64]*Task) (err error) {
err = addMoreInfoToTasks(s, tasks, &user.User{ID: 1})
if err != nil {
return err
return fmt.Errorf("could not fetch more task info: %s", err.Error())
}
projects := make(map[int64]*Project)
typesenseTasks := []interface{}{}
for _, task := range tasks {
searchTask := convertTaskToTypesenseTask(task)
p, has := projects[task.ProjectID]
if !has {
p, err = GetProjectSimpleByID(s, task.ProjectID)
if err != nil {
return fmt.Errorf("could not fetch project %d: %s", task.ProjectID, err.Error())
}
projects[task.ProjectID] = p
}
comment := &TaskComment{TaskID: task.ID}
searchTask.Comments, _, _, err = comment.ReadAll(s, task.CreatedBy, "", -1, -1)
searchTask.Comments, _, _, err = comment.ReadAll(s, &user.User{ID: p.OwnerID}, "", -1, -1)
if err != nil {
return err
return fmt.Errorf("could not fetch comments for task %d: %s", task.ID, err.Error())
}
typesenseTasks = append(typesenseTasks, searchTask)
@ -260,6 +279,7 @@ func reindexTasks(s *xorm.Session, tasks map[int64]*Task) (err error) {
BatchSize: pointer.Int(100),
})
if err != nil {
log.Errorf("Could not upsert task into Typesense", err)
return err
}

View File

@ -52,13 +52,44 @@ func insertFromStructure(s *xorm.Session, str []*models.ProjectWithTasksAndBucke
labels := make(map[string]*models.Label)
archivedProjects := []int64{}
childRelations := make(map[int64][]int64) // old id is the key, slice of old children ids
projectsByOldID := make(map[int64]*models.Project) // old id is the key
// Create all projects
for _, p := range str {
oldID := p.ID
if p.ParentProjectID != 0 {
childRelations[p.ParentProjectID] = append(childRelations[p.ParentProjectID], oldID)
}
p.ID = 0
err = createProjectWithChildren(s, p, 0, &archivedProjects, labels, user)
err = createProject(s, p, &archivedProjects, labels, user)
if err != nil {
return err
}
projectsByOldID[oldID] = &p.Project
}
// parent / child relations
for parentID, children := range childRelations {
parent, has := projectsByOldID[parentID]
if !has {
log.Debugf("[creating structure] could not find parentID project with old id %d", parentID)
continue
}
for _, childID := range children {
child, has := projectsByOldID[childID]
if !has {
log.Debugf("[creating structure] could not find child project with old id %d for parent project with old id %d", childID, parentID)
continue
}
child.ParentProjectID = parent.ID
err = child.Update(s, user)
if err != nil {
return err
}
}
}
if len(archivedProjects) > 0 {
@ -76,30 +107,18 @@ func insertFromStructure(s *xorm.Session, str []*models.ProjectWithTasksAndBucke
return nil
}
func createProjectWithChildren(s *xorm.Session, project *models.ProjectWithTasksAndBuckets, parentProjectID int64, archivedProjectIDs *[]int64, labels map[string]*models.Label, user *user.User) (err error) {
err = createProjectWithEverything(s, project, parentProjectID, archivedProjectIDs, labels, user)
func createProject(s *xorm.Session, project *models.ProjectWithTasksAndBuckets, archivedProjectIDs *[]int64, labels map[string]*models.Label, user *user.User) (err error) {
err = createProjectWithEverything(s, project, archivedProjectIDs, labels, user)
if err != nil {
return err
}
log.Debugf("[creating structure] Created project %d", project.ID)
if len(project.ChildProjects) > 0 {
log.Debugf("[creating structure] Creating %d projects", len(project.ChildProjects))
// Create all projects
for _, cp := range project.ChildProjects {
err = createProjectWithChildren(s, cp, project.ID, archivedProjectIDs, labels, user)
if err != nil {
return err
}
}
}
return
}
func createProjectWithEverything(s *xorm.Session, project *models.ProjectWithTasksAndBuckets, parentProjectID int64, archivedProjects *[]int64, labels map[string]*models.Label, user *user.User) (err error) {
func createProjectWithEverything(s *xorm.Session, project *models.ProjectWithTasksAndBuckets, archivedProjects *[]int64, labels map[string]*models.Label, user *user.User) (err error) {
// The tasks and bucket slices are going to be reset during the creation of the project, so we rescue it here
// to be able to still loop over them aftere the project was created.
tasks := project.Tasks
@ -114,9 +133,12 @@ func createProjectWithEverything(s *xorm.Session, project *models.ProjectWithTas
project.IsArchived = false
}
project.ParentProjectID = parentProjectID
project.ID = 0
err = project.Create(s, user)
if err != nil && models.IsErrProjectIdentifierIsNotUnique(err) {
project.Identifier = ""
err = project.Create(s, user)
}
if err != nil {
return
}
@ -174,15 +196,18 @@ func createProjectWithEverything(s *xorm.Session, project *models.ProjectWithTas
}
}
tasksByOldID := make(map[int64]*models.TaskWithComments, len(tasks))
// Create all tasks
for _, t := range tasks {
setBucketOrDefault(&t.Task)
oldid := t.ID
t.ProjectID = project.ID
err = t.Create(s, user)
if err != nil {
return
}
tasksByOldID[oldid] = t
log.Debugf("[creating structure] Created task %d", t.ID)
if len(t.RelatedTasks) > 0 {
@ -198,13 +223,15 @@ func createProjectWithEverything(s *xorm.Session, project *models.ProjectWithTas
for _, rt := range tasks {
// First create the related tasks if they do not exist
if rt.ID == 0 {
if _, exists := tasksByOldID[rt.ID]; !exists || rt.ID == 0 {
oldid := rt.ID
setBucketOrDefault(rt)
rt.ProjectID = t.ProjectID
err = rt.Create(s, user)
if err != nil {
return
}
tasksByOldID[oldid] = &models.TaskWithComments{Task: *rt}
log.Debugf("[creating structure] Created related task %d", rt.ID)
}
@ -214,8 +241,11 @@ func createProjectWithEverything(s *xorm.Session, project *models.ProjectWithTas
OtherTaskID: rt.ID,
RelationKind: kind,
}
if ttt, exists := tasksByOldID[rt.ID]; exists {
taskRel.OtherTaskID = ttt.ID
}
err = taskRel.Create(s, user)
if err != nil {
if err != nil && !models.IsErrRelationAlreadyExists(err) {
return
}

View File

@ -35,6 +35,7 @@ func TestInsertFromStructure(t *testing.T) {
testStructure := []*models.ProjectWithTasksAndBuckets{
{
Project: models.Project{
ID: 1,
Title: "Test1",
Description: "Lorem Ipsum",
},
@ -45,113 +46,112 @@ func TestInsertFromStructure(t *testing.T) {
},
},
},
ChildProjects: []*models.ProjectWithTasksAndBuckets{
},
{
Project: models.Project{
Title: "Testproject1",
Description: "Something",
ParentProjectID: 1,
},
Buckets: []*models.Bucket{
{
Project: models.Project{
Title: "Testproject1",
Description: "Something",
ID: 1234,
Title: "Test Bucket",
},
},
Tasks: []*models.TaskWithComments{
{
Task: models.Task{
Title: "Task1",
Description: "Lorem",
},
Buckets: []*models.Bucket{
{
ID: 1234,
Title: "Test Bucket",
},
{
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",
},
},
},
},
Tasks: []*models.TaskWithComments{
{
Task: models.Task{
Title: "Task1",
Description: "Lorem",
},
},
{
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{
{
File: &files.File{
Name: "testfile",
Size: 4,
FileContent: []byte{1, 2, 3, 4},
},
},
},
{
Task: models.Task{
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",
},
},
{
Task: models.Task{
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",
},
},
},
},
{
Task: models.Task{
Title: "Task in a bucket",
BucketID: 1234,
},
},
{
Task: models.Task{
Title: "Task in a nonexisting bucket",
BucketID: 1111,
},
},
{
Task: models.Task{
Title: "Task with same label",
Labels: []*models.Label{
{
Title: "Label1",
HexColor: "ff00ff",
},
},
},
},
{
Task: models.Task{
Title: "Task in a bucket",
BucketID: 1234,
},
},
{
Task: models.Task{
Title: "Task in a nonexisting bucket",
BucketID: 1111,
},
},
},
},
}
err := InsertFromStructure(testStructure, u)
assert.NoError(t, err)
db.AssertExists(t, "projects", map[string]interface{}{
"title": testStructure[0].ChildProjects[0].Title,
"description": testStructure[0].ChildProjects[0].Description,
"title": testStructure[1].Title,
"description": testStructure[1].Description,
}, false)
db.AssertExists(t, "tasks", map[string]interface{}{
"title": testStructure[0].ChildProjects[0].Tasks[5].Title,
"bucket_id": testStructure[0].ChildProjects[0].Buckets[0].ID,
"title": testStructure[1].Tasks[5].Title,
"bucket_id": testStructure[1].Buckets[0].ID,
}, false)
db.AssertMissing(t, "tasks", map[string]interface{}{
"title": testStructure[0].ChildProjects[0].Tasks[6].Title,
"title": testStructure[1].Tasks[6].Title,
"bucket_id": 1111, // No task with that bucket should exist
})
db.AssertExists(t, "tasks", map[string]interface{}{
"title": testStructure[0].Tasks[0].Title,
}, false)
assert.NotEqual(t, 0, testStructure[0].ChildProjects[0].Tasks[0].BucketID) // Should get the default bucket
assert.NotEqual(t, 0, testStructure[0].ChildProjects[0].Tasks[6].BucketID) // Should get the default bucket
assert.NotEqual(t, 0, testStructure[1].Tasks[0].BucketID) // Should get the default bucket
assert.NotEqual(t, 0, testStructure[1].Tasks[6].BucketID) // Should get the default bucket
})
}

View File

@ -30,6 +30,7 @@ import (
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/modules/migration"
"code.vikunja.io/api/pkg/user"
vversion "code.vikunja.io/api/pkg/version"
"github.com/hashicorp/go-version"
)
@ -119,17 +120,22 @@ func (v *FileMigrator) Migrate(user *user.User, file io.ReaderAt, size int64) er
return fmt.Errorf("could not read version file: %w", err)
}
dumpedVersion, err := version.NewVersion(bufVersion.String())
if err != nil {
return err
}
minVersion, err := version.NewVersion("0.20.1+61")
if err != nil {
return err
}
versionString := bufVersion.String()
if versionString == "dev" && vversion.Version == "dev" {
log.Debugf(logPrefix + "Importing from dev version")
} else {
dumpedVersion, err := version.NewVersion(bufVersion.String())
if err != nil {
return err
}
minVersion, err := version.NewVersion("0.20.1+61")
if err != nil {
return err
}
if dumpedVersion.LessThan(minVersion) {
return fmt.Errorf("export was created with an older version, need at least %s but the export needs at least %s", dumpedVersion, minVersion)
if dumpedVersion.LessThan(minVersion) {
return fmt.Errorf("export was created with an older version, need at least %s but the export needs at least %s", dumpedVersion, minVersion)
}
}
//////

View File

@ -20,6 +20,7 @@ import (
"encoding/json"
"code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/log"
)
// Notification is a notification which can be sent via mail or db.
@ -40,10 +41,13 @@ type NotificationWithSubject interface {
// Notifiable is an entity which can be notified. Usually a user.
type Notifiable interface {
// Should return the email address this notifiable has.
// RouteForMail should return the email address this notifiable has.
RouteForMail() (string, error)
// Should return the id of the notifiable entity
// RouteForDB should return the id of the notifiable entity to save it in the database.
RouteForDB() int64
// ShouldNotify provides a last-minute way to cancel a notification. It will be called immediately before
// sending a notification.
ShouldNotify() (should bool, err error)
}
// Notify notifies a notifiable of a notification
@ -53,6 +57,12 @@ func Notify(notifiable Notifiable, notification Notification) (err error) {
return nil
}
should, err := notifiable.ShouldNotify()
if err != nil || !should {
log.Debugf("Not notifying user %d because they are disabled", notifiable.RouteForDB())
return err
}
err = notifyMail(notifiable, notification)
if err != nil {
return

View File

@ -50,6 +50,7 @@ func (n *testNotification) Name() string {
}
type testNotifiable struct {
ShouldSendNotification bool
}
// RouteForMail routes a test notification for mail
@ -62,30 +63,64 @@ func (t *testNotifiable) RouteForDB() int64 {
return 42
}
func TestNotify(t *testing.T) {
tn := &testNotification{
Test: "somethingsomething",
OtherValue: 42,
}
tnf := &testNotifiable{}
err := Notify(tnf, tn)
assert.NoError(t, err)
vals := map[string]interface{}{
"notifiable_id": 42,
"notification": "'{\"other_value\":42,\"test\":\"somethingsomething\"}'",
}
if db.Type() == schemas.POSTGRES {
vals["notification::jsonb"] = vals["notification"].(string) + "::jsonb"
delete(vals, "notification")
}
if db.Type() == schemas.SQLITE {
vals["CAST(notification AS BLOB)"] = "CAST(" + vals["notification"].(string) + " AS BLOB)"
delete(vals, "notification")
}
db.AssertExists(t, "notifications", vals, true)
func (t *testNotifiable) ShouldNotify() (should bool, err error) {
return t.ShouldSendNotification, nil
}
func TestNotify(t *testing.T) {
t.Run("normal", func(t *testing.T) {
s := db.NewSession()
defer s.Close()
_, err := s.Exec("delete from notifications")
assert.NoError(t, err)
tn := &testNotification{
Test: "somethingsomething",
OtherValue: 42,
}
tnf := &testNotifiable{
ShouldSendNotification: true,
}
err = Notify(tnf, tn)
assert.NoError(t, err)
vals := map[string]interface{}{
"notifiable_id": 42,
"notification": "'{\"other_value\":42,\"test\":\"somethingsomething\"}'",
}
if db.Type() == schemas.POSTGRES {
vals["notification::jsonb"] = vals["notification"].(string) + "::jsonb"
delete(vals, "notification")
}
if db.Type() == schemas.SQLITE {
vals["CAST(notification AS BLOB)"] = "CAST(" + vals["notification"].(string) + " AS BLOB)"
delete(vals, "notification")
}
db.AssertExists(t, "notifications", vals, true)
})
t.Run("disabled notifiable", func(t *testing.T) {
s := db.NewSession()
defer s.Close()
_, err := s.Exec("delete from notifications")
assert.NoError(t, err)
tn := &testNotification{
Test: "somethingsomething",
OtherValue: 42,
}
tnf := &testNotifiable{
ShouldSendNotification: false,
}
err = Notify(tnf, tn)
assert.NoError(t, err)
db.AssertMissing(t, "notifications", map[string]interface{}{
"notifiable_id": 42,
})
})
}

View File

@ -134,9 +134,7 @@ func (vcls *VikunjaCaldavProjectStorage) GetResourcesByList(rpaths []string) ([]
var uids []string
for _, path := range rpaths {
parts := strings.Split(path, "/")
uid := []rune(parts[4]) // The 4th part is the id with ".ics" suffix
endlen := len(uid) - len(".ics") // ".ics" are 4 bytes
uids = append(uids, string(uid[:endlen]))
uids = append(uids, strings.TrimSuffix(parts[4], ".ics"))
}
s := db.NewSession()

View File

@ -379,7 +379,7 @@ func registerAPIRoutes(a *echo.Group) {
return &models.Task{}
},
}
a.PUT("/projects/:project", taskHandler.CreateWeb)
a.PUT("/projects/:project/tasks", taskHandler.CreateWeb)
a.GET("/tasks/:projecttask", taskHandler.ReadOneWeb)
a.GET("/tasks/all", taskCollectionHandler.ReadAllWeb)
a.DELETE("/tasks/:projecttask", taskHandler.DeleteWeb)

8663
pkg/swagger/docs.go Normal file

File diff suppressed because it is too large Load Diff

8638
pkg/swagger/swagger.json Normal file

File diff suppressed because it is too large Load Diff

5911
pkg/swagger/swagger.yaml Normal file

File diff suppressed because it is too large Load Diff

View File

@ -137,6 +137,17 @@ func (u *User) RouteForDB() int64 {
return u.ID
}
func (u *User) ShouldNotify() (bool, error) {
s := db.NewSession()
defer s.Close()
user, err := getUser(s, &User{ID: u.ID}, true)
if err != nil {
return false, err
}
return user.Status != StatusDisabled, err
}
// GetID implements the Auth interface
func (u *User) GetID() int64 {
return u.ID

View File

@ -1,26 +0,0 @@
// Vikunja is a to-do list application to facilitate your life.
// Copyright 2018-present2023 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/>.
//go:build swagger
// +build swagger
package version
import "code.vikunja.io/api/pkg/swagger"
func init() {
// Additional swagger information
swagger.SwaggerInfo.Version = Version
}

View File

@ -16,8 +16,15 @@
package version
import "code.vikunja.io/api/pkg/swagger"
// This package holds the version info
// It is an own package to avoid import cycles
// Version sets the version to be printed to the user. Gets overwritten by "make release" or "make build" with last git commit or tag.
var Version = "dev"
func init() {
// Additional swagger information
swagger.SwaggerInfo.Version = Version
}