Merge branch 'main' into fix-trello-checklist

This commit is contained in:
kolaente 2024-03-09 09:33:19 +01:00
commit f2701f2878
Signed by: konrad
GPG Key ID: F40E70337AB24C9B
58 changed files with 2995 additions and 3343 deletions

View File

@ -3,6 +3,11 @@ kind: pipeline
type: docker
name: build-and-test
trigger:
event:
exclude:
- cron
workspace:
base: /go
path: src/code.vikunja.io/api
@ -528,6 +533,9 @@ trigger:
ref:
- refs/heads/main
- "refs/tags/**"
event:
exclude:
- cron
steps:
# Needed to get the versions right as they depend on tags
@ -808,6 +816,9 @@ trigger:
ref:
- refs/heads/main
- "refs/tags/**"
event:
exclude:
- cron
steps:
- name: fetch-tags
@ -1145,6 +1156,9 @@ trigger:
ref:
- refs/heads/main
- "refs/tags/**"
event:
exclude:
- cron
steps:
- name: fetch-tags
@ -1360,6 +1374,9 @@ trigger:
ref:
- refs/heads/main
- "refs/tags/**"
event:
exclude:
- cron
depends_on:
- build-and-test
@ -1384,6 +1401,6 @@ steps:
- failure
---
kind: signature
hmac: 008b86263a8d03806da907c128a837a380901f1a2190a658c22d4e06cadc1b64
hmac: a569410ea13ad83c15c7606ed44b17b6bac0eb66d668344dfbf008c9448b4af5
...

View File

@ -44,3 +44,4 @@ ENV VIKUNJA_SERVICE_ROOTPATH=/app/vikunja/
ENV VIKUNJA_DATABASE_PATH=/db/vikunja.db
COPY --from=apibuilder /build/vikunja-* vikunja
COPY --from=apibuilder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/

View File

@ -6,7 +6,7 @@ service:
# The duration of the issued JWT tokens in seconds.
# The default is 259200 seconds (3 Days).
jwtttl: 259200
# The duration of the "remember me" time in seconds. When the login request is made with
# The duration of the "remember me" time in seconds. When the login request is made with
# the long param set, the token returned will be valid for this period.
# The default is 2592000 seconds (30 Days).
jwtttllong: 2592000
@ -48,7 +48,7 @@ service:
# If enabled, vikunja will send an email to everyone who is either assigned to a task or created it when a task reminder
# is due.
enableemailreminders: true
# If true, will allow users to request the complete deletion of their account. When using external authentication methods
# If true, will allow users to request the complete deletion of their account. When using external authentication methods
# it may be required to coordinate with them in order to delete the account. This setting will not affect the cli commands
# for user deletion.
enableuserdeletion: true
@ -109,7 +109,7 @@ database:
typesense:
# Whether to enable the Typesense integration. If true, all tasks will be synced to the configured Typesense
# instance and all search and filtering will run through Typesense instead of only through the database.
# Typesense allows fast fulltext search including fuzzy matching support. It may return different results than
# Typesense allows fast fulltext search including fuzzy matching support. It may return different results than
# what you'd get with a database-only search.
enabled: false
# The url to the Typesense instance you want to use. Can be hosted locally or in Typesense Cloud as long
@ -203,7 +203,7 @@ ratelimit:
# Possible values are "keyvalue", "memory" or "redis".
# When choosing "keyvalue" this setting follows the one configured in the "keyvalue" section.
store: keyvalue
# The number of requests a user can make from the same IP to all unauthenticated routes (login, register,
# The number of requests a user can make from the same IP to all unauthenticated routes (login, register,
# password confirmation, email verification, password reset request) per minute. This limit cannot be disabled.
# You should only change this if you know what you're doing.
noauthlimit: 10
@ -301,13 +301,11 @@ auth:
enabled: true
# OpenID configuration will allow users to authenticate through a third-party OpenID Connect compatible provider.<br/>
# The provider needs to support the `openid`, `profile` and `email` scopes.<br/>
# **Note:** Some openid providers (like gitlab) only make the email of the user available through openid claims if they have set it to be publicly visible.
# **Note:** Some openid providers (like Gitlab) only make the email of the user available through OpenID if they have set it to be publicly visible.
# If the email is not public in those cases, authenticating will fail.
# **Note 2:** The frontend expects to be redirected after authentication by the third party
# to <frontend-url>/auth/openid/<auth key>. Please make sure to configure the redirect url in your third party
# auth service accordingly if you're using the default vikunja frontend.
# The frontend will automatically provide the api with the redirect url, composed from the current url where it's hosted.
# If you want to use the desktop client with openid, make sure to allow redirects to `127.0.0.1`.
# **Note 2:** The frontend expects the third party to rediect the user <frontend-url>/auth/openid/<auth key> after authentication. Please make sure to configure the redirect url in your third party auth service accordingly if you're using the default vikunja frontend.
# The frontend will automatically provide the API with the redirect url, composed from the current url where it's hosted.
# If you want to use the desktop client with OpenID, make sure to allow redirects to `127.0.0.1`.
# Take a look at the [default config file](https://kolaente.dev/vikunja/vikunja/src/branch/main/config.yml.sample) for more information about how to configure openid authentication.
openid:
# Enable or disable OpenID Connect authentication
@ -325,6 +323,10 @@ auth:
clientid:
# The client secret used to authenticate Vikunja at the OpenID Connect provider.
clientsecret:
# The scope necessary to use oidc.
# If you want to use the Feature to create and assign to vikunja teams via oidc, you have to add the custom "vikunja_scope" and check [openid.md](https://vikunja.io/docs/openid/).
# e.g. scope: openid email profile vikunja_scope
scope: openid email profile
# Prometheus metrics endpoint
metrics:

View File

@ -51,11 +51,11 @@
}
},
"devDependencies": {
"electron": "29.0.0",
"electron-builder": "24.9.1"
"electron": "29.1.1",
"electron-builder": "24.13.3"
},
"dependencies": {
"connect-history-api-fallback": "2.0.0",
"express": "4.18.2"
"express": "4.18.3"
}
}

View File

@ -40,10 +40,10 @@
optionalDependencies:
global-agent "^3.0.0"
"@electron/notarize@2.1.0":
version "2.1.0"
resolved "https://registry.yarnpkg.com/@electron/notarize/-/notarize-2.1.0.tgz#76aaec10c8687225e8d0a427cc9df67611c46ff3"
integrity sha512-Q02xem1D0sg4v437xHgmBLxI2iz/fc0D4K7fiVWHa/AnW8o7D751xyKNXgziA6HrTOme9ul1JfWN5ark8WH1xA==
"@electron/notarize@2.2.1":
version "2.2.1"
resolved "https://registry.yarnpkg.com/@electron/notarize/-/notarize-2.2.1.tgz#d0aa6bc43cba830c41bfd840b85dbe0e273f59fe"
integrity sha512-aL+bFMIkpR0cmmj5Zgy0LMKEpgy43/hw5zadEArgmAMWWlKc5buwFvFT9G/o/YJkvXAJm5q3iuTuLaiaXW39sg==
dependencies:
debug "^4.1.1"
fs-extra "^9.0.1"
@ -61,10 +61,10 @@
minimist "^1.2.6"
plist "^3.0.5"
"@electron/universal@1.4.1":
version "1.4.1"
resolved "https://registry.yarnpkg.com/@electron/universal/-/universal-1.4.1.tgz#3fbda2a5ed9ff9f3304c8e8316b94c1e3a7b3785"
integrity sha512-lE/U3UNw1YHuowNbTmKNs9UlS3En3cPgwM5MI+agIgr/B1hSze9NdOP0qn7boZaI9Lph8IDv3/24g9IxnJP7aQ==
"@electron/universal@1.5.1":
version "1.5.1"
resolved "https://registry.yarnpkg.com/@electron/universal/-/universal-1.5.1.tgz#f338bc5bcefef88573cf0ab1d5920fac10d06ee5"
integrity sha512-kbgXxyEauPJiQQUNG2VgUeyfQNFk6hBF11ISN2PNI6agUgPl55pv4eQmaqHzTAzchBvqZ2tQuRVaPStGf0mxGw==
dependencies:
"@electron/asar" "^3.2.1"
"@malept/cross-spawn-promise" "^1.1.0"
@ -253,26 +253,25 @@ app-builder-bin@4.0.0:
resolved "https://registry.yarnpkg.com/app-builder-bin/-/app-builder-bin-4.0.0.tgz#1df8e654bd1395e4a319d82545c98667d7eed2f0"
integrity sha512-xwdG0FJPQMe0M0UA4Tz0zEB8rBJTRA5a476ZawAqiBkMv16GRK5xpXThOjMaEOFnZ6zabejjG4J3da0SXG63KA==
app-builder-lib@24.9.1:
version "24.9.1"
resolved "https://registry.yarnpkg.com/app-builder-lib/-/app-builder-lib-24.9.1.tgz#bf3568529298b4de8595ed1acbb351fe27db5ba4"
integrity sha512-Q1nYxZcio4r+W72cnIRVYofEAyjBd3mG47o+zms8HlD51zWtA/YxJb01Jei5F+jkWhge/PTQK+uldsPh6d0/4g==
app-builder-lib@24.13.3:
version "24.13.3"
resolved "https://registry.yarnpkg.com/app-builder-lib/-/app-builder-lib-24.13.3.tgz#36e47b65fecb8780bb73bff0fee4e0480c28274b"
integrity sha512-FAzX6IBit2POXYGnTCT8YHFO/lr5AapAII6zzhQO3Rw4cEDOgK+t1xhLc5tNcKlicTHlo9zxIwnYCX9X2DLkig==
dependencies:
"7zip-bin" "~5.2.0"
"@develar/schema-utils" "~2.6.5"
"@electron/notarize" "2.1.0"
"@electron/notarize" "2.2.1"
"@electron/osx-sign" "1.0.5"
"@electron/universal" "1.4.1"
"@electron/universal" "1.5.1"
"@malept/flatpak-bundler" "^0.4.0"
"@types/fs-extra" "9.0.13"
async-exit-hook "^2.0.1"
bluebird-lst "^1.0.9"
builder-util "24.8.1"
builder-util-runtime "9.2.3"
builder-util "24.13.1"
builder-util-runtime "9.2.4"
chromium-pickle-js "^0.2.0"
debug "^4.3.4"
ejs "^3.1.8"
electron-publish "24.8.1"
electron-publish "24.13.1"
form-data "^4.0.0"
fs-extra "^10.1.0"
hosted-git-info "^4.1.0"
@ -349,13 +348,13 @@ bluebird@^3.5.5:
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
body-parser@1.20.1:
version "1.20.1"
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668"
integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==
body-parser@1.20.2:
version "1.20.2"
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd"
integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==
dependencies:
bytes "3.1.2"
content-type "~1.0.4"
content-type "~1.0.5"
debug "2.6.9"
depd "2.0.0"
destroy "1.2.0"
@ -363,7 +362,7 @@ body-parser@1.20.1:
iconv-lite "0.4.24"
on-finished "2.4.1"
qs "6.11.0"
raw-body "2.5.1"
raw-body "2.5.2"
type-is "~1.6.18"
unpipe "1.0.0"
@ -410,24 +409,24 @@ buffer@^5.1.0:
base64-js "^1.3.1"
ieee754 "^1.1.13"
builder-util-runtime@9.2.3:
version "9.2.3"
resolved "https://registry.yarnpkg.com/builder-util-runtime/-/builder-util-runtime-9.2.3.tgz#0a82c7aca8eadef46d67b353c638f052c206b83c"
integrity sha512-FGhkqXdFFZ5dNC4C+yuQB9ak311rpGAw+/ASz8ZdxwODCv1GGMWgLDeofRkdi0F3VCHQEWy/aXcJQozx2nOPiw==
builder-util-runtime@9.2.4:
version "9.2.4"
resolved "https://registry.yarnpkg.com/builder-util-runtime/-/builder-util-runtime-9.2.4.tgz#13cd1763da621e53458739a1e63f7fcba673c42a"
integrity sha512-upp+biKpN/XZMLim7aguUyW8s0FUpDvOtK6sbanMFDAMBzpHDqdhgVYm6zc9HJ6nWo7u2Lxk60i2M6Jd3aiNrA==
dependencies:
debug "^4.3.4"
sax "^1.2.4"
builder-util@24.8.1:
version "24.8.1"
resolved "https://registry.yarnpkg.com/builder-util/-/builder-util-24.8.1.tgz#594d45b0c86d1d17f5c7bebbb77405080b2571c2"
integrity sha512-ibmQ4BnnqCnJTNrdmdNlnhF48kfqhNzSeqFMXHLIl+o9/yhn6QfOaVrloZ9YUu3m0k3rexvlT5wcki6LWpjTZw==
builder-util@24.13.1:
version "24.13.1"
resolved "https://registry.yarnpkg.com/builder-util/-/builder-util-24.13.1.tgz#4a4c4f9466b016b85c6990a0ea15aa14edec6816"
integrity sha512-NhbCSIntruNDTOVI9fdXz0dihaqX2YuE1D6zZMrwiErzH4ELZHE6mdiB40wEgZNprDia+FghRFgKoAqMZRRjSA==
dependencies:
"7zip-bin" "~5.2.0"
"@types/debug" "^4.1.6"
app-builder-bin "4.0.0"
bluebird-lst "^1.0.9"
builder-util-runtime "9.2.3"
builder-util-runtime "9.2.4"
chalk "^4.1.2"
cross-spawn "^7.0.3"
debug "^4.3.4"
@ -572,7 +571,7 @@ content-disposition@0.5.4:
dependencies:
safe-buffer "5.2.1"
content-type@~1.0.4:
content-type@~1.0.4, content-type@~1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918"
integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==
@ -690,14 +689,14 @@ dir-compare@^3.0.0:
buffer-equal "^1.0.0"
minimatch "^3.0.4"
dmg-builder@24.9.1:
version "24.9.1"
resolved "https://registry.yarnpkg.com/dmg-builder/-/dmg-builder-24.9.1.tgz#04bf6c0dcd235f6214511f2358a78ed2b9379421"
integrity sha512-huC+O6hvHd24Ubj3cy2GMiGLe2xGFKN3klqVMLAdcbB6SWMd1yPSdZvV8W1O01ICzCCRlZDHiv4VrNUgnPUfbQ==
dmg-builder@24.13.3:
version "24.13.3"
resolved "https://registry.yarnpkg.com/dmg-builder/-/dmg-builder-24.13.3.tgz#95d5b99c587c592f90d168a616d7ec55907c7e55"
integrity sha512-rcJUkMfnJpfCboZoOOPf4L29TRtEieHNOeAbYPWPxlaBw/Z1RKrRA86dOI9rwaI4tQSc/RD82zTNHprfUHXsoQ==
dependencies:
app-builder-lib "24.9.1"
builder-util "24.8.1"
builder-util-runtime "9.2.3"
app-builder-lib "24.13.3"
builder-util "24.13.1"
builder-util-runtime "9.2.4"
fs-extra "^10.1.0"
iconv-lite "^0.6.2"
js-yaml "^4.1.0"
@ -740,16 +739,16 @@ ejs@^3.1.8:
dependencies:
jake "^10.8.5"
electron-builder@24.9.1:
version "24.9.1"
resolved "https://registry.yarnpkg.com/electron-builder/-/electron-builder-24.9.1.tgz#4aee03947963b829a7f48a850fe02c219311ef63"
integrity sha512-v7BuakDuY6sKMUYM8mfQGrwyjBpZ/ObaqnenU0H+igEL10nc6ht049rsCw2HghRBdEwJxGIBuzs3jbEhNaMDmg==
electron-builder@24.13.3:
version "24.13.3"
resolved "https://registry.yarnpkg.com/electron-builder/-/electron-builder-24.13.3.tgz#c506dfebd36d9a50a83ee8aa32d803d83dbe4616"
integrity sha512-yZSgVHft5dNVlo31qmJAe4BVKQfFdwpRw7sFp1iQglDRCDD6r22zfRJuZlhtB5gp9FHUxCMEoWGq10SkCnMAIg==
dependencies:
app-builder-lib "24.9.1"
builder-util "24.8.1"
builder-util-runtime "9.2.3"
app-builder-lib "24.13.3"
builder-util "24.13.1"
builder-util-runtime "9.2.4"
chalk "^4.1.2"
dmg-builder "24.9.1"
dmg-builder "24.13.3"
fs-extra "^10.1.0"
is-ci "^3.0.0"
lazy-val "^1.0.5"
@ -757,23 +756,23 @@ electron-builder@24.9.1:
simple-update-notifier "2.0.0"
yargs "^17.6.2"
electron-publish@24.8.1:
version "24.8.1"
resolved "https://registry.yarnpkg.com/electron-publish/-/electron-publish-24.8.1.tgz#4216740372bf4297a429543402a1a15ce8c3560b"
integrity sha512-IFNXkdxMVzUdweoLJNXSupXkqnvgbrn3J4vognuOY06LaS/m0xvfFYIf+o1CM8if6DuWYWoQFKPcWZt/FUjZPw==
electron-publish@24.13.1:
version "24.13.1"
resolved "https://registry.yarnpkg.com/electron-publish/-/electron-publish-24.13.1.tgz#57289b2f7af18737dc2ad134668cdd4a1b574a0c"
integrity sha512-2ZgdEqJ8e9D17Hwp5LEq5mLQPjqU3lv/IALvgp+4W8VeNhryfGhYEQC/PgDPMrnWUp+l60Ou5SJLsu+k4mhQ8A==
dependencies:
"@types/fs-extra" "^9.0.11"
builder-util "24.8.1"
builder-util-runtime "9.2.3"
builder-util "24.13.1"
builder-util-runtime "9.2.4"
chalk "^4.1.2"
fs-extra "^10.1.0"
lazy-val "^1.0.5"
mime "^2.5.2"
electron@29.0.0:
version "29.0.0"
resolved "https://registry.yarnpkg.com/electron/-/electron-29.0.0.tgz#2c1d34a95fc23a5b3530aa53de6eacca89a410b1"
integrity sha512-HhrRC5vWb6fAbWXP3A6ABwKUO9JvYSC4E141RzWFgnDBqNiNtabfmgC8hsVeCR65RQA2MLSDgC8uP52I9zFllQ==
electron@29.1.1:
version "29.1.1"
resolved "https://registry.yarnpkg.com/electron/-/electron-29.1.1.tgz#e9cb11311324e4b43a3e73667cd2b65a30e8fa34"
integrity sha512-cXN15NgCi7MkzGo5/23ZQbii+0UfhmUiDjACunmzcUofYCjF42XhFbL7JZnwgI0qtBCCeJU8qZNZt9lU91gUFw==
dependencies:
"@electron/get" "^2.0.0"
"@types/node" "^20.9.0"
@ -831,14 +830,14 @@ etag@~1.8.1:
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==
express@4.18.2:
version "4.18.2"
resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59"
integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==
express@4.18.3:
version "4.18.3"
resolved "https://registry.yarnpkg.com/express/-/express-4.18.3.tgz#6870746f3ff904dee1819b82e4b51509afffb0d4"
integrity sha512-6VyCijWQ+9O7WuVMTRBTl+cjNNIzD5cY5mQ1WM8r/LEkI2u8EYpOotESNwzNlyCn3g+dmjKYI6BmNneSr/FSRw==
dependencies:
accepts "~1.3.8"
array-flatten "1.1.1"
body-parser "1.20.1"
body-parser "1.20.2"
content-disposition "0.5.4"
content-type "~1.0.4"
cookie "0.5.0"
@ -1570,10 +1569,10 @@ range-parser@~1.2.1:
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
raw-body@2.5.1:
version "2.5.1"
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857"
integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==
raw-body@2.5.2:
version "2.5.2"
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a"
integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==
dependencies:
bytes "3.1.2"
http-errors "2.0.0"

View File

@ -1,15 +0,0 @@
#!/usr/bin/env sh
set -e
if [ -n "$PUID" ] && [ "$PUID" -ne 0 ] && \
[ -n "$PGID" ] && [ "$PGID" -ne 0 ] ; then
echo "info: creating the new user vikunja with $PUID:$PGID"
groupmod -g "$PGID" -o vikunja
usermod -u "$PUID" -o vikunja
chown -R vikunja:vikunja ./files/
chown vikunja:vikunja .
exec su vikunja -c /app/vikunja/vikunja "$@"
else
echo "info: creation of non-root user is skipped"
exec /app/vikunja/vikunja "$@"
fi

View File

@ -94,7 +94,7 @@ Environment path: `VIKUNJA_SERVICE_JWTTTL`
### jwtttllong
The duration of the "remember me" time in seconds. When the login request is made with
The duration of the "remember me" time in seconds. When the login request is made with
the long param set, the token returned will be valid for this period.
The default is 2592000 seconds (30 Days).
@ -289,7 +289,7 @@ Environment path: `VIKUNJA_SERVICE_ENABLEEMAILREMINDERS`
### enableuserdeletion
If true, will allow users to request the complete deletion of their account. When using external authentication methods
If true, will allow users to request the complete deletion of their account. When using external authentication methods
it may be required to coordinate with them in order to delete the account. This setting will not affect the cli commands
for user deletion.
@ -406,7 +406,7 @@ Environment path: `VIKUNJA_SENTRY_FRONTENDDSN`
### type
Database type to use. Supported types are mysql, postgres and sqlite.
Database type to use. Supported values are mysql, postgres and sqlite. Vikunja is able to run with MySQL 8.0+, Mariadb 10.2+, PostgreSQL 12+, and sqlite.
Default: `sqlite`
@ -569,7 +569,7 @@ Environment path: `VIKUNJA_DATABASE_TLS`
Whether to enable the Typesense integration. If true, all tasks will be synced to the configured Typesense
instance and all search and filtering will run through Typesense instead of only through the database.
Typesense allows fast fulltext search including fuzzy matching support. It may return different results than
Typesense allows fast fulltext search including fuzzy matching support. It may return different results than
what you'd get with a database-only search.
Default: `false`
@ -1024,7 +1024,7 @@ Environment path: `VIKUNJA_RATELIMIT_STORE`
### noauthlimit
The number of requests a user can make from the same IP to all unauthenticated routes (login, register,
The number of requests a user can make from the same IP to all unauthenticated routes (login, register,
password confirmation, email verification, password reset request) per minute. This limit cannot be disabled.
You should only change this if you know what you're doing.
@ -1209,13 +1209,11 @@ Environment path: `VIKUNJA_AUTH_LOCAL`
OpenID configuration will allow users to authenticate through a third-party OpenID Connect compatible provider.<br/>
The provider needs to support the `openid`, `profile` and `email` scopes.<br/>
**Note:** Some openid providers (like gitlab) only make the email of the user available through openid claims if they have set it to be publicly visible.
**Note:** Some openid providers (like Gitlab) only make the email of the user available through OpenID if they have set it to be publicly visible.
If the email is not public in those cases, authenticating will fail.
**Note 2:** The frontend expects to be redirected after authentication by the third party
to <frontend-url>/auth/openid/<auth key>. Please make sure to configure the redirect url in your third party
auth service accordingly if you're using the default vikunja frontend.
The frontend will automatically provide the api with the redirect url, composed from the current url where it's hosted.
If you want to use the desktop client with openid, make sure to allow redirects to `127.0.0.1`.
**Note 2:** The frontend expects the third party to rediect the user <frontend-url>/auth/openid/<auth key> after authentication. Please make sure to configure the redirect url in your third party auth service accordingly if you're using the default vikunja frontend.
The frontend will automatically provide the API with the redirect url, composed from the current url where it's hosted.
If you want to use the desktop client with OpenID, make sure to allow redirects to `127.0.0.1`.
Take a look at the [default config file](https://kolaente.dev/vikunja/vikunja/src/branch/main/config.yml.sample) for more information about how to configure openid authentication.
Default: `<empty>`

View File

@ -10,7 +10,7 @@ menu:
# OpenID example configurations
On this page you will find examples about how to set up Vikunja with a third-party OpenID provider.
On this page you will find examples about how to set up Vikunja with a third-party OAuth 2.0 provider using OpenID Connect.
To add another example, please [edit this document](https://kolaente.dev/vikunja/vikunja/src/branch/main/docs/content/doc/setup/openid-examples.md) and send a PR.
{{< table_of_contents >}}
@ -67,7 +67,7 @@ Google config:
Note that there currently seems to be no way to stop creation of new users, even when `enableregistration` is `false` in the configuration. This means that this approach works well only with an "Internal Organization" app for Google Workspace, which limits the allowed users to organizational accounts only. External / public applications will potentially allow every Google user to register.
## Keycloak
## Keycloak
Vikunja Config:
```yaml
@ -111,4 +111,7 @@ auth:
clientsecret: "" # copy from Authentik
```
**Note:** The `authurl` that Vikunja requires is not the `Authorize URL` that you can see in the Provider. Vikunja uses Open ID Discovery to find the correct endpoint to use. Vikunja does this by automatically accessing the `OpenID Configuration URL` (usually `https://authentik.mydomain.com/application/o/vikunja/.well-known/openid-configuration`). Use this URL without the `.well-known/openid-configuration` as the `authurl`.
**Note:** The `authurl` that Vikunja requires is not the `Authorize URL` that you can see in the Provider.
OpenID Discovery is used to find the correct endpoint to use automatically, by accessing the `OpenID Configuration URL` (usually `https://authentik.mydomain.com/application/o/vikunja/.well-known/openid-configuration`).
Use this URL without the `.well-known/openid-configuration` as the `authurl`.
Typically this URL can be found in the metadata section within your identity provider.

View File

@ -0,0 +1,180 @@
---
date: "2022-08-09:00:00+02:00"
title: "OpenID"
draft: false
type: "doc"
menu:
sidebar:
parent: "setup"
---
# OpenID
Vikunja allows for authentication with an external identity source such as Authentik, Keycloak or similar via the
[OpenID Connect](https://openid.net/developers/specs/) standard.
{{< table_of_contents >}}
## OpenID Connect Overview
OpenID Connect is a standardized identity layer built on top of the more generic OAuth 2.0 specification, simplying interaction between the involved parties significantly.
While the [OpenID specification](https://openid.net/specs/openid-connect-core-1_0.html#Overview) is worth a read, we summarize the most important basics here.
The involved parties are:
- **Resource Owner:** typically the end-user
- **Resource Server:** the application server handling requests from the client, the Vikunja API in our case
- **Client:** the application or client accessing the RS on behalf of the RO. Vikunja web frontend or any of the apps
- **Authorization Server:** the server verifying the user identity and issuing tokens. These docs also use the words `OAuth 2.0 provider`, `Identity Provider` interchangeably.
After the user is authenticated, the provider issues a token to the user, containing various claims.
There's different types of tokens (ID token, access token, refresh token), and all of them are created as [JSON Web Token](https://www.rfc-editor.org/info/rfc7519).
Claims in turn are assertions containing information about the token bearer, usually the user.
**Scopes** are requested by the client when redirecting the end-user to the Authorization Server for authentication, and indirectly control which claims are included in the resulting tokens.
There's certain default scopes, but its also possible to define custom scopes, which are used by the feature assigning users to Teams automatically.
## Configuring OIDC Authentication
To achieve authentication via an external provider, it is required to (a) configure a confidential Client on your OAuth 2.0 provider and (b) configure Vikunja to authenticate against this provider.
[Example configurations]({{< ref "openid-examples.md">}}) are provided for various different identity providers, below you can find generic guides though.
OpenID Connect defines various flow types indicating how exactly the interaction between the involved parties work, Vikunja makes use of the standard **Authorization Code Flow**.
### Step 1: Configure your Authorization Server
The first step is to configure the Authorization Server to correctly handle requests coming from Vikunja.
In general, this involves the following steps at a minimum:
- Create a confidential client and obtain the client ID and client secret
- Configure (whitelist) redirect URLs that can be used by Vikunja
- Make sure the required scopes (`openid profile email` are the default scopes used by Vikunja) are supported
- Optional: configure an additional scope for automatic team assignment, see below for details
More detailled instructions for various different identity providers can be [found here]({{< ref "openid-examples.md">}})
### Step 2: Configure Vikunja
Vikunja has to be configured to use the identity provider. Please note that there is currently no option to configure these settings via environment variables, they have to be defined using the configuration file. The configuration schema is as follows:
```yaml
auth:
openid:
enabled: true
redirecturl: https://vikunja.mydomain.com/auth/openid/ <---- slash at the end is important
providers:
- name: <provider-name>
authurl: <auth-url>
clientid: <vikunja client-id>
clientsecret: <vikunja client-secret>
scope: openid profile email
```
The values for `authurl` can be obtained from the Metadata of your provider, while `clientid` and `clientsecret` are obtained when configuring the client.
The scope usually doesn't need to be specified or changed, unless you want to configure the automatic team assignment.
Optionally it is possible to disable local authentication and therefore forcing users to login via OpenID connect:
```yaml
auth:
local:
enabled: false
```
## Automatically assign users to teams
Vikunja is capable of automatically adding users to a team based on OIDC claims added by the identity provider.
If configured, Vikunja will sync teams, automatically create new ones and make sure the members are part of the configured teams.
Teams which exist only because they were created from oidc attributes are not editable in Vikunja.
To distinguish between teams created in Vikunja and teams generated automatically via oidc, generated teams have an `oidcID` assigned internally.
Within the UI, the teams created through OIDC get a `(OIDC)` suffix to make them distinguishable from locally created teams.
On a high level, you need to make sure that the **ID token** issued by your identity provider contains a `vikunja_groups` claim, following the structure defined below.
It depends on the provider being used as well as the preferences of the administrator how this is achieved.
Typically you'd want to request an additional scope (e.g. `vikunja_scope`) which then triggers the identity provider to add the claim.
If the `vikunja_groups` is part of the **ID token**, Vikunja will start the procedure and import teams and team memberships.
The claim structure expexted by Vikunja is as follows:
```json
{
"vikunja_groups": [
{
"name": "team 1",
"oidcID": 33349
},
{
"name": "team 2",
"oidcID": 35933
}
]
}
```
For each team, you need to define a team `name` and an `oidcID`, where the `oidcID` can be any string with a length of less than 250 characters.
The `oidcID` is used to uniquely identify the team, so please make sure to keep this unique.
Below you'll find two example implementations for Authentik and Keycloak.
If you've successfully implemented this with another identity provider, please let us know and submit a PR to improve the docs.
### Setup in Authentik
To configure automatic team management through Authentik, we assume you have already [set up Authentik]({{< ref "openid-examples.md">}}#authentik) as an OIDC provider for authentication with Vikunja.
To use Authentik's group assignment feature, follow these steps:
1. Edit [your config]({{< ref "config.md">}}) to include the following scopes: `openid profile email vikunja_scope`
2. Open `<your authentik url>/if/admin/#/core/property-mappings`
3. Create a new property mapping called `vikunja_scope` as scope mapping. There is a field `expression` to enter python expressions that will be delivered with the oidc token.
4. Write a small script like the following to add group information to `vikunja_scope`:
```python
groupsDict = {"vikunja_groups": []}
for group in request.user.ak_groups.all():
groupsDict["vikunja_groups"].append({"name": group.name, "oidcID": group.num_pk})
return groupsDict
```
5. In Authentik's menu on the left, go to Applications > Providers > Select the Vikunja provider. Then click on "Edit", on the bottom open "Advanced protocol settings", select the newly created property mapping under "Scopes". Save the provider.
Now when you log into Vikunja via Authentik it will show you a list of scopes you are claiming.
You should see the description you entered on the OIDC provider's admin area.
Proceed to vikunja and open the teams page in the sidebar menu.
You should see "(OIDC)" written next to each team you were assigned through OIDC.
### Setup in Keycloak
The kind people from Makerspace Darmstadt e.V. have written [a guide on how to create a mapper for Vikunja here](https://github.com/makerspace-darmstadt/keycloak-vikunja-mapper).
## Use cases
All examples assume one team called "Team 1" to be configured within your provider.
* *Token delivers team.name +team.oidcID and Vikunja team does not exist:* \
New team will be created called "Team 1" with attribute oidcID: "33929"
2. *In Vikunja Team with name "team 1" already exists in vikunja, but has no oidcID set:* \
new team will be created called "team 1" with attribute oidcID: "33929"
3. *In Vikunja Team with name "team 1" already exists in vikunja, but has different oidcID set:* \
new team will be created called "team 1" with attribute oidcID: "33929"
4. *In Vikunja Team with oidcID "33929" already exists in vikunja, but has different name than "team1":* \
new team will be created called "team 1" with attribute oidcID: "33929"
5. *Scope vikunja_scope is not set:* \
nothing happens
6. *oidcID is not set:* \
You'll get error.
Custom Scope malformed
"The custom scope set by the OIDC provider is malformed. Please make sure the openid provider sets the data correctly for your scope. Check especially to have set an oidcID."
7. *In Vikunja I am in "team 3" with oidcID "", but the token does not deliver any data for "team 3":* \
You will stay in team 3 since it was not set by the oidc provider
8. *In Vikunja I am in "team 3" with oidcID "12345", but the token does not deliver any data for "team 3"*:\
You will be signed out of all teams, which have an oidcID set and are not contained in the token.
Especially if you've been the last team member, the team will be deleted.

View File

@ -44,6 +44,7 @@ This document describes the different errors Vikunja can return.
| 1020 | 412 | This user account is disabled. |
| 1021 | 412 | This account is managed by a third-party authentication provider. |
| 1021 | 412 | The username must not contain spaces. |
| 1022 | 412 | The custom scope set by the OIDC provider is malformed. Please make sure the openid provider sets the data correctly for your scope. Check especially to have set an oidcID. |
## Validation
@ -106,6 +107,9 @@ This document describes the different errors Vikunja can return.
| 6005 | 409 | The user is already a member of that team. |
| 6006 | 400 | Cannot delete the last team member. |
| 6007 | 403 | The team does not have access to the project to perform that action. |
| 6008 | 400 | There are no teams found with that team name. |
| 6009 | 400 | There is no oidc team with that team name and oidcId. |
| 6010 | 400 | There are no oidc teams found for the user. |
## User Project Access

View File

@ -56,10 +56,10 @@
"@fortawesome/vue-fontawesome": "3.0.6",
"@github/hotkey": "3.1.0",
"@infectoone/vue-ganttastic": "2.2.0",
"@intlify/unplugin-vue-i18n": "2.0.0",
"@intlify/unplugin-vue-i18n": "3.0.1",
"@kyvg/vue3-notification": "3.2.0",
"@sentry/tracing": "7.102.1",
"@sentry/vue": "7.102.1",
"@sentry/tracing": "7.106.0",
"@sentry/vue": "7.106.0",
"@tiptap/core": "2.2.4",
"@tiptap/extension-blockquote": "2.2.4",
"@tiptap/extension-bold": "2.2.4",
@ -95,8 +95,8 @@
"@tiptap/vue-3": "2.2.4",
"@types/is-touch-device": "1.0.2",
"@types/lodash.clonedeep": "4.5.9",
"@vueuse/core": "10.8.0",
"@vueuse/router": "10.8.0",
"@vueuse/core": "10.9.0",
"@vueuse/router": "10.9.0",
"axios": "1.6.7",
"blurhash": "2.0.5",
"bulma-css-variables": "0.9.33",
@ -118,10 +118,10 @@
"sortablejs": "1.15.2",
"tippy.js": "6.3.7",
"ufo": "1.4.0",
"vue": "3.4.19",
"vue": "3.4.21",
"vue-advanced-cropper": "2.8.8",
"vue-flatpickr-component": "11.0.4",
"vue-i18n": "9.9.1",
"vue-flatpickr-component": "11.0.5",
"vue-i18n": "9.10.1",
"vue-router": "4.3.0",
"workbox-precaching": "7.0.0",
"zhyswan-vuedraggable": "4.1.3"
@ -130,9 +130,9 @@
"@4tw/cypress-drag-drop": "2.2.5",
"@cypress/vite-dev-server": "5.0.7",
"@cypress/vue": "6.0.0",
"@faker-js/faker": "8.4.0",
"@faker-js/faker": "8.4.1",
"@histoire/plugin-screenshot": "0.17.8",
"@histoire/plugin-vue": "0.17.9",
"@histoire/plugin-vue": "0.17.12",
"@rushstack/eslint-patch": "1.7.2",
"@tsconfig/node18": "18.2.2",
"@types/codemirror": "5.60.15",
@ -141,44 +141,44 @@
"@types/is-touch-device": "1.0.2",
"@types/lodash.debounce": "4.0.9",
"@types/marked": "5.0.2",
"@types/node": "20.11.10",
"@types/node": "20.11.25",
"@types/postcss-preset-env": "7.7.0",
"@types/sortablejs": "1.15.7",
"@typescript-eslint/eslint-plugin": "7.0.1",
"@typescript-eslint/parser": "7.0.1",
"@vitejs/plugin-legacy": "5.3.0",
"@vitejs/plugin-vue": "5.0.3",
"@vue/eslint-config-typescript": "12.0.0",
"@types/sortablejs": "1.15.8",
"@typescript-eslint/eslint-plugin": "7.1.1",
"@typescript-eslint/parser": "7.1.1",
"@vitejs/plugin-legacy": "5.3.2",
"@vitejs/plugin-vue": "5.0.4",
"@vue/eslint-config-typescript": "13.0.0",
"@vue/test-utils": "2.4.4",
"@vue/tsconfig": "0.5.1",
"autoprefixer": "10.4.17",
"browserslist": "4.22.3",
"caniuse-lite": "1.0.30001581",
"css-has-pseudo": "6.0.1",
"autoprefixer": "10.4.18",
"browserslist": "4.23.0",
"caniuse-lite": "1.0.30001596",
"css-has-pseudo": "6.0.2",
"csstype": "3.1.3",
"cypress": "13.6.3",
"esbuild": "0.20.0",
"eslint": "8.56.0",
"eslint-plugin-vue": "9.20.1",
"happy-dom": "13.3.5",
"cypress": "13.6.6",
"esbuild": "0.20.1",
"eslint": "8.57.0",
"eslint-plugin-vue": "9.22.0",
"happy-dom": "13.7.0",
"histoire": "0.17.9",
"postcss": "8.4.33",
"postcss": "8.4.35",
"postcss-easing-gradients": "3.0.1",
"postcss-easings": "4.0.0",
"postcss-focus-within": "8.0.1",
"postcss-preset-env": "9.3.0",
"rollup": "4.9.6",
"postcss-preset-env": "9.5.0",
"rollup": "4.12.1",
"rollup-plugin-visualizer": "5.12.0",
"sass": "1.70.0",
"sass": "1.71.1",
"start-server-and-test": "2.0.3",
"typescript": "5.3.3",
"vite": "5.0.12",
"typescript": "5.4.2",
"vite": "5.1.5",
"vite-plugin-inject-preload": "1.3.3",
"vite-plugin-pwa": "0.17.5",
"vite-plugin-sentry": "1.3.0",
"vite-plugin-pwa": "0.19.2",
"vite-plugin-sentry": "1.4.0",
"vite-svg-loader": "5.1.0",
"vitest": "1.2.2",
"vue-tsc": "1.8.27",
"vitest": "1.3.1",
"vue-tsc": "2.0.6",
"wait-on": "7.2.0",
"workbox-cli": "7.0.0"
},

File diff suppressed because it is too large Load Diff

View File

@ -26,7 +26,7 @@
class="base-button"
:href="href"
rel="noreferrer noopener nofollow"
target="_blank"
:target="openExternalInNewTab ? '_blank' : undefined"
>
<slot />
</a>
@ -69,6 +69,7 @@ export interface BaseButtonProps extends /* @vue-ignore */ HTMLAttributes {
disabled?: boolean
to?: RouteLocationRaw
href?: string
openExternalInNewTab?: boolean
}
export interface BaseButtonEmits {
@ -78,6 +79,7 @@ export interface BaseButtonEmits {
const {
type = BASE_BUTTON_TYPES_MAP.BUTTON,
disabled = false,
openExternalInNewTab = true,
} = defineProps<BaseButtonProps>()
const emit = defineEmits<BaseButtonEmits>()

View File

@ -122,7 +122,7 @@ const labelStore = useLabelStore()
labelStore.loadAllLabels()
const projectStore = useProjectStore()
projectStore.loadProjects()
projectStore.loadAllProjects()
</script>
<style lang="scss" scoped>

View File

@ -11,14 +11,17 @@ export function getRedirectUrlFromCurrentFrontendPath(provider: IProvider): stri
export const redirectToProvider = (provider: IProvider) => {
console.log({provider})
const redirectUrl = getRedirectUrlFromCurrentFrontendPath(provider)
const state = createRandomID(24)
localStorage.setItem('state', state)
window.location.href = `${provider.authUrl}?client_id=${provider.clientId}&redirect_uri=${redirectUrl}&response_type=code&scope=openid email profile&state=${state}`
let scope = 'openid email profile'
if (provider.scope !== null){
scope = provider.scope
}
window.location.href = `${provider.authUrl}?client_id=${provider.clientId}&redirect_uri=${redirectUrl}&response_type=code&scope=${scope}&state=${state}`
}
export const redirectToProviderOnLogout = (provider: IProvider) => {
if (provider.logoutUrl.length > 0) {
window.location.href = `${provider.logoutUrl}`

View File

@ -21,6 +21,7 @@ export const SUPPORTED_LOCALES = {
'hu-HU': 'Magyar',
'ar-SA': 'اَلْعَرَبِيَّةُ',
'sl-SI': 'Slovenščina',
'pt-BR': 'Português Brasileiro',
// IMPORTANT: Also add new languages to useDayjsLanguageSync
} as const

File diff suppressed because it is too large Load Diff

View File

@ -57,7 +57,11 @@
"logout": "Logga ut",
"emailInvalid": "Vänligen ange en giltig e-postadress.",
"usernameRequired": "Vänligen ange ett användarnamn.",
"usernameMustNotContainSpace": "The username must not contain spaces.",
"usernameMustNotLookLikeUrl": "The username must not look like a URL.",
"passwordRequired": "Vänligen ange ett lösenord.",
"passwordNotMin": "Password must have at least 8 characters.",
"passwordNotMax": "Password must have at most 250 characters.",
"showPassword": "Show the password",
"hidePassword": "Hide the password",
"noAccountYet": "Har du inget konto än?",
@ -708,7 +712,8 @@
"repeat": "Upprepa",
"startDate": "Startdatum",
"title": "Titel",
"updated": "Updated"
"updated": "Updated",
"doneAt": "Done At"
},
"subscription": {
"subscribedTaskThroughParentProject": "You can't unsubscribe here because you are subscribed to this task through its project.",
@ -969,7 +974,9 @@
"setBackground": "Set background",
"share": "Dela",
"newProject": "Nytt projekt",
"createProject": "Skapa projekt"
"createProject": "Skapa projekt",
"cantArchiveIsDefault": "",
"cantDeleteIsDefault": ""
},
"apiConfig": {
"url": "Vikunja URL",
@ -1089,8 +1096,7 @@
},
"about": {
"title": "Om",
"frontendVersion": "Frontend Version: {version}",
"apiVersion": "API Version: {version}"
"version": ""
},
"time": {
"units": {

View File

@ -1,7 +1,7 @@
import {computed, ref, watch} from 'vue'
import type dayjs from 'dayjs'
import {i18n, type SupportedLocale, type ISOLanguage} from '@/i18n'
import {i18n, type ISOLanguage, type SupportedLocale} from '@/i18n'
export const DAYJS_LOCALE_MAPPING = {
'de-de': 'de',
@ -22,6 +22,7 @@ export const DAYJS_LOCALE_MAPPING = {
'hu-HU': 'hu',
'ar-SA': 'ar-sa',
'sl-SI': 'sl',
'pt-BR': 'pt',
} as Record<SupportedLocale, ISOLanguage>
export const DAYJS_LANGUAGE_IMPORTS = {
@ -36,13 +37,14 @@ export const DAYJS_LANGUAGE_IMPORTS = {
'nl-nl': () => import('dayjs/locale/nl'),
'pt-pt': () => import('dayjs/locale/pt'),
'zh-cn': () => import('dayjs/locale/zh-cn'),
'no-no': () => import('dayjs/locale/nn'),
'es-es': () => import('dayjs/locale/es'),
'da-dk': () => import('dayjs/locale/da'),
'ja-jp': () => import('dayjs/locale/ja'),
'hu-hu': () => import('dayjs/locale/hu'),
'ar-sa': () => import('dayjs/locale/ar-sa'),
'sl-si': () => import('dayjs/locale/sl'),
'no-no': () => import('dayjs/locale/nn'),
'es-es': () => import('dayjs/locale/es'),
'da-dk': () => import('dayjs/locale/da'),
'ja-jp': () => import('dayjs/locale/ja'),
'hu-hu': () => import('dayjs/locale/hu'),
'ar-sa': () => import('dayjs/locale/ar-sa'),
'sl-si': () => import('dayjs/locale/sl'),
'pt-br': () => import('dayjs/locale/pt-br'),
} as Record<SupportedLocale, () => Promise<ILocale>>
export function useDayjsLanguageSync(dayjsGlobal: typeof dayjs) {

View File

@ -9,6 +9,7 @@ export interface ITeam extends IAbstract {
description: string
members: ITeamMember[]
right: Right
oidcId: string
createdBy: IUser
created: Date

View File

@ -13,6 +13,7 @@ export default class TeamModel extends AbstractModel<ITeam> implements ITeam {
description = ''
members: ITeamMember[] = []
right: Right = RIGHTS.READ
oidcId = ''
createdBy: IUser = {} // FIXME: seems wrong
created: Date = null

View File

@ -111,13 +111,13 @@ export function useSavedFilter(projectId?: MaybeRef<IProject['id']>) {
async function createFilter() {
filter.value = await filterService.create(filter.value)
await projectStore.loadProjects()
await projectStore.loadAllProjects()
router.push({name: 'project.index', params: {projectId: getProjectId(filter.value)}})
}
async function saveFilter() {
const response = await filterService.update(filter.value)
await projectStore.loadProjects()
await projectStore.loadAllProjects()
success({message: t('filters.edit.success')})
response.filters = objectToSnakeCase(response.filters)
filter.value = response
@ -130,7 +130,7 @@ export function useSavedFilter(projectId?: MaybeRef<IProject['id']>) {
async function deleteFilter() {
await filterService.delete(filter.value)
await projectStore.loadProjects()
await projectStore.loadAllProjects()
success({message: t('filters.delete.success')})
router.push({name: 'projects.index'})
}

View File

@ -175,20 +175,28 @@ export const useProjectStore = defineStore('project', () => {
}
}
async function loadProjects() {
async function loadAllProjects() {
const cancel = setModuleLoading(setIsLoading)
const projectService = new ProjectService()
const loadedProjects: IProject[] = []
let page = 1
try {
const loadedProjects = await projectService.getAll({}, {is_archived: true}) as IProject[]
projects.value = {}
setProjects(loadedProjects)
loadedProjects.forEach(p => add(p))
return loadedProjects
do {
const newProjects = await projectService.getAll({}, {is_archived: true}, page) as IProject[]
loadedProjects.push(...newProjects)
page++
} while (page <= projectService.totalPages)
} finally {
cancel()
}
projects.value = {}
setProjects(loadedProjects)
loadedProjects.forEach(p => add(p))
return loadedProjects
}
function getAncestors(project: IProject): IProject[] {
@ -222,7 +230,7 @@ export const useProjectStore = defineStore('project', () => {
setProjects,
removeProjectById,
toggleProjectFavorite,
loadProjects,
loadAllProjects,
createProject,
updateProject,
deleteProject,

View File

@ -473,7 +473,7 @@ export const useTaskStore = defineStore('task', () => {
task = await taskService.update(task)
// reloading the projects list so that the Favorites project shows up or is hidden when there are (or are not) favorite tasks
await projectStore.loadProjects()
await projectStore.loadAllProjects()
return task
}

View File

@ -4,4 +4,5 @@ export interface IProvider {
authUrl: string;
clientId: string;
logoutUrl: string;
scope: string;
}

View File

@ -27,6 +27,7 @@
:loading="migrationService.loading"
:disabled="migrationService.loading || undefined"
:href="authUrl"
:open-external-in-new-tab="false"
>
{{ $t('migrate.getStarted') }}
</x-button>
@ -212,7 +213,7 @@ async function migrate() {
const result = await migrationFileService.migrate(migrationConfig as File)
message.value = result.message
const projectStore = useProjectStore()
return projectStore.loadProjects()
return projectStore.loadAllProjects()
}
await migrationService.migrate(migrationConfig as MigrationConfig)

View File

@ -4,7 +4,7 @@
:class="{ 'is-loading': teamService.loading }"
>
<card
v-if="userIsAdmin"
v-if="userIsAdmin && !team.oidcId"
class="is-fullwidth"
:title="title"
>
@ -77,7 +77,7 @@
:padding="false"
>
<div
v-if="userIsAdmin"
v-if="userIsAdmin && !team.oidcId"
class="p-4"
>
<div class="field has-addons">

View File

@ -17,11 +17,13 @@
class="teams box"
>
<li
v-for="team in teams"
:key="team.id"
v-for="t in teams"
:key="t.id"
>
<router-link :to="{name: 'teams.edit', params: {id: team.id}}">
{{ team.name }}
<router-link :to="{name: 'teams.edit', params: {id: t.id}}">
<p>
{{ t.name }}
</p>
</router-link>
</li>
</ul>
@ -63,7 +65,7 @@ ul.teams {
li {
list-style: none;
margin: 0;
border-bottom: 1px solid $border;
border-bottom: 1px solid var(--grey-200);
a {
color: var(--text);

View File

@ -286,16 +286,15 @@ function toggleGroupPermissionsFromChild(group: string, checked: boolean) {
:key="group"
class="mb-2"
>
<strong class="is-capitalized">{{ formatPermissionTitle(group) }}</strong><br>
<template
v-if="Object.keys(routes).length > 1"
v-if="Object.keys(routes).length >= 1"
>
<Fancycheckbox
v-model="newTokenPermissionsGroup[group]"
class="mr-2 is-italic"
class="mr-2 is-capitalized has-text-weight-bold"
@update:modelValue="checked => selectPermissionGroup(group, checked)"
>
{{ $t('user.settings.apiTokens.selectAll') }}
{{ formatPermissionTitle(group) }}
</Fancycheckbox>
<br>
</template>
@ -305,7 +304,7 @@ function toggleGroupPermissionsFromChild(group: string, checked: boolean) {
>
<Fancycheckbox
v-model="newTokenPermissions[group][route]"
class="mr-2 is-capitalized"
class="ml-4 mr-2 is-capitalized"
@update:modelValue="checked => toggleGroupPermissionsFromChild(group, checked)"
>
{{ formatPermissionTitle(route) }}

32
go.mod
View File

@ -21,7 +21,7 @@ require (
dario.cat/mergo v1.0.0
github.com/ThreeDotsLabs/watermill v1.3.5
github.com/adlio/trello v1.10.0
github.com/arran4/golang-ical v0.2.6
github.com/arran4/golang-ical v0.2.7
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2
github.com/bbrks/go-blurhash v1.1.1
github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500
@ -32,10 +32,10 @@ require (
github.com/dustinkirkland/golang-petname v0.0.0-20231002161417-6a283f1aaaf2
github.com/gabriel-vasile/mimetype v1.4.3
github.com/getsentry/sentry-go v0.27.0
github.com/go-sql-driver/mysql v1.7.1
github.com/go-sql-driver/mysql v1.8.0
github.com/go-testfixtures/testfixtures/v3 v3.10.0
github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a
github.com/golang-jwt/jwt/v5 v5.2.0
github.com/golang-jwt/jwt/v5 v5.2.1
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
github.com/google/uuid v1.6.0
github.com/hashicorp/go-version v1.6.0
@ -52,37 +52,38 @@ require (
github.com/olekukonko/tablewriter v0.0.5
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
github.com/pquerna/otp v1.4.0
github.com/prometheus/client_golang v1.18.0
github.com/prometheus/client_golang v1.19.0
github.com/redis/go-redis/v9 v9.5.1
github.com/robfig/cron/v3 v3.0.1
github.com/samedi/caldav-go v3.0.0+incompatible
github.com/spf13/afero v1.11.0
github.com/spf13/cobra v1.8.0
github.com/spf13/viper v1.18.2
github.com/stretchr/testify v1.8.4
github.com/stretchr/testify v1.9.0
github.com/swaggo/swag v1.16.3
github.com/tkuchiki/go-timezone v0.2.2
github.com/typesense/typesense-go v1.0.0
github.com/ulule/limiter/v3 v3.11.2
github.com/wneessen/go-mail v0.4.0
github.com/yuin/goldmark v1.7.0
golang.org/x/crypto v0.19.0
golang.org/x/crypto v0.21.0
golang.org/x/image v0.15.0
golang.org/x/oauth2 v0.17.0
golang.org/x/oauth2 v0.18.0
golang.org/x/sync v0.6.0
golang.org/x/sys v0.17.0
golang.org/x/term v0.17.0
golang.org/x/sys v0.18.0
golang.org/x/term v0.18.0
golang.org/x/text v0.14.0
gopkg.in/d4l3k/messagediff.v1 v1.2.1
gopkg.in/yaml.v3 v3.0.1
mvdan.cc/xurls/v2 v2.5.0
src.techknowlogick.com/xgo v1.7.1-0.20240206231429-45b9ea635e03
src.techknowlogick.com/xgo v1.7.1-0.20240305180710-770b8eae9cec
src.techknowlogick.com/xormigrate v1.7.1
xorm.io/builder v0.3.13
xorm.io/xorm v1.3.8
)
require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/ClickHouse/ch-go v0.58.2 // indirect
github.com/ClickHouse/clickhouse-go/v2 v2.18.0 // indirect
github.com/KyleBanks/depth v1.2.1 // indirect
@ -106,7 +107,7 @@ require (
github.com/go-chi/chi/v5 v5.0.10 // indirect
github.com/go-faster/city v1.0.1 // indirect
github.com/go-faster/errors v0.6.1 // indirect
github.com/go-jose/go-jose/v3 v3.0.1 // indirect
github.com/go-jose/go-jose/v3 v3.0.3 // indirect
github.com/go-openapi/jsonpointer v0.20.1 // indirect
github.com/go-openapi/jsonreference v0.20.3 // indirect
github.com/go-openapi/spec v0.20.4 // indirect
@ -134,8 +135,6 @@ require (
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
@ -148,7 +147,7 @@ require (
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_model v0.5.0 // indirect
github.com/prometheus/common v0.45.0 // indirect
github.com/prometheus/common v0.48.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
github.com/rivo/uniseg v0.4.4 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
@ -162,6 +161,7 @@ require (
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/syndtr/goleveldb v1.0.0 // indirect
github.com/tj/assert v0.0.3 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
github.com/urfave/cli/v2 v2.3.0 // indirect
@ -174,11 +174,11 @@ require (
golang.org/x/arch v0.4.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/mod v0.12.0 // indirect
golang.org/x/net v0.21.0 // indirect
golang.org/x/net v0.22.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.13.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/protobuf v1.31.0 // indirect
google.golang.org/protobuf v1.32.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect

168
go.sum
View File

@ -2,6 +2,8 @@ code.vikunja.io/web v0.0.0-20210706160506-d85def955bd3 h1:MXl7Ff9a/ndTpuEmQKIGhq
code.vikunja.io/web v0.0.0-20210706160506-d85def955bd3/go.mod h1:OgFO06HN1KpA4S7Dw/QAIeygiUPSeGJJn1ykz/sjZdU=
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
gitee.com/travelliu/dm v1.8.11192/go.mod h1:DHTzyhCrM843x9VdKVbZ+GKXGRbKM2sJ4LxihRxShkE=
@ -9,12 +11,8 @@ github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0/go.mod h1:h6H6c8enJmmocHUbL
github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0=
github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/ClickHouse/ch-go v0.55.0 h1:jw4Tpx887YXrkyL5DfgUome/po8MLz92nz2heOQ6RjQ=
github.com/ClickHouse/ch-go v0.55.0/go.mod h1:kQT2f+yp2p+sagQA/7kS6G3ukym+GQ5KAu1kuFAFDiU=
github.com/ClickHouse/ch-go v0.58.2 h1:jSm2szHbT9MCAB1rJ3WuCJqmGLi5UTjlNu+f530UTS0=
github.com/ClickHouse/ch-go v0.58.2/go.mod h1:Ap/0bEmiLa14gYjCiRkYGbXvbe8vwdrfTYWhsuQ99aw=
github.com/ClickHouse/clickhouse-go/v2 v2.9.1 h1:IeE2bwVvAba7Yw5ZKu98bKI4NpDmykEy6jUaQdJJCk8=
github.com/ClickHouse/clickhouse-go/v2 v2.9.1/go.mod h1:teXfZNM90iQ99Jnuht+dxQXCuhDZ8nvvMoTJOFrcmcg=
github.com/ClickHouse/clickhouse-go/v2 v2.18.0 h1:O1LicIeg2JS2V29fKRH4+yT3f6jvvcJBm506dpVQ4mQ=
github.com/ClickHouse/clickhouse-go/v2 v2.18.0/go.mod h1:ztQvX6wm7kAbhJslS87EXEhOVNY/TObXwyURnGju5FQ=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
@ -28,21 +26,13 @@ github.com/ThreeDotsLabs/watermill v1.3.5 h1:50JEPEhMGZQMh08ct0tfO1PsgMOAOhV3zxK
github.com/ThreeDotsLabs/watermill v1.3.5/go.mod h1:O/u/Ptyrk5MPTxSeWM5vzTtZcZfxXfO9PK9eXTYiFZY=
github.com/adlio/trello v1.10.0 h1:ia/rzoBwJJKr4IqnMlrU6n09CVqeyaahSkEVcV5/gPc=
github.com/adlio/trello v1.10.0/go.mod h1:I4Lti4jf2KxjTNgTqs5W3lLuE78QZZdYbbPnQQGwjOo=
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
github.com/arran4/golang-ical v0.2.3 h1:C4Vj7+BjJBIrAJhHgi6Ku+XUkQVugRq4re5Cqj5QVdE=
github.com/arran4/golang-ical v0.2.3/go.mod h1:RqMuPGmwRRwjkb07hmm+JBqcWa1vF1LvVmPtSZN2OhQ=
github.com/arran4/golang-ical v0.2.4 h1:0/rTXn2qqEekLKec3SzRRy+z7pCLtniMb0KD/dPogUo=
github.com/arran4/golang-ical v0.2.4/go.mod h1:RqMuPGmwRRwjkb07hmm+JBqcWa1vF1LvVmPtSZN2OhQ=
github.com/arran4/golang-ical v0.2.5 h1:zaAdee/cOnOCeSuxUSgkWnF9jZl/oYq2ZgDk+LU3wGs=
github.com/arran4/golang-ical v0.2.5/go.mod h1:RqMuPGmwRRwjkb07hmm+JBqcWa1vF1LvVmPtSZN2OhQ=
github.com/arran4/golang-ical v0.2.6 h1:WRpbLKSIMjujycCNKGAjOALyj6evvklVpWXH+Hp72G4=
github.com/arran4/golang-ical v0.2.6/go.mod h1:RqMuPGmwRRwjkb07hmm+JBqcWa1vF1LvVmPtSZN2OhQ=
github.com/arran4/golang-ical v0.2.7 h1:VO7YlVaGupZE15aj6NhUhte/MIfZuoIzkoI71VsG6Gg=
github.com/arran4/golang-ical v0.2.7/go.mod h1:RqMuPGmwRRwjkb07hmm+JBqcWa1vF1LvVmPtSZN2OhQ=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/bbrks/go-blurhash v1.1.1 h1:uoXOxRPDca9zHYabUTwvS4KnY++KKUbwFo+Yxb8ME4M=
@ -119,10 +109,6 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/getsentry/sentry-go v0.25.0 h1:q6Eo+hS+yoJlTO3uu/azhQadsD8V+jQn2D8VvX1eOyI=
github.com/getsentry/sentry-go v0.25.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
github.com/getsentry/sentry-go v0.26.0 h1:IX3++sF6/4B5JcevhdZfdKIHfyvMmAq/UnqcyT2H6mA=
github.com/getsentry/sentry-go v0.26.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps=
github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
@ -137,8 +123,8 @@ github.com/go-faster/city v1.0.1 h1:4WAxSZ3V2Ws4QRDrscLEDcibJY8uf41H6AhXDrNDcGw=
github.com/go-faster/city v1.0.1/go.mod h1:jKcUJId49qdW3L1qKHH/3wPeUstCVpVSXTM6vO3VcTw=
github.com/go-faster/errors v0.6.1 h1:nNIPOBkprlKzkThvS/0YaX8Zs9KewLCOSFQS5BU06FI=
github.com/go-faster/errors v0.6.1/go.mod h1:5MGV2/2T9yvlrbhe9pD9LO5Z/2zCSq2T8j+Jpi2LAyY=
github.com/go-jose/go-jose/v3 v3.0.1 h1:pWmKFVtt+Jl0vBZTIpz/eAKwsm6LkIxDVVbFHKkchhA=
github.com/go-jose/go-jose/v3 v3.0.1/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k=
github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
@ -166,10 +152,10 @@ github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-sql-driver/mysql v1.8.0 h1:UtktXaU2Nb64z/pLiGIxY4431SJ4/dR5cjMmlVHgnT4=
github.com/go-sql-driver/mysql v1.8.0/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-testfixtures/testfixtures/v3 v3.9.0 h1:938g5V+GWLVejm3Hc+nWCuEXRlcglZDDlN/t1gWzcSY=
github.com/go-testfixtures/testfixtures/v3 v3.9.0/go.mod h1:cdsKD2ApFBjdog9jRsz6EJqF+LClq/hrwE9K/1Dzo4s=
github.com/go-testfixtures/testfixtures/v3 v3.10.0 h1:BrBwN7AuC+74g5qtk9D59TLGOaEa8Bw1WmIsf+SyzWc=
github.com/go-testfixtures/testfixtures/v3 v3.10.0/go.mod h1:z8RoleoNtibi6Ar8ziCW7e6PQ+jWiqbUWvuv8AMe4lo=
github.com/gocarina/gocsv v0.0.0-20231116093920-b87c2d0e983a h1:RYfmiM0zluBJOiPDJseKLEN4BapJ42uSi9SZBQ2YyiA=
@ -183,8 +169,11 @@ github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keL
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw=
github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
@ -209,19 +198,17 @@ github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@ -329,13 +316,9 @@ 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.11.3 h1:Upyu3olaqSHkCjs1EJJwQ3WId8b8b1hxbogyommKktM=
github.com/labstack/echo/v4 v4.11.3/go.mod h1:UcGuQ8V6ZNRmSweBIJkPvGfwCMIlFmiqrPqiEBfPYws=
github.com/labstack/echo/v4 v4.11.4 h1:vDZmA+qNeh1pd/cCkEicDMrjtrnMGQ1QFI9gWN1zGq8=
github.com/labstack/echo/v4 v4.11.4/go.mod h1:noh7EvLwqDsmh/X/HWKPUl1AjzJrhyptRyEbQJfxen8=
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
github.com/labstack/gommon v0.4.1 h1:gqEff0p/hTENGMABzezPoPSRtIh1Cvw0ueMOe0/dfOk=
github.com/labstack/gommon v0.4.1/go.mod h1:TyTrpPqxR5KMk8LKVtLmfMjeQ5FEkBYdxLYPw/WfrOM=
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
github.com/laurent22/ical-go v0.1.1-0.20181107184520-7e5d6ade8eef h1:RZnRnSID1skF35j/15KJ6hKZkdIC/teQClJK5wP5LU4=
@ -383,18 +366,8 @@ github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI=
github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mattn/go-sqlite3 v1.14.20 h1:BAZ50Ns0OFBNxdAqFhbZqdPcht1Xlb16pDCqkq1spr0=
github.com/mattn/go-sqlite3 v1.14.20/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mattn/go-sqlite3 v1.14.21 h1:IXocQLOykluc3xPE0Lvy8FtggMz1G+U3mEjg+0zGizc=
github.com/mattn/go-sqlite3 v1.14.21/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@ -424,15 +397,11 @@ 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/paulmach/orb v0.9.0 h1:MwA1DqOKtvCgm7u9RZ/pnYejTeDJPnr0+0oFajBbJqk=
github.com/paulmach/orb v0.9.0/go.mod h1:SudmOk85SXtmXAB3sLGyJ6tZy/8pdfrV0o6ef98Xc30=
github.com/paulmach/orb v0.11.1 h1:3koVegMC4X/WeiXYz9iswopaTwMem53NzTJuTF20JzU=
github.com/paulmach/orb v0.11.1/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU=
github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY=
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/pierrec/lz4/v4 v4.1.17 h1:kV4Ip+/hUBC+8T6+2EgburRtkE9ef4nbY3f4dFhGjMc=
github.com/pierrec/lz4/v4 v4.1.17/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pierrec/lz4/v4 v4.1.18 h1:xaKrnTkyoqfh1YItXl56+6KJNVYWlEEPuAQW9xsplYQ=
github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
@ -446,28 +415,14 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg=
github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q=
github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY=
github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk=
github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA=
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 h1:v7DLqVdK4VrYkVD5diGdl4sxJurKJEMnODWRJlxV9oM=
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU=
github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k=
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY=
github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY=
github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM=
github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY=
github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI=
github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY=
github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE=
github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
github.com/redis/go-redis/v9 v9.3.1 h1:KqdY8U+3X6z+iACvumCNxnoluToB+9Me+TvyFa21Mds=
github.com/redis/go-redis/v9 v9.3.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
github.com/redis/go-redis/v9 v9.4.0 h1:Yzoz33UZw9I/mFhx4MNrB6Fk+XHO1VukNcCa1+lwyKk=
github.com/redis/go-redis/v9 v9.4.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
github.com/redis/go-redis/v9 v9.5.0 h1:Xe9TKMmZv939gwTBcvc0n1tzK5l2re0pKw/W/tN3amw=
github.com/redis/go-redis/v9 v9.5.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
github.com/redis/go-redis/v9 v9.5.1 h1:H1X4D3yHPaYrkL5X06Wh6xNVM/pX0Ft4RV0vMGvLBh8=
github.com/redis/go-redis/v9 v9.5.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
@ -530,23 +485,22 @@ 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.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/swaggo/swag v1.16.2 h1:28Pp+8DkQoV+HLzLx8RGJZXNGKbFqnuvSbAAtoxiY04=
github.com/swaggo/swag v1.16.2/go.mod h1:6YzXnDcpr0767iOejs318CwYkCQqyGer6BizOg03f+E=
github.com/swaggo/swag v1.16.3 h1:PnCYjPCah8FK4I26l2F/KQ4yz3sILcVUN3cTlBFA9Pg=
github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI/gRk=
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tj/assert v0.0.3 h1:Df/BlaZ20mq6kuai7f5z2TvPFiwC3xaWJSDQNiIS3Rk=
github.com/tj/assert v0.0.3/go.mod h1:Ne6X72Q+TB1AteidzQncjw9PabbMp4PBMZ1k+vd1Pvk=
github.com/tkuchiki/go-timezone v0.2.2 h1:MdHR65KwgVTwWFQrota4SKzc4L5EfuH5SdZZGtk/P2Q=
github.com/tkuchiki/go-timezone v0.2.2/go.mod h1:oFweWxYl35C/s7HMVZXiA19Jr9Y0qJHMaG/J2TES4LY=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/typesense/typesense-go v0.9.0 h1:V1sk0QN6jHevHHiV3GZyL6aIb6Oa8QsmyXRUYJj2Zfg=
github.com/typesense/typesense-go v0.9.0/go.mod h1:4mq4FYHzU7csU/KHaZoyG2bCSKl7GrCeyAr2YhXT1/0=
github.com/typesense/typesense-go v1.0.0 h1:/8Lr1yf9YjmUKdn/xbTNy+OhwOvBd0noBTRkcB22Uhw=
github.com/typesense/typesense-go v1.0.0/go.mod h1:4mq4FYHzU7csU/KHaZoyG2bCSKl7GrCeyAr2YhXT1/0=
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
@ -573,20 +527,13 @@ github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7Jul
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.6.0 h1:boZcn2GTjpsynOsC0iJHnBWa4Bi0qzfJjthwauItG68=
github.com/yuin/goldmark v1.6.0/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.7.0 h1:EfOIvIMZIzHdB/R/zVrikYLPPwJlfMcNczJFMs1m6sA=
github.com/yuin/goldmark v1.7.0/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
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.mongodb.org/mongo-driver v1.11.1/go.mod h1:s7p5vEtfbeR1gYi6pnj3c3/urpbLv2T5Sfd6Rp2HBB8=
go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g=
go.opentelemetry.io/otel v1.15.0 h1:NIl24d4eiLJPM0vKn4HjLYM+UZf6gSfi9Z+NmCxkWbk=
go.opentelemetry.io/otel v1.15.0/go.mod h1:qfwLEbWhLPk5gyWrne4XnF0lC8wtywbuJbgfAE3zbek=
go.opentelemetry.io/otel v1.22.0 h1:xS7Ku+7yTFvDfDraDIJVpw7XPyuHlB9MCiqqX5mcJ6Y=
go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI=
go.opentelemetry.io/otel/trace v1.15.0 h1:5Fwje4O2ooOxkfyqI/kJwxWotggDLix4BSAvpE1wlpo=
go.opentelemetry.io/otel/trace v1.15.0/go.mod h1:CUsmE2Ht1CRkvE8OsMESvraoZrrcgD1J2W8GV1ev0Y4=
go.opentelemetry.io/otel/trace v1.22.0 h1:Hg6pPujv0XG9QaVbGOBVHunyuLcCC3jN7WEhPx83XD0=
go.opentelemetry.io/otel/trace v1.22.0/go.mod h1:RbbHXVqKES9QhzZq/fE5UnOSILqRt40a21sPw2He1xo=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
@ -610,7 +557,6 @@ golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
@ -622,17 +568,12 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/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.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4=
golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8=
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
@ -641,6 +582,7 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/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.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=
@ -662,27 +604,18 @@ golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qx
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.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ=
golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM=
golang.org/x/oauth2 v0.16.0 h1:aDkGMBSYxElaoP81NpoUoz2oo2R2wHdZpGToUxfyQrQ=
golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o=
golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ=
golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI=
golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -713,22 +646,18 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/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.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
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.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
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=
@ -737,6 +666,7 @@ 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.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -757,6 +687,7 @@ golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
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.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -776,8 +707,8 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
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.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@ -801,6 +732,7 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
@ -850,14 +782,8 @@ nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYm
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
src.techknowlogick.com/xgo v1.7.1-0.20231205202227-c7ed78300ce9 h1:lcNlqzNPv7WBKVRqGXWjs+nt9r5WBf2FG+eBOCUcyLM=
src.techknowlogick.com/xgo v1.7.1-0.20231205202227-c7ed78300ce9/go.mod h1:31CE1YKtDOrKTk9PSnjTpe6YbO6W/0LTYZ1VskL09oU=
src.techknowlogick.com/xgo v1.7.1-0.20240124202215-77ac23f331fe h1:8t+5jXWFfMOxWi0OIBMpRSM5agX6xhwA5+em+P9nGTE=
src.techknowlogick.com/xgo v1.7.1-0.20240124202215-77ac23f331fe/go.mod h1:31CE1YKtDOrKTk9PSnjTpe6YbO6W/0LTYZ1VskL09oU=
src.techknowlogick.com/xgo v1.7.1-0.20240206191224-5aae65575674 h1:/uC4C2ANN3SsMZmsLSDWvfjJPP+nHisQIfD8ElkjBdI=
src.techknowlogick.com/xgo v1.7.1-0.20240206191224-5aae65575674/go.mod h1:31CE1YKtDOrKTk9PSnjTpe6YbO6W/0LTYZ1VskL09oU=
src.techknowlogick.com/xgo v1.7.1-0.20240206231429-45b9ea635e03 h1:GMq57lSFGhXrFuOJ/HuSf67Y/SfzWxlJRZus262YxXw=
src.techknowlogick.com/xgo v1.7.1-0.20240206231429-45b9ea635e03/go.mod h1:31CE1YKtDOrKTk9PSnjTpe6YbO6W/0LTYZ1VskL09oU=
src.techknowlogick.com/xgo v1.7.1-0.20240305180710-770b8eae9cec h1:ICDp83UjJvLcOFWHAxr7vmziKIHJkE4jsIF1mbT9Bwk=
src.techknowlogick.com/xgo v1.7.1-0.20240305180710-770b8eae9cec/go.mod h1:31CE1YKtDOrKTk9PSnjTpe6YbO6W/0LTYZ1VskL09oU=
src.techknowlogick.com/xormigrate v1.7.1 h1:RKGLLUAqJ+zO8iZ7eOc7oLH7f0cs2gfXSZSvBRBHnlY=
src.techknowlogick.com/xormigrate v1.7.1/go.mod h1:YGNBdj8prENlySwIKmfoEXp7ILGjAltyKFXD0qLgD7U=
xorm.io/builder v0.3.7/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
@ -866,11 +792,5 @@ xorm.io/builder v0.3.13 h1:a3jmiVVL19psGeXx8GIurTp7p0IIgqeDmwhcR6BAOAo=
xorm.io/builder v0.3.13/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
xorm.io/xorm v1.0.5/go.mod h1:uF9EtbhODq5kNWxMbnBEj8hRRZnlcNSz2t2N7HW/+A4=
xorm.io/xorm v1.3.3/go.mod h1:qFJGFoVYbbIdnz2vaL5OxSQ2raleMpyRRalnq3n9OJo=
xorm.io/xorm v1.3.4 h1:vWFKzR3DhGUDl5b4srhUjhDwjxkZAc4C7BFszpu0swI=
xorm.io/xorm v1.3.4/go.mod h1:qFJGFoVYbbIdnz2vaL5OxSQ2raleMpyRRalnq3n9OJo=
xorm.io/xorm v1.3.6 h1:hfpWHkDIWWqUi8FRF2H2M9O8lO3Ov47rwFcS9gPzPkU=
xorm.io/xorm v1.3.6/go.mod h1:qFJGFoVYbbIdnz2vaL5OxSQ2raleMpyRRalnq3n9OJo=
xorm.io/xorm v1.3.7 h1:mLceAGu0b87r9pD4qXyxGHxifOXIIrAdVcA6k95/osw=
xorm.io/xorm v1.3.7/go.mod h1:LsCCffeeYp63ssk0pKumP6l96WZcHix7ChpurcLNuMw=
xorm.io/xorm v1.3.8 h1:CJmplmWqfSRpLWSPMmqz+so8toBp3m7ehuRehIWedZo=
xorm.io/xorm v1.3.8/go.mod h1:LsCCffeeYp63ssk0pKumP6l96WZcHix7ChpurcLNuMw=

View File

@ -25,7 +25,6 @@ import (
"context"
"crypto/sha256"
"fmt"
"github.com/iancoleman/strcase"
"io"
"os"
"os/exec"
@ -34,6 +33,8 @@ import (
"strings"
"time"
"github.com/iancoleman/strcase"
"github.com/magefile/mage/mg"
"golang.org/x/sync/errgroup"
"gopkg.in/yaml.v3"

View File

@ -25,6 +25,7 @@
title: testbucket4 - other project
project_id: 2
created_by_id: 1
position: 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
@ -241,4 +242,11 @@
project_id: 38
created_by_id: 15
created: 2020-04-18 21:13:52
updated: 2020-04-18 21:13:52
updated: 2020-04-18 21:13:52
- id: 40
title: testbucket40
project_id: 2
created_by_id: 1
position: 10
created: 2020-04-18 21:13:52
updated: 2020-04-18 21:13:52

View File

@ -16,6 +16,7 @@
owner_id: 3
position: 2
done_bucket_id: 4
default_bucket_id: 40
updated: 2018-12-02 15:13:12
created: 2018-12-01 15:13:12
-

View File

@ -55,3 +55,7 @@
team_id: 13
user_id: 10
created: 2018-12-01 15:13:12
-
team_id: 14
user_id: 10
created: 2018-12-01 15:13:12

View File

@ -28,4 +28,9 @@
created_by_id: 7
- id: 13
name: testteam13
created_by_id: 7
created_by_id: 7
- id: 14
name: testteam14
created_by_id: 7
oidc_id: 14
issuer: "https://some.issuer"

View File

@ -0,0 +1,43 @@
// 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"
)
type teams20230104152903 struct {
OidcID string `xorm:"varchar(250) null" maxLength:"250" json:"oidc_id"`
}
func (teams20230104152903) TableName() string {
return "teams"
}
func init() {
migrations = append(migrations, &xormigrate.Migration{
ID: "20230104152903",
Description: "Adding OidcID to teams",
Migrate: func(tx *xorm.Engine) error {
return tx.Sync2(teams20230104152903{})
},
Rollback: func(tx *xorm.Engine) error {
return nil
},
})
}

View File

@ -0,0 +1,43 @@
// 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"
)
type teams20240304153738 struct {
Issuer string `xorm:"text null" json:"-"`
}
func (teams20240304153738) TableName() string {
return "teams"
}
func init() {
migrations = append(migrations, &xormigrate.Migration{
ID: "20240304153738",
Description: "Add issuer column to teams to track the OIDC provider this issue has bene created by",
Migrate: func(tx *xorm.Engine) error {
return tx.Sync2(teams20240304153738{})
},
Rollback: func(tx *xorm.Engine) error {
return nil
},
})
}

View File

@ -1059,7 +1059,6 @@ func (err ErrTeamNameCannotBeEmpty) HTTPError() web.HTTPError {
return web.HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeTeamNameCannotBeEmpty, Message: "The team name cannot be empty"}
}
// ErrTeamDoesNotExist represents an error where a team does not exist
type ErrTeamDoesNotExist struct {
TeamID int64
}
@ -1178,6 +1177,54 @@ func (err ErrTeamDoesNotHaveAccessToProject) HTTPError() web.HTTPError {
return web.HTTPError{HTTPCode: http.StatusForbidden, Code: ErrCodeTeamDoesNotHaveAccessToProject, Message: "This team does not have access to the project."}
}
// ErrOIDCTeamDoesNotExist represents an error where a team with specified oidcId property does not exist for a given issuer
type ErrOIDCTeamDoesNotExist struct {
OidcID string
Issuer string
}
// IsErrOIDCTeamDoesNotExist checks if an error is ErrOIDCTeamDoesNotExist.
func IsErrOIDCTeamDoesNotExist(err error) bool {
_, ok := err.(ErrOIDCTeamDoesNotExist)
return ok
}
// ErrTeamDoesNotExist represents an error where a team does not exist
func (err ErrOIDCTeamDoesNotExist) Error() string {
return fmt.Sprintf("No team could be found for the given oidcId and issuer. [OIDC ID : %v] [Issuer: %v] ", err.OidcID, err.Issuer)
}
// ErrCodeTeamDoesNotExist holds the unique world-error code of this error
const ErrCodeOIDCTeamDoesNotExist = 6008
// HTTPError holds the http error description
func (err ErrOIDCTeamDoesNotExist) HTTPError() web.HTTPError {
return web.HTTPError{HTTPCode: http.StatusNotFound, Code: ErrCodeTeamDoesNotExist, Message: "No team could be found for the given OIDC ID and issuer."}
}
// ErrOIDCTeamsDoNotExistForUser represents an error where an oidcTeam does not exist for the user
type ErrOIDCTeamsDoNotExistForUser struct {
UserID int64
}
// IsErrOIDCTeamsDoNotExistForUser checks if an error is ErrOIDCTeamsDoNotExistForUser.
func IsErrOIDCTeamsDoNotExistForUser(err error) bool {
_, ok := err.(ErrOIDCTeamsDoNotExistForUser)
return ok
}
func (err ErrOIDCTeamsDoNotExistForUser) Error() string {
return fmt.Sprintf("No teams with property oidcId could be found for user [User ID: %d]", err.UserID)
}
// ErrCodeTeamDoesNotExist holds the unique world-error code of this error
const ErrCodeOIDCTeamsDoNotExistForUser = 6009
// HTTPError holds the http error description
func (err ErrOIDCTeamsDoNotExistForUser) HTTPError() web.HTTPError {
return web.HTTPError{HTTPCode: http.StatusNotFound, Code: ErrCodeTeamDoesNotExist, Message: "No Teams with property oidcId could be found for User."}
}
// ====================
// User <-> Project errors
// ====================

View File

@ -316,8 +316,8 @@ func GetProjectSimplByTaskID(s *xorm.Session, taskID int64) (l *Project, err err
return &project, nil
}
// GetProjectsSimplByTaskIDs gets a list of projects by a task ids
func GetProjectsSimplByTaskIDs(s *xorm.Session, taskIDs []int64) (ps map[int64]*Project, err error) {
// GetProjectsMapSimplByTaskIDs gets a list of projects by a task ids
func GetProjectsMapSimplByTaskIDs(s *xorm.Session, taskIDs []int64) (ps map[int64]*Project, err error) {
ps = make(map[int64]*Project)
err = s.
Select("projects.*").
@ -328,8 +328,18 @@ func GetProjectsSimplByTaskIDs(s *xorm.Session, taskIDs []int64) (ps map[int64]*
return
}
// GetProjectsByIDs returns a map of projects from a slice with project ids
func GetProjectsByIDs(s *xorm.Session, projectIDs []int64) (projects map[int64]*Project, err error) {
func GetProjectsSimplByTaskIDs(s *xorm.Session, taskIDs []int64) (ps []*Project, err error) {
err = s.
Select("projects.*").
Table(Project{}).
Join("INNER", "tasks", "projects.id = tasks.project_id").
In("tasks.id", taskIDs).
Find(&ps)
return
}
// GetProjectsMapByIDs returns a map of projects from a slice with project ids
func GetProjectsMapByIDs(s *xorm.Session, projectIDs []int64) (projects map[int64]*Project, err error) {
projects = make(map[int64]*Project, len(projectIDs))
if len(projectIDs) == 0 {
@ -340,6 +350,17 @@ func GetProjectsByIDs(s *xorm.Session, projectIDs []int64) (projects map[int64]*
return
}
func GetProjectsByIDs(s *xorm.Session, projectIDs []int64) (projects []*Project, err error) {
projects = make([]*Project, 0, len(projectIDs))
if len(projectIDs) == 0 {
return
}
err = s.In("id", projectIDs).Find(&projects)
return
}
type projectOptions struct {
search string
user *user.User
@ -455,7 +476,7 @@ SELECT DISTINCT * FROM all_projects ORDER BY position `+limitSQL, args...).Find(
totalCount, err = s.
SQL(`WITH RECURSIVE all_projects as (`+baseQuery+`)
SELECT count(*) FROM all_projects GROUP BY all_projects.id`, args...).
SELECT COUNT(DISTINCT all_projects.id) FROM all_projects`, args...).
Count(&Project{})
if err != nil {
return nil, 0, err
@ -519,19 +540,24 @@ func getSavedFilterProjects(s *xorm.Session, doer *user.User) (savedFiltersProje
}
// GetAllParentProjects returns all parents of a given project
func (p *Project) GetAllParentProjects(s *xorm.Session) (err error) {
if p.ParentProjectID == 0 {
return
}
parent, err := GetProjectSimpleByID(s, p.ParentProjectID)
if err != nil {
return err
}
p.ParentProject = parent
return parent.GetAllParentProjects(s)
func GetAllParentProjects(s *xorm.Session, projectID int64) (allProjects map[int64]*Project, err error) {
allProjects = make(map[int64]*Project)
err = s.SQL(`WITH RECURSIVE all_projects AS (
SELECT
p.*
FROM
projects p
WHERE
p.id = ?
UNION ALL
SELECT
p.*
FROM
projects p
INNER JOIN all_projects pc ON p.ID = pc.parent_project_id
)
SELECT DISTINCT * FROM all_projects`, projectID).Find(&allProjects)
return
}
// addProjectDetails adds owner user objects and project tasks to all projects in the slice
@ -559,7 +585,7 @@ func addProjectDetails(s *xorm.Session, projects []*Project, a web.Auth) (err er
return err
}
subscriptions, err := GetSubscriptions(s, SubscriptionEntityProject, projectIDs, a)
subscriptions, err := GetSubscriptionsForProjects(s, projects, a)
if err != nil {
log.Errorf("An error occurred while getting project subscriptions for a project: %s", err.Error())
subscriptions = make(map[int64][]*Subscription)
@ -646,12 +672,14 @@ func checkProjectBeforeUpdateOrDelete(s *xorm.Session, project *Project) (err er
}
}
var parent *Project
parent, err = GetProjectSimpleByID(s, project.ParentProjectID)
allProjects, err := GetAllParentProjects(s, project.ParentProjectID)
if err != nil {
return err
}
var parent *Project
parent = allProjects[project.ParentProjectID]
// Check if there's a cycle in the parent relation
parentsVisited := make(map[int64]bool)
parentsVisited[project.ID] = true
@ -660,11 +688,7 @@ func checkProjectBeforeUpdateOrDelete(s *xorm.Session, project *Project) (err er
break
}
// FIXME: Can we do this with better performance?
parent, err = GetProjectSimpleByID(s, parent.ParentProjectID)
if err != nil {
return err
}
parent = allProjects[parent.ParentProjectID]
if parentsVisited[parent.ID] {
return &ErrProjectCannotHaveACyclicRelationship{

View File

@ -223,7 +223,11 @@ func GetSubscriptions(s *xorm.Session, entityType SubscriptionEntityType, entity
switch entityType {
case SubscriptionEntityProject:
return getSubscriptionsForProjects(s, entityIDs, u)
projects, err := GetProjectsByIDs(s, entityIDs)
if err != nil {
return nil, err
}
return GetSubscriptionsForProjects(s, projects, u)
case SubscriptionEntityTask:
subs, err := getSubscriptionsForTasks(s, entityIDs, u)
if err != nil {
@ -232,22 +236,34 @@ func GetSubscriptions(s *xorm.Session, entityType SubscriptionEntityType, entity
// If the task does not have a subscription directly or from its project, get the one
// from the parent and return it instead.
var taskIDsWithoutSubscription []int64
for _, eID := range entityIDs {
if _, has := subs[eID]; has {
continue
}
task, err := GetTaskByIDSimple(s, eID)
if err != nil {
return nil, err
}
projectSubscriptions, err := getSubscriptionsForProjects(s, []int64{task.ProjectID}, u)
if err != nil {
return nil, err
}
for _, subscription := range projectSubscriptions {
subs[eID] = subscription // The first project subscription is the subscription we're looking for
break
taskIDsWithoutSubscription = append(taskIDsWithoutSubscription, eID)
}
projects, err := GetProjectsSimplByTaskIDs(s, taskIDsWithoutSubscription)
if err != nil {
return nil, err
}
tasks, err := GetTasksSimpleByIDs(s, taskIDsWithoutSubscription)
if err != nil {
return nil, err
}
projectSubscriptions, err := GetSubscriptionsForProjects(s, projects, u)
if err != nil {
return nil, err
}
for _, task := range tasks {
sub, has := projectSubscriptions[task.ProjectID]
if has {
subs[task.ID] = sub
}
}
@ -257,48 +273,57 @@ func GetSubscriptions(s *xorm.Session, entityType SubscriptionEntityType, entity
return
}
func getSubscriptionsForProjects(s *xorm.Session, projectIDs []int64, u *user.User) (projectsToSubscriptions map[int64][]*Subscription, err error) {
origEntityIDs := projectIDs
func GetSubscriptionsForProjects(s *xorm.Session, projects []*Project, a web.Auth) (projectsToSubscriptions map[int64][]*Subscription, err error) {
u, is := a.(*user.User)
if u != nil && !is {
return
}
var ps = make(map[int64]*Project)
origProjectIDs := make([]int64, 0, len(projects))
allProjectIDs := make([]int64, 0, len(projects))
for _, eID := range projectIDs {
if eID < 1 {
for _, p := range projects {
ps[p.ID] = p
origProjectIDs = append(origProjectIDs, p.ID)
allProjectIDs = append(allProjectIDs, p.ID)
}
// We can't just use the projects we have, we need to fetch the parents
// because they may not be loaded in the same object
for _, p := range projects {
if p.ParentProjectID == 0 {
continue
}
ps[eID], err = GetProjectSimpleByID(s, eID)
if err != nil && IsErrProjectDoesNotExist(err) {
// If the project does not exist, it might got deleted. There could still be subscribers though.
delete(ps, eID)
if _, has := ps[p.ParentProjectID]; has {
continue
}
if err != nil {
return nil, err
}
err = ps[eID].GetAllParentProjects(s)
parents, err := GetAllParentProjects(s, p.ID)
if err != nil {
return nil, err
}
parentIDs := []int64{}
var parent = ps[eID].ParentProject
// Walk the tree up until we reach the top
var parent = parents[p.ParentProjectID] // parent now has a pointer…
ps[p.ID].ParentProject = parents[p.ParentProjectID]
for parent != nil {
parentIDs = append(parentIDs, parent.ID)
parent = parent.ParentProject
allProjectIDs = append(allProjectIDs, parent.ID)
parent = parents[parent.ParentProjectID] // … which means we can update it here and then update the pointer in the map
}
// Now we have all parent ids
projectIDs = append(projectIDs, parentIDs...) // the child project id is already in there
}
var subscriptions []*Subscription
if u != nil {
err = s.
Where("user_id = ?", u.ID).
And(getSubscriberCondForEntities(SubscriptionEntityProject, projectIDs)).
And(getSubscriberCondForEntities(SubscriptionEntityProject, allProjectIDs)).
Find(&subscriptions)
} else {
err = s.
And(getSubscriberCondForEntities(SubscriptionEntityProject, projectIDs)).
And(getSubscriberCondForEntities(SubscriptionEntityProject, allProjectIDs)).
Find(&subscriptions)
}
if err != nil {
@ -313,7 +338,7 @@ func getSubscriptionsForProjects(s *xorm.Session, projectIDs []int64, u *user.Us
// Rearrange so that subscriptions trickle down
for _, eID := range origEntityIDs {
for _, eID := range origProjectIDs {
// If the current project does not have a subscription, climb up the tree until a project has one,
// then use that subscription for all child projects
_, has := projectsToSubscriptions[eID]

View File

@ -139,7 +139,7 @@ func RegisterOverdueReminderCron() {
}
}
projects, err := GetProjectsSimplByTaskIDs(s, taskIDs)
projects, err := GetProjectsMapSimplByTaskIDs(s, taskIDs)
if err != nil {
log.Errorf("[Undone Overdue Tasks Reminder] Could not get projects for tasks: %s", err)
return

View File

@ -173,7 +173,7 @@ func getTasksWithRemindersDueAndTheirUsers(s *xorm.Session, now time.Time) (remi
seen := make(map[int64]map[int64]bool)
projects, err := GetProjectsSimplByTaskIDs(s, taskIDs)
projects, err := GetProjectsMapSimplByTaskIDs(s, taskIDs)
if err != nil {
return
}

View File

@ -356,6 +356,11 @@ func GetTaskSimple(s *xorm.Session, t *Task) (task Task, err error) {
return
}
func GetTasksSimpleByIDs(s *xorm.Session, ids []int64) (tasks []*Task, err error) {
err = s.In("id", ids).Find(&tasks)
return
}
// GetTasksByIDs returns all tasks for a project of ids
func (bt *BulkTask) GetTasksByIDs(s *xorm.Session) (err error) {
for _, id := range bt.IDs {
@ -586,7 +591,7 @@ func addMoreInfoToTasks(s *xorm.Session, taskMap map[int64]*Task, a web.Auth) (e
}
// Get all identifiers
projects, err := GetProjectsByIDs(s, projectIDs)
projects, err := GetProjectsMapByIDs(s, projectIDs)
if err != nil {
return err
}
@ -652,7 +657,8 @@ func setTaskBucket(s *xorm.Session, task *Task, originalTask *Task, doCheckBucke
}
var bucket *Bucket
if task.Done && originalTask != nil && !originalTask.Done {
if task.Done && originalTask != nil &&
(!originalTask.Done || task.ProjectID != originalTask.ProjectID) {
task.BucketID = project.DoneBucketID
}
@ -661,7 +667,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) {
// But if the task was moved between projects, don't update the done bucket
// because then we have it already updated to the done bucket.
if task.BucketID == 0 ||
(originalTask != nil && task.ProjectID != 0 && originalTask.ProjectID != task.ProjectID && !task.Done) {
task.BucketID, err = getDefaultBucketID(s, project)
if err != nil {
return
@ -855,17 +864,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, nil)
project, err := GetProjectSimpleByID(s, t.ProjectID)
if err != nil {
return err
}
targetBucket, err := setTaskBucket(s, t, &ot, t.BucketID != 0 && t.BucketID != ot.BucketID, project)
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.
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

@ -345,8 +345,7 @@ func TestTask_Update(t *testing.T) {
err = s.Commit()
require.NoError(t, err)
assert.Equal(t, int64(4), task.BucketID) // bucket 4 is the default bucket on project 2
assert.True(t, task.Done) // bucket 4 is the done bucket, so the task should be marked as done as well
assert.Equal(t, int64(40), task.BucketID) // bucket 40 is the default bucket on project 2
})
t.Run("marking a task as done should move it to the done bucket", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
@ -387,7 +386,29 @@ func TestTask_Update(t *testing.T) {
db.AssertExists(t, "tasks", map[string]interface{}{
"id": 1,
"project_id": 2,
"bucket_id": 4,
"bucket_id": 40,
}, false)
})
t.Run("move done task to another project with a done bucket", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
s := db.NewSession()
defer s.Close()
task := &Task{
ID: 2,
Done: true,
ProjectID: 2,
}
err := task.Update(s, u)
require.NoError(t, err)
err = s.Commit()
require.NoError(t, err)
db.AssertExists(t, "tasks", map[string]interface{}{
"id": 2,
"project_id": 2,
"bucket_id": 4, // 4 is the done bucket
"done": true,
}, false)
})
t.Run("repeating tasks should not be moved to the done bucket", func(t *testing.T) {

View File

@ -44,7 +44,6 @@ func (tm *TeamMember) Create(s *xorm.Session, a web.Auth) (err error) {
if err != nil {
return err
}
// Check if the user exists
member, err := user2.GetUserByUsername(s, tm.Username)
if err != nil {
@ -109,6 +108,12 @@ func (tm *TeamMember) Delete(s *xorm.Session, _ web.Auth) (err error) {
return
}
func (tm *TeamMember) MembershipExists(s *xorm.Session) (exists bool, err error) {
return s.
Where("team_id = ? AND user_id = ?", tm.TeamID, tm.UserID).
Exist(&TeamMember{})
}
// Update toggles a team member's admin status
// @Summary Toggle a team member's admin status
// @Description If a user is team admin, this will make them member and vise-versa.

View File

@ -38,6 +38,10 @@ type Team struct {
// The team's description.
Description string `xorm:"longtext null" json:"description"`
CreatedByID int64 `xorm:"bigint not null INDEX" json:"-"`
// The team's oidc id delivered by the oidc provider
OidcID string `xorm:"varchar(250) null" maxLength:"250" json:"oidc_id"`
// Contains the issuer extracted from the vikunja_groups claim if this team was created through oidc
Issuer string `xorm:"text null" json:"-"`
// The user who created this team.
CreatedBy *user.User `xorm:"-" json:"created_by"`
@ -86,11 +90,18 @@ func (*TeamMember) TableName() string {
// TeamUser is the team member type
type TeamUser struct {
user.User `xorm:"extends"`
// Whether or not the member is an admin of the team. See the docs for more about what a team admin can do
// Whether the member is an admin of the team. See the docs for more about what a team admin can do
Admin bool `json:"admin"`
TeamID int64 `json:"-"`
}
// OIDCTeam is the relevant data for a team and is delivered by oidc token
type OIDCTeam struct {
Name string
OidcID string
Description string
}
// GetTeamByID gets a team by its ID
func GetTeamByID(s *xorm.Session, id int64) (team *Team, err error) {
if id < 1 {
@ -120,6 +131,34 @@ func GetTeamByID(s *xorm.Session, id int64) (team *Team, err error) {
return
}
// GetTeamByOidcID returns a team matching the given oidc_id
// For oidc team creation oidcID and Name need to be set
func GetTeamByOidcIDAndIssuer(s *xorm.Session, oidcID string, issuer string) (*Team, error) {
team := &Team{}
has, err := s.
Table("teams").
Where("oidc_id = ? AND issuer = ?", oidcID, issuer).
Get(team)
if !has || err != nil {
return nil, ErrOIDCTeamDoesNotExist{issuer, oidcID}
}
return team, nil
}
func FindAllOidcTeamIDsForUser(s *xorm.Session, userID int64) (ts []int64, err error) {
err = s.
Table("team_members").
Where("user_id = ? ", userID).
Join("RIGHT", "teams", "teams.id = team_members.team_id").
Where("teams.oidc_id != ? AND teams.oidc_id IS NOT NULL", "").
Cols("teams.id").
Find(&ts)
if ts == nil || err != nil {
return ts, err
}
return ts, nil
}
func addMoreInfoToTeams(s *xorm.Session, teams []*Team) (err error) {
if len(teams) == 0 {
@ -172,6 +211,42 @@ func addMoreInfoToTeams(s *xorm.Session, teams []*Team) (err error) {
return
}
// CreateNewTeam creates a new team and assignes the user that has caused creation
// as the first member of the team
// If firstUserShouldBeAdmin is true, the user will be an admin of the team
// Note: this function has been extracted from the Create method to allow
// an additional parameter to control whether the user should become admin of the team
func (t *Team) CreateNewTeam(s *xorm.Session, a web.Auth, firstUserShouldBeAdmin bool) (err error) {
doer, err := user.GetFromAuth(a)
if err != nil {
return err
}
// Check if we have a name
if t.Name == "" {
return ErrTeamNameCannotBeEmpty{}
}
t.CreatedByID = doer.ID
t.CreatedBy = doer
_, err = s.Insert(t)
if err != nil {
return
}
tm := TeamMember{TeamID: t.ID, Username: doer.Username, Admin: firstUserShouldBeAdmin}
if err = tm.Create(s, doer); err != nil {
return err
}
return events.Dispatch(&TeamCreatedEvent{
Team: t,
Doer: a,
})
}
// ReadOne implements the CRUD method to get one team
// @Summary Gets one team
// @Description Returns a team by its ID.
@ -252,34 +327,13 @@ func (t *Team) ReadAll(s *xorm.Session, a web.Auth, search string, page int, per
// @Failure 500 {object} models.Message "Internal error"
// @Router /teams [put]
func (t *Team) Create(s *xorm.Session, a web.Auth) (err error) {
doer, err := user.GetFromAuth(a)
err = t.CreateNewTeam(s, a, true)
if err != nil {
return err
}
// Check if we have a name
if t.Name == "" {
return ErrTeamNameCannotBeEmpty{}
}
t.CreatedByID = doer.ID
t.CreatedBy = doer
_, err = s.Insert(t)
if err != nil {
return
}
// Insert the current user as member and admin
tm := TeamMember{TeamID: t.ID, Username: doer.Username, Admin: true}
if err = tm.Create(s, doer); err != nil {
return err
}
return events.Dispatch(&TeamCreatedEvent{
Team: t,
Doer: a,
})
return
}
// Delete deletes a team

View File

@ -21,21 +21,22 @@ import (
"encoding/json"
"errors"
"net/http"
"strconv"
"strings"
"code.vikunja.io/web/handler"
"code.vikunja.io/api/pkg/db"
"xorm.io/xorm"
"code.vikunja.io/api/pkg/log"
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/modules/auth"
"code.vikunja.io/api/pkg/user"
"code.vikunja.io/api/pkg/utils"
"github.com/coreos/go-oidc/v3/oidc"
petname "github.com/dustinkirkland/golang-petname"
"github.com/labstack/echo/v4"
"golang.org/x/oauth2"
"xorm.io/xorm"
)
// Callback contains the callback after an auth request was made and redirected
@ -53,16 +54,17 @@ type Provider struct {
AuthURL string `json:"auth_url"`
LogoutURL string `json:"logout_url"`
ClientID string `json:"client_id"`
Scope string `json:"scope"`
ClientSecret string `json:"-"`
openIDProvider *oidc.Provider
Oauth2Config *oauth2.Config `json:"-"`
}
type claims struct {
Email string `json:"email"`
Name string `json:"name"`
PreferredUsername string `json:"preferred_username"`
Nickname string `json:"nickname"`
Email string `json:"email"`
Name string `json:"name"`
PreferredUsername string `json:"preferred_username"`
Nickname string `json:"nickname"`
VikunjaGroups []map[string]interface{} `json:"vikunja_groups"`
}
func init() {
@ -96,6 +98,7 @@ func HandleCallback(c echo.Context) error {
// Check if the provider exists
providerKey := c.Param("provider")
provider, err := GetProvider(providerKey)
log.Debugf("Provider: %v", provider)
if err != nil {
log.Error(err)
return handler.HandleHTTPError(err, c)
@ -145,6 +148,7 @@ func HandleCallback(c echo.Context) error {
// Extract custom claims
cl := &claims{}
err = idToken.Claims(cl)
if err != nil {
log.Errorf("Error getting token claims for provider %s: %v", provider.Name, err)
@ -198,16 +202,183 @@ func HandleCallback(c echo.Context) error {
return handler.HandleHTTPError(err, c)
}
// does the oidc token contain well formed "vikunja_groups" through vikunja_scope
log.Debugf("Checking for vikunja_groups in token %v", cl.VikunjaGroups)
teamData, errs := getTeamDataFromToken(cl.VikunjaGroups, provider)
if len(teamData) > 0 {
for _, err := range errs {
log.Errorf("Error creating teams for user and vikunja groups %s: %v", cl.VikunjaGroups, err)
}
// find old teams for user through oidc
oldOidcTeams, err := models.FindAllOidcTeamIDsForUser(s, u.ID)
if err != nil {
log.Debugf("No oidc teams found for user %v", err)
}
oidcTeams, err := AssignOrCreateUserToTeams(s, u, teamData, idToken.Issuer)
if err != nil {
log.Errorf("Could not proceed with group routine %v", err)
}
teamIDsToLeave := utils.NotIn(oldOidcTeams, oidcTeams)
err = RemoveUserFromTeamsByIds(s, u, teamIDsToLeave)
if err != nil {
log.Errorf("Found error while leaving teams %v", err)
}
errs := RemoveEmptySSOTeams(s, teamIDsToLeave)
if len(errs) > 0 {
for _, err := range errs {
log.Errorf("Found error while removing empty teams %v", err)
}
}
}
err = s.Commit()
if err != nil {
_ = s.Rollback()
log.Errorf("Error creating new team for provider %s: %v", provider.Name, err)
return handler.HandleHTTPError(err, c)
}
// Create token
return auth.NewUserAuthTokenResponse(u, c, false)
}
func AssignOrCreateUserToTeams(s *xorm.Session, u *user.User, teamData []*models.OIDCTeam, issuer string) (oidcTeams []int64, err error) {
if len(teamData) == 0 {
return
}
// check if we have seen these teams before.
// find or create Teams and assign user as teammember.
teams, err := GetOrCreateTeamsByOIDC(s, teamData, u, issuer)
if err != nil {
log.Errorf("Error verifying team for %v, got %v. Error: %v", u.Name, teams, err)
return nil, err
}
for _, team := range teams {
tm := models.TeamMember{TeamID: team.ID, UserID: u.ID, Username: u.Username}
exists, _ := tm.MembershipExists(s)
if !exists {
err = tm.Create(s, u)
if err != nil {
log.Errorf("Could not assign user %s to team %s: %v", u.Username, team.Name, err)
}
}
oidcTeams = append(oidcTeams, team.ID)
}
return oidcTeams, err
}
func RemoveEmptySSOTeams(s *xorm.Session, teamIDs []int64) (errs []error) {
for _, teamID := range teamIDs {
count, err := s.Where("team_id = ?", teamID).Count(&models.TeamMember{})
if count == 0 && err == nil {
log.Debugf("SSO team with id %v has no members. It will be deleted", teamID)
_, _err := s.Where("id = ?", teamID).Delete(&models.Team{})
if _err != nil {
errs = append(errs, _err)
}
}
}
return errs
}
func RemoveUserFromTeamsByIds(s *xorm.Session, u *user.User, teamIDs []int64) (err error) {
if len(teamIDs) < 1 {
return nil
}
log.Debugf("Removing team_member with user_id %v from team_ids %v", u.ID, teamIDs)
_, err = s.In("team_id", teamIDs).And("user_id = ?", u.ID).Delete(&models.TeamMember{})
return err
}
func getTeamDataFromToken(groups []map[string]interface{}, provider *Provider) (teamData []*models.OIDCTeam, errs []error) {
teamData = []*models.OIDCTeam{}
errs = []error{}
for _, team := range groups {
var name string
var description string
var oidcID string
_, exists := team["name"]
if exists {
name = team["name"].(string)
}
_, exists = team["description"]
if exists {
description = team["description"].(string)
}
_, exists = team["oidcID"]
if exists {
switch t := team["oidcID"].(type) {
case string:
oidcID = team["oidcID"].(string)
case int64:
oidcID = strconv.FormatInt(team["oidcID"].(int64), 10)
case float64:
oidcID = strconv.FormatFloat(team["oidcID"].(float64), 'f', -1, 64)
default:
log.Errorf("No oidcID assigned for %v or type %v not supported", team, t)
}
}
if name == "" || oidcID == "" {
log.Errorf("Claim of your custom scope does not hold name or oidcID for automatic group assignment through oidc provider. Please check %s", provider.Name)
errs = append(errs, &user.ErrOpenIDCustomScopeMalformed{})
continue
}
teamData = append(teamData, &models.OIDCTeam{Name: name, OidcID: oidcID, Description: description})
}
return teamData, errs
}
func getOIDCTeamName(name string) string {
return name + " (OIDC)"
}
func CreateOIDCTeam(s *xorm.Session, teamData *models.OIDCTeam, u *user.User, issuer string) (team *models.Team, err error) {
team = &models.Team{
Name: getOIDCTeamName(teamData.Name),
Description: teamData.Description,
OidcID: teamData.OidcID,
Issuer: issuer,
}
err = team.CreateNewTeam(s, u, false)
return team, err
}
// GetOrCreateTeamsByOIDC returns a slice of teams which were generated from the oidc data. If a team did not exist previously it is automatically created.
func GetOrCreateTeamsByOIDC(s *xorm.Session, teamData []*models.OIDCTeam, u *user.User, issuer string) (te []*models.Team, err error) {
te = []*models.Team{}
// Procedure can only be successful if oidcID is set
for _, oidcTeam := range teamData {
team, err := models.GetTeamByOidcIDAndIssuer(s, oidcTeam.OidcID, issuer)
if err != nil && !models.IsErrOIDCTeamDoesNotExist(err) {
return nil, err
}
if err != nil && models.IsErrOIDCTeamDoesNotExist(err) {
log.Debugf("Team with oidc_id %v and name %v does not exist. Creating team… ", oidcTeam.OidcID, oidcTeam.Name)
newTeam, err := CreateOIDCTeam(s, oidcTeam, u, issuer)
if err != nil {
return te, err
}
te = append(te, newTeam)
continue
}
if team.Name != getOIDCTeamName(oidcTeam.Name) {
team.Name = getOIDCTeamName(oidcTeam.Name)
err = team.Update(s, u)
if err != nil {
return nil, err
}
}
log.Debugf("Team with oidc_id %v and name %v already exists.", team.OidcID, team.Name)
te = append(te, team)
}
return te, err
}
func getOrCreateUser(s *xorm.Session, cl *claims, issuer, subject string) (u *user.User, err error) {
// Check if the user exists for that issuer and subject
u, err = user.GetUserWithEmail(s, &user.User{
Issuer: issuer,

View File

@ -20,7 +20,9 @@ import (
"testing"
"code.vikunja.io/api/pkg/db"
"code.vikunja.io/api/pkg/models"
"code.vikunja.io/api/pkg/user"
"code.vikunja.io/api/pkg/utils"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -95,4 +97,145 @@ func TestGetOrCreateUser(t *testing.T) {
"email": cl.Email,
}, false)
})
t.Run("existing user, non existing team", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
s := db.NewSession()
defer s.Close()
team := "new sso team"
oidcID := "47404"
cl := &claims{
Email: "other-email-address@some.service.com",
VikunjaGroups: []map[string]interface{}{
{"name": team, "oidcID": oidcID},
},
}
u, err := getOrCreateUser(s, cl, "https://some.service.com", "12345")
require.NoError(t, err)
teamData, errs := getTeamDataFromToken(cl.VikunjaGroups, nil)
for _, err := range errs {
require.NoError(t, err)
}
require.NoError(t, err)
oidcTeams, err := AssignOrCreateUserToTeams(s, u, teamData, "https://some.issuer")
require.NoError(t, err)
err = s.Commit()
require.NoError(t, err)
db.AssertExists(t, "users", map[string]interface{}{
"id": u.ID,
"email": cl.Email,
}, false)
db.AssertExists(t, "teams", map[string]interface{}{
"id": oidcTeams,
"name": team + " (OIDC)",
}, false)
})
t.Run("existing user, assign to existing team", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
s := db.NewSession()
defer s.Close()
team := "testteam14"
oidcID := "14"
cl := &claims{
Email: "other-email-address@some.service.com",
VikunjaGroups: []map[string]interface{}{
{"name": team, "oidcID": oidcID},
},
}
u := &user.User{ID: 10}
teamData, errs := getTeamDataFromToken(cl.VikunjaGroups, nil)
for _, err := range errs {
require.NoError(t, err)
}
oidcTeams, err := AssignOrCreateUserToTeams(s, u, teamData, "https://some.issuer")
require.NoError(t, err)
err = s.Commit()
require.NoError(t, err)
db.AssertExists(t, "team_members", map[string]interface{}{
"team_id": oidcTeams,
"user_id": u.ID,
}, false)
})
t.Run("existing user, remove from existing team", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
s := db.NewSession()
defer s.Close()
cl := &claims{
Email: "other-email-address@some.service.com",
VikunjaGroups: []map[string]interface{}{},
}
u := &user.User{ID: 10}
teamData, errs := getTeamDataFromToken(cl.VikunjaGroups, nil)
if len(errs) > 0 {
for _, err := range errs {
require.NoError(t, err)
}
}
oldOidcTeams, err := models.FindAllOidcTeamIDsForUser(s, u.ID)
require.NoError(t, err)
oidcTeams, err := AssignOrCreateUserToTeams(s, u, teamData, "https://some.issuer")
require.NoError(t, err)
teamIDsToLeave := utils.NotIn(oldOidcTeams, oidcTeams)
require.NoError(t, err)
err = RemoveUserFromTeamsByIds(s, u, teamIDsToLeave)
require.NoError(t, err)
errs = RemoveEmptySSOTeams(s, teamIDsToLeave)
for _, err = range errs {
require.NoError(t, err)
}
errs = RemoveEmptySSOTeams(s, teamIDsToLeave)
for _, err = range errs {
require.NoError(t, err)
}
err = s.Commit()
require.NoError(t, err)
db.AssertMissing(t, "team_members", map[string]interface{}{
"team_id": oidcTeams,
"user_id": u.ID,
})
})
t.Run("existing user, remove from existing team and delete team", func(t *testing.T) {
db.LoadAndAssertFixtures(t)
s := db.NewSession()
defer s.Close()
cl := &claims{
Email: "other-email-address@some.service.com",
VikunjaGroups: []map[string]interface{}{},
}
u := &user.User{ID: 10}
teamData, errs := getTeamDataFromToken(cl.VikunjaGroups, nil)
if len(errs) > 0 {
for _, err := range errs {
require.NoError(t, err)
}
}
oldOidcTeams, err := models.FindAllOidcTeamIDsForUser(s, u.ID)
require.NoError(t, err)
oidcTeams, err := AssignOrCreateUserToTeams(s, u, teamData, "https://some.issuer")
require.NoError(t, err)
teamIDsToLeave := utils.NotIn(oldOidcTeams, oidcTeams)
require.NoError(t, err)
err = RemoveUserFromTeamsByIds(s, u, teamIDsToLeave)
require.NoError(t, err)
errs = RemoveEmptySSOTeams(s, teamIDsToLeave)
for _, err := range errs {
require.NoError(t, err)
}
err = s.Commit()
require.NoError(t, err)
db.AssertMissing(t, "teams", map[string]interface{}{
"id": oidcTeams,
})
})
}

View File

@ -125,6 +125,10 @@ func getProviderFromMap(pi map[string]interface{}) (provider *Provider, err erro
logoutURL = ""
}
scope, _ := pi["scope"].(string)
if scope == "" {
scope = "openid profile email"
}
provider = &Provider{
Name: pi["name"].(string),
Key: k,
@ -132,6 +136,7 @@ func getProviderFromMap(pi map[string]interface{}) (provider *Provider, err erro
OriginalAuthURL: pi["authurl"].(string),
ClientSecret: pi["clientsecret"].(string),
LogoutURL: logoutURL,
Scope: scope,
}
cl, is := pi["clientid"].(int)

View File

@ -17,6 +17,8 @@
package trello
import (
"bytes"
"code.vikunja.io/api/pkg/config"
"code.vikunja.io/api/pkg/files"
"code.vikunja.io/api/pkg/log"
@ -24,6 +26,7 @@ import (
"code.vikunja.io/api/pkg/modules/migration"
"code.vikunja.io/api/pkg/user"
"github.com/adlio/trello"
"github.com/yuin/goldmark"
)
// Migration represents the trello migration struct
@ -74,7 +77,7 @@ func (m *Migration) Name() string {
// @Router /migration/trello/auth [get]
func (m *Migration) AuthURL() string {
return "https://trello.com/1/authorize" +
"?expiration=1hour" +
"?expiration=never" +
"&scope=read" +
"&callback_method=fragment" +
"&response_type=token" +
@ -160,6 +163,16 @@ func getTrelloData(token string) (trelloData []*trello.Board, err error) {
return
}
func convertMarkdownToHTML(input string) (output string, err error) {
var buf bytes.Buffer
err = goldmark.Convert([]byte(input), &buf)
if err != nil {
return
}
//#nosec - we are not responsible to escape this as we don't know the context where it is used
return buf.String(), nil
}
// Converts all previously obtained data from trello into the vikunja format.
// `trelloData` should contain all boards with their projects and cards respectively.
func convertTrelloDataToVikunja(trelloData []*trello.Board, token string) (fullVikunjaHierachie []*models.ProjectWithTasksAndBuckets, err error) {
@ -220,11 +233,15 @@ func convertTrelloDataToVikunja(trelloData []*trello.Board, token string) (fullV
// The usual stuff: Title, description, position, bucket id
task := &models.Task{
Title: card.Name,
Description: card.Desc,
KanbanPosition: card.Pos,
BucketID: bucketID,
}
task.Description, err = convertMarkdownToHTML(card.Desc)
if err != nil {
return nil, err
}
if card.Due != nil {
task.DueDate = *card.Due
}

View File

@ -52,7 +52,7 @@ func TestConvertTrelloToVikunja(t *testing.T) {
Cards: []*trello.Card{
{
Name: "Test Card 1",
Desc: "Card Description",
Desc: "Card Description **bold**",
Pos: 123,
Due: &time1,
Labels: []*trello.Label{
@ -218,7 +218,7 @@ func TestConvertTrelloToVikunja(t *testing.T) {
{
Task: models.Task{
Title: "Test Card 1",
Description: "Card Description",
Description: "<p>Card Description <strong>bold</strong></p>\n",
BucketID: 1,
KanbanPosition: 123,
DueDate: time1,

View File

@ -156,10 +156,10 @@ func setupSentry(e *echo.Echo) {
if hub != nil {
hub.WithScope(func(scope *sentry.Scope) {
scope.SetExtra("url", c.Request().URL)
hub.CaptureException(err)
hub.CaptureException(herr.Internal)
})
} else {
sentry.CaptureException(err)
sentry.CaptureException(herr.Internal)
log.Debugf("Could not add context for sending error '%s' to sentry", err.Error())
}
log.Debugf("Error '%s' sent to sentry", err.Error())

View File

@ -8300,6 +8300,11 @@ const docTemplate = `{
"maxLength": 250,
"minLength": 1
},
"oidc_id": {
"description": "The team's oidc id delivered by the oidc provider",
"type": "string",
"maxLength": 250
},
"updated": {
"description": "A timestamp when this relation was last updated. You cannot change this value.",
"type": "string"
@ -8362,7 +8367,7 @@ const docTemplate = `{
"type": "object",
"properties": {
"admin": {
"description": "Whether or not the member is an admin of the team. See the docs for more about what a team admin can do",
"description": "Whether the member is an admin of the team. See the docs for more about what a team admin can do",
"type": "boolean"
},
"created": {
@ -8430,6 +8435,11 @@ const docTemplate = `{
"maxLength": 250,
"minLength": 1
},
"oidc_id": {
"description": "The team's oidc id delivered by the oidc provider",
"type": "string",
"maxLength": 250
},
"right": {
"$ref": "#/definitions/models.Right"
},
@ -8573,6 +8583,9 @@ const docTemplate = `{
},
"name": {
"type": "string"
},
"scope": {
"type": "string"
}
}
},

View File

@ -8292,6 +8292,11 @@
"maxLength": 250,
"minLength": 1
},
"oidc_id": {
"description": "The team's oidc id delivered by the oidc provider",
"type": "string",
"maxLength": 250
},
"updated": {
"description": "A timestamp when this relation was last updated. You cannot change this value.",
"type": "string"
@ -8354,7 +8359,7 @@
"type": "object",
"properties": {
"admin": {
"description": "Whether or not the member is an admin of the team. See the docs for more about what a team admin can do",
"description": "Whether the member is an admin of the team. See the docs for more about what a team admin can do",
"type": "boolean"
},
"created": {
@ -8422,6 +8427,11 @@
"maxLength": 250,
"minLength": 1
},
"oidc_id": {
"description": "The team's oidc id delivered by the oidc provider",
"type": "string",
"maxLength": 250
},
"right": {
"$ref": "#/definitions/models.Right"
},
@ -8565,6 +8575,9 @@
},
"name": {
"type": "string"
},
"scope": {
"type": "string"
}
}
},

View File

@ -904,6 +904,10 @@ definitions:
maxLength: 250
minLength: 1
type: string
oidc_id:
description: The team's oidc id delivered by the oidc provider
maxLength: 250
type: string
updated:
description: A timestamp when this relation was last updated. You cannot change
this value.
@ -954,8 +958,8 @@ definitions:
models.TeamUser:
properties:
admin:
description: Whether or not the member is an admin of the team. See the docs
for more about what a team admin can do
description: Whether the member is an admin of the team. See the docs for
more about what a team admin can do
type: boolean
created:
description: A timestamp when this task was created. You cannot change this
@ -1007,6 +1011,10 @@ definitions:
maxLength: 250
minLength: 1
type: string
oidc_id:
description: The team's oidc id delivered by the oidc provider
maxLength: 250
type: string
right:
$ref: '#/definitions/models.Right'
updated:
@ -1116,6 +1124,8 @@ definitions:
type: string
name:
type: string
scope:
type: string
type: object
todoist.Migration:
properties:

View File

@ -426,6 +426,32 @@ func (err *ErrNoOpenIDEmailProvided) HTTPError() web.HTTPError {
}
}
// ErrNoOpenIDEmailProvided represents a "NoEmailProvided" kind of error.
type ErrOpenIDCustomScopeMalformed struct {
}
// IsErrNoEmailProvided checks if an error is a ErrNoOpenIDEmailProvided.
func IsErrOpenIDCustomScopeMalformed(err error) bool {
_, ok := err.(*ErrOpenIDCustomScopeMalformed)
return ok
}
func (err *ErrOpenIDCustomScopeMalformed) Error() string {
return "Custom Scope malformed"
}
// ErrCodeNoOpenIDEmailProvided holds the unique world-error code of this error
const ErrCodeOpenIDCustomScopeMalformed = 1022
// HTTPError holds the http error description
func (err *ErrOpenIDCustomScopeMalformed) HTTPError() web.HTTPError {
return web.HTTPError{
HTTPCode: http.StatusPreconditionFailed,
Code: ErrCodeOpenIDCustomScopeMalformed,
Message: "The custom scope set by the OIDC provider is malformed. Please make sure the openid provider sets the data correctly for your scope. Check especially to have set an oidcID",
}
}
// ErrAccountDisabled represents a "AccountDisabled" kind of error.
type ErrAccountDisabled struct {
UserID int64

View File

@ -0,0 +1,37 @@
// 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 utils
// find the elements which appear in slice1, but not in slice2
func NotIn(slice1 []int64, slice2 []int64) []int64 {
var diff []int64
for _, s1 := range slice1 {
found := false
for _, s2 := range slice2 {
if s1 == s2 {
found = true
break
}
}
// int64 not found. We add it to return slice
if !found {
diff = append(diff, s1)
}
}
return diff
}