Compare commits
34 Commits
dd85cc1357
...
5262a44f2f
Author | SHA1 | Date | |
---|---|---|---|
5262a44f2f | |||
9147e6739f | |||
570d146b21 | |||
cc2c158b9d | |||
78a206c818 | |||
fc5703ac8c | |||
3277f6acf7 | |||
8a1e98a7f2 | |||
9a2655dbf1 | |||
d48aa101cf | |||
df45675df3 | |||
afd6bde74d | |||
e23014dbe4 | |||
8e65ffb99b | |||
3f6d85497f | |||
88b9ea6a96 | |||
67f863120e | |||
ffede02ddc | |||
3973ce985d | |||
2351194547 | |||
b7ec24ff52 | |||
aaac4c6dfc | |||
2e52cc1802 | |||
20ede346b4 | |||
b76ad8efe2 | |||
d695681a0e | |||
52c3075c3d | |||
e837c2a003 | |||
f670d394fa | |||
71cc7334cc | |||
fc1867ec70 | |||
d69482cda9 | |||
71d60e908a | |||
86b7d224ab |
22
.editorconfig
Normal file
22
.editorconfig
Normal file
|
@ -0,0 +1,22 @@
|
|||
# EditorConfig is awesome: https://EditorConfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = tab
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = false
|
||||
insert_final_newline = false
|
||||
|
||||
[*.go]
|
||||
indent_style = tab
|
||||
|
||||
[*.{yaml,yml}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.json]
|
||||
indent_style = space
|
||||
indent_size = 4
|
173
CHANGELOG.md
173
CHANGELOG.md
|
@ -7,6 +7,179 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
All releases can be found on https://code.vikunja.io/api/releases.
|
||||
|
||||
## [0.17.0] - 2021-05-14
|
||||
|
||||
### Added
|
||||
|
||||
* Add a "done" option to kanban buckets (#821)
|
||||
* Add arm64 builds
|
||||
* Add basic auth for metrics endpoint
|
||||
* Add bucket limit validation
|
||||
* Add crud endpoints for notifications (#801)
|
||||
* Add endpoint to remove a list background
|
||||
* Add events (#777)
|
||||
* Add github funding link
|
||||
* Add link share password authentication (#831)
|
||||
* Add names for link shares (#829)
|
||||
* Add notifications package for easy sending of notifications (#779)
|
||||
* Add reminders for overdue tasks (#832)
|
||||
* Add repeat monthly setting for tasks (#834)
|
||||
* Add security information to readme
|
||||
* Add separate docker manifest file for latest docker images
|
||||
* Add systemd service file to linux packages
|
||||
* Add test for moving a task to another list
|
||||
* Enable searching users by full email or name
|
||||
* Expose tls parameter of Go MySQL driver to config file (#855)
|
||||
* Pagingation for tasks in kanban buckets (#805)
|
||||
|
||||
### Changed
|
||||
|
||||
* Change keyvalue.Get to return if a value exists or not instead of an error
|
||||
* Change main branch to main
|
||||
* Change test file names to unstable
|
||||
* Change the name of the newly created bucket from "New Bucket" to "Backlog"
|
||||
* Change unstable versions in migration tests
|
||||
* Check if we're on main and change the version name accordingly if that's the case
|
||||
* Cleanup listener names
|
||||
* Cleanup old docs themes submodule
|
||||
* Disable deb repo in drone
|
||||
* Don't keep old releases from os packages when releasing for master
|
||||
* Don't try to get users for tasks if no tasks were found when looking for reminders
|
||||
* Explicitly add docker build step for latest
|
||||
* Explicitly check if there are Ids before trying to get items by a list of Ids
|
||||
* Improve duration format of overdue tasks in reminders
|
||||
* Improve loading labels performance (#824)
|
||||
* Improve sending overdue task reminders by only sending one for all overdue tasks
|
||||
* Make sure all tables are properly pluralized
|
||||
* Only send reminders for undone tasks
|
||||
* Re-Enable migration test steps in pipeline
|
||||
* Refactor getting all namespaces
|
||||
* Remove unused tools from tools.go
|
||||
* Run all lint checks at once
|
||||
* Send a notification to the user when they are added to the list
|
||||
* Show empty avatar when the user was not found
|
||||
* Subscribe a user to a task when they are assigned to it
|
||||
* Subscriptions and notifications for namespaces, tasks and lists (#786)
|
||||
* Switch building the docs to download the theme instead of building
|
||||
* Switch telegram notifications to matrix notifications
|
||||
* Temporarily disable migration step
|
||||
* Temporary build fix
|
||||
* Update changelog
|
||||
* Update copyright year
|
||||
* Update README (#858)
|
||||
* Use golang's tzdata package to handle time zones
|
||||
|
||||
### Fixed
|
||||
|
||||
* Explicitly set darwin-10.15 when building binaries
|
||||
* Fix build
|
||||
* Fix checking list rights when accessing a bucket
|
||||
* Fix /dav/principals/*/ throwing a server error when accessed with GET instead of PROPFIND (#769)
|
||||
* Fix deleting task relations
|
||||
* Fix docs
|
||||
* Fix drone file
|
||||
* Fix due dates with times when migrating from todoist
|
||||
* Fix event error handler retrying infinitely
|
||||
* Fix filter for task index
|
||||
* Fix getting lists for shared, favorite and saved lists namespace
|
||||
* Fix getting user info from /user endpoint for link shares
|
||||
* Fix IncrBy and DecrBy in memory keyvalue implementation if there was no value set previously
|
||||
* Fix lint
|
||||
* Fix matrix notify room id
|
||||
* Fix moving repeating tasks to the done bucket
|
||||
* Fix multiarch docker image building
|
||||
* Fix not able to make saved filters favorite
|
||||
* Fix notifications table not being created on initial setup
|
||||
* Fix resetting the bucket limit
|
||||
* Fix retrieving over openid providers if there are none
|
||||
* Fix sending notifications to users if the user object didn't have an email
|
||||
* Fix setting the user in created_by when uploading an attachment
|
||||
* Fix shared lists showing up twice
|
||||
* Fix tests
|
||||
* Fix the shared lists pseudo namespace containing owned lists
|
||||
* Fix unstable version build file names
|
||||
* Fix user uploaded avatars
|
||||
* Pin golang alpine builder image to 3.12 to fix builds on arm
|
||||
* Revert "Update alpine Docker tag to v3.13 (#768)"
|
||||
|
||||
### Dependency Updates
|
||||
|
||||
* Update alpine Docker tag to v3.13 (#768)
|
||||
* Update github.com/gordonklaus/ineffassign commit hash to 2e10b26 (#803)
|
||||
* Update github.com/gordonklaus/ineffassign commit hash to d0e41b2 (#780)
|
||||
* Update golang.org/x/crypto commit hash to 0c34fe9 (#822)
|
||||
* Update golang.org/x/crypto commit hash to 3497b51 (#853)
|
||||
* Update golang.org/x/crypto commit hash to 38f3c27 (#854)
|
||||
* Update golang.org/x/crypto commit hash to 4f45737 (#836)
|
||||
* Update golang.org/x/crypto commit hash to 513c2a4 (#817)
|
||||
* Update golang.org/x/crypto commit hash to 5bf0f12 (#839)
|
||||
* Update golang.org/x/crypto commit hash to 5ea612d (#797)
|
||||
* Update golang.org/x/crypto commit hash to 83a5a9b (#840)
|
||||
* Update golang.org/x/crypto commit hash to b8e89b7 (#793)
|
||||
* Update golang.org/x/crypto commit hash to c07d793 (#861)
|
||||
* Update golang.org/x/crypto commit hash to cd7d49e (#860)
|
||||
* Update golang.org/x/crypto commit hash to e6e6c4f (#816)
|
||||
* Update golang.org/x/crypto commit hash to e9a3299 (#851)
|
||||
* Update golang.org/x/image commit hash to 4410531 (#788)
|
||||
* Update golang.org/x/image commit hash to 55ae14f (#787)
|
||||
* Update golang.org/x/image commit hash to 7319ad4 (#852)
|
||||
* Update golang.org/x/image commit hash to ac19c3e (#798)
|
||||
* Update golang.org/x/oauth2 commit hash to 0101308 (#776)
|
||||
* Update golang.org/x/oauth2 commit hash to 01de73c (#762)
|
||||
* Update golang.org/x/oauth2 commit hash to 16ff188 (#789)
|
||||
* Update golang.org/x/oauth2 commit hash to 22b0ada (#823)
|
||||
* Update golang.org/x/oauth2 commit hash to 2e8d934 (#827)
|
||||
* Update golang.org/x/oauth2 commit hash to 5366d9d (#813)
|
||||
* Update golang.org/x/oauth2 commit hash to 5e61552 (#833)
|
||||
* Update golang.org/x/oauth2 commit hash to 6667018 (#783)
|
||||
* Update golang.org/x/oauth2 commit hash to 81ed05c (#848)
|
||||
* Update golang.org/x/oauth2 commit hash to 8b1d76f (#764)
|
||||
* Update golang.org/x/oauth2 commit hash to 9bb9049 (#796)
|
||||
* Update golang.org/x/oauth2 commit hash to af13f52 (#773)
|
||||
* Update golang.org/x/oauth2 commit hash to ba52d33 (#794)
|
||||
* Update golang.org/x/oauth2 commit hash to cd4f82c (#815)
|
||||
* Update golang.org/x/oauth2 commit hash to d3ed898 (#765)
|
||||
* Update golang.org/x/oauth2 commit hash to f9ce19e (#775)
|
||||
* Update golang.org/x/sync commit hash to 036812b (#799)
|
||||
* Update golang.org/x/term commit hash to 6a3ed07 (#800)
|
||||
* Update golang.org/x/term commit hash to 72f3dc4 (#828)
|
||||
* Update golang.org/x/term commit hash to a79de54 (#850)
|
||||
* Update golang.org/x/term commit hash to b80969c (#843)
|
||||
* Update golang.org/x/term commit hash to c04ba85 (#849)
|
||||
* Update golang.org/x/term commit hash to de623e6 (#818)
|
||||
* Update golang.org/x/term commit hash to f5beecf (#845)
|
||||
* Update module adlio/trello to v1.9.0 (#825)
|
||||
* Update module coreos/go-oidc to v3 (#760)
|
||||
* Update module gabriel-vasile/mimetype to v1.2.0 (#812)
|
||||
* Update module gabriel-vasile/mimetype to v1.3.0 (#857)
|
||||
* Update module getsentry/sentry-go to v0.10.0 (#792)
|
||||
* Update module go-redis/redis/v8 to v8.4.10 (#771)
|
||||
* Update module go-redis/redis/v8 to v8.4.11 (#774)
|
||||
* Update module go-redis/redis/v8 to v8.4.9 (#770)
|
||||
* Update module go-redis/redis/v8 to v8.5.0 (#778)
|
||||
* Update module go-redis/redis/v8 to v8.6.0 (#795)
|
||||
* Update module go-sql-driver/mysql to v1.6.0 (#826)
|
||||
* Update module go-testfixtures/testfixtures/v3 to v3.5.0 (#761)
|
||||
* Update module go-testfixtures/testfixtures/v3 to v3.6.0 (#838)
|
||||
* Update module iancoleman/strcase to v0.1.3 (#766)
|
||||
* Update module imdario/mergo to v0.3.12 (#811)
|
||||
* Update module jgautheron/goconst to v1 (#804)
|
||||
* Update module labstack/echo/v4 to v4.2.0 (#785)
|
||||
* Update module labstack/echo/v4 to v4.2.1 (#810)
|
||||
* Update module labstack/echo/v4 to v4.2.2 (#830)
|
||||
* Update module labstack/echo/v4 to v4.3.0 (#856)
|
||||
* Update module lib/pq to v1.10.0 (#809)
|
||||
* Update module lib/pq to v1.10.1 (#841)
|
||||
* Update module mattn/go-sqlite3 to v1.14.7 (#835)
|
||||
* Update module olekukonko/tablewriter to v0.0.5 (#782)
|
||||
* Update module prometheus/client_golang to v1.10.0 (#819)
|
||||
* Update module spf13/afero to v1.6.0 (#820)
|
||||
* Update module spf13/cobra to v1.1.2 (#781)
|
||||
* Update module spf13/cobra to v1.1.3 (#784)
|
||||
* Update module src.techknowlogick.com/xgo to v1.3.0+1.16.0 (#791)
|
||||
* Update module src.techknowlogick.com/xgo to v1.4.0+1.16.2 (#814)
|
||||
* Update module stretchr/testify to v1.7.0 (#763)
|
||||
|
||||
## [0.16.1] - 2021-04-22
|
||||
|
||||
### Fixed
|
||||
|
|
17
README.md
17
README.md
|
@ -2,10 +2,10 @@
|
|||
|
||||
[![Build Status](https://drone.kolaente.de/api/badges/vikunja/api/status.svg)](https://drone.kolaente.de/vikunja/api)
|
||||
[![License: AGPL v3](https://img.shields.io/badge/License-AGPL%20v3-blue.svg)](LICENSE)
|
||||
[![Download](https://img.shields.io/badge/download-v0.16.1-brightgreen.svg)](https://dl.vikunja.io)
|
||||
[![Download](https://img.shields.io/badge/download-v0.17.0-brightgreen.svg)](https://dl.vikunja.io)
|
||||
[![Docker Pulls](https://img.shields.io/docker/pulls/vikunja/api.svg)](https://hub.docker.com/r/vikunja/api/)
|
||||
[![Swagger Docs](https://img.shields.io/badge/swagger-docs-brightgreen.svg)](https://try.vikunja.io/api/v1/docs)
|
||||
[![Go Report Card](https://goreportcard.com/badge/git.kolaente.de/vikunja/api)](https://goreportcard.com/report/git.kolaente.de/vikunja/api)
|
||||
[![Go Report Card](https://goreportcard.com/badge/kolaente.dev/vikunja/api)](https://goreportcard.com/report/kolaente.dev/vikunja/api)
|
||||
|
||||
# Vikunja API
|
||||
|
||||
|
@ -28,7 +28,7 @@ If you find any security-related issues you don't want to disclose publicly, ple
|
|||
|
||||
* Create TODO lists with tasks
|
||||
* Reminder for tasks
|
||||
* Namespaces: A "group" which bundels multiple lists
|
||||
* Namespaces: A "group" which bundles multiple lists
|
||||
* Share lists and namespaces with teams and users with granular permissions
|
||||
* Plenty of details for tasks
|
||||
|
||||
|
@ -40,23 +40,22 @@ try it on [try.vikunja.io](https://try.vikunja.io)!
|
|||
* [Installing](https://vikunja.io/docs/installing/)
|
||||
* [Build from source](https://vikunja.io/docs/build-from-sources/)
|
||||
* [Development setup](https://vikunja.io/docs/development/)
|
||||
* [Magefile](https://vikunja.io/docs/mage/)
|
||||
* [Magefile](https://vikunja.io/docs/magefile/)
|
||||
* [Testing](https://vikunja.io/docs/testing/)
|
||||
|
||||
All docs can be found on [the vikunja home page](https://vikunja.io/docs/).
|
||||
All docs can be found on [the Vikunja home page](https://vikunja.io/docs/).
|
||||
|
||||
### Roadmap
|
||||
|
||||
See [the roadmap](https://my.vikunja.cloud/share/QFyzYEmEYfSyQfTOmIRSwLUpkFjboaBqQCnaPmWd/auth) (hosted on Vikunja!) for more!
|
||||
|
||||
* [ ] [Mobile apps](https://code.vikunja.io/app) (seperate repo) *In Progress*
|
||||
* [ ] [Webapp](https://code.vikunja.io/frontend) (seperate repo) *In Progress*
|
||||
* [ ] [Mobile apps](https://code.vikunja.io/app) (separate repo) *In Progress*
|
||||
* [ ] [Webapp](https://code.vikunja.io/frontend) (separate repo) *In Progress*
|
||||
|
||||
## Contributing
|
||||
|
||||
Fork -> Push -> Pull-Request. Also see the [dev docs](https://vikunja.io/docs/development/) for more infos.
|
||||
Fork -> Push -> Pull-Request. Also see the [dev docs](https://vikunja.io/docs/development/) for more info.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the AGPLv3 License. See the [LICENSE](LICENSE) file for the full license text.
|
||||
|
||||
|
|
|
@ -62,6 +62,8 @@ database:
|
|||
# Secure connection mode. Only used with postgres.
|
||||
# (see https://pkg.go.dev/github.com/lib/pq?tab=doc#hdr-Connection_String_Parameters)
|
||||
sslmode: disable
|
||||
# Enable SSL/TLS for mysql connections. Options: false, true, skip-verify, preferred
|
||||
tls: false
|
||||
|
||||
cache:
|
||||
# If cache is enabled or not
|
||||
|
@ -260,10 +262,12 @@ 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:** The frontend expects to be redirected after authentication by the third party
|
||||
# **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.
|
||||
# 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 with your third party
|
||||
# auth service accordingy if you're using the default vikunja frontend.
|
||||
# Take a look at the [default config file](https://kolaente.dev/vikunja/api/src/branch/master/config.yml.sample) for more information about how to configure openid authentication.
|
||||
# Take a look at the [default config file](https://kolaente.dev/vikunja/api/src/branch/main/config.yml.sample) for more information about how to configure openid authentication.
|
||||
openid:
|
||||
# Enable or disable OpenID Connect authentication
|
||||
enabled: false
|
||||
|
|
|
@ -15,7 +15,7 @@ menu:
|
|||
We use go modules to vendor libraries for Vikunja, so you'll need at least go `1.11` to use these.
|
||||
If you don't intend to add new dependencies, go `1.9` and above should be fine.
|
||||
|
||||
To contribute to Vikunja, fork the project and work on the master branch.
|
||||
To contribute to Vikunja, fork the project and work on the main branch.
|
||||
|
||||
A lot of developing tasks are automated using a Magefile, so make sure to [take a look at it]({{< ref "mage.md">}}).
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ There are multiple categories of subcommands in the magefile:
|
|||
|
||||
## CI
|
||||
|
||||
These tasks are automatically run in our CI every time someone pushes to master or you update a pull request:
|
||||
These tasks are automatically run in our CI every time someone pushes to main or you update a pull request:
|
||||
|
||||
* `mage check:lint`
|
||||
* `mage check:fmt`
|
||||
|
|
|
@ -110,4 +110,4 @@ If it was called, no mails are being sent and you can instead assert they have b
|
|||
|
||||
## Example
|
||||
|
||||
Take a look at the [pkg/user/notifications.go](https://code.vikunja.io/api/src/branch/master/pkg/user/notifications.go) file for a good example.
|
||||
Take a look at the [pkg/user/notifications.go](https://code.vikunja.io/api/src/branch/main/pkg/user/notifications.go) file for a good example.
|
||||
|
|
|
@ -32,7 +32,7 @@ first:
|
|||
Vikunja supports using `toml`, `yaml`, `hcl`, `ini`, `json`, envfile, env variables and Java Properties files.
|
||||
We reccomend yaml or toml, but you're free to use whatever you want.
|
||||
|
||||
Vikunja provides a default [`config.yml`](https://kolaente.dev/vikunja/api/src/branch/master/config.yml.sample) file which you can use as a starting point.
|
||||
Vikunja provides a default [`config.yml`](https://kolaente.dev/vikunja/api/src/branch/main/config.yml.sample) file which you can use as a starting point.
|
||||
|
||||
# Config file locations
|
||||
|
||||
|
@ -46,7 +46,7 @@ Vikunja will search on various places for a config file:
|
|||
# Default configuration with explanations
|
||||
|
||||
The following explains all possible config variables and their defaults.
|
||||
You can find a full example configuration file in [here](https://code.vikunja.io/api/src/branch/master/config.yml.sample).
|
||||
You can find a full example configuration file in [here](https://code.vikunja.io/api/src/branch/main/config.yml.sample).
|
||||
|
||||
If you don't provide a value in your config file, their default will be used.
|
||||
|
||||
|
@ -55,7 +55,7 @@ If you don't provide a value in your config file, their default will be used.
|
|||
Most config variables are nested under some "higher-level" key.
|
||||
For example, the `interface` config variable is a child of the `service` key.
|
||||
|
||||
The docs below aim to reflect that leveling, but please also have a lookt at [the default config](https://code.vikunja.io/api/src/branch/master/config.yml.sample) file
|
||||
The docs below aim to reflect that leveling, but please also have a lookt at [the default config](https://code.vikunja.io/api/src/branch/main/config.yml.sample) file
|
||||
to better grasp how the nesting looks like.
|
||||
|
||||
<!-- Generated config will be injected here -->
|
||||
|
@ -237,6 +237,12 @@ Secure connection mode. Only used with postgres.
|
|||
|
||||
Default: `disable`
|
||||
|
||||
### tls
|
||||
|
||||
Enable SSL/TLS for mysql connections. Options: false, true, skip-verify, preferred
|
||||
|
||||
Default: `false`
|
||||
|
||||
---
|
||||
|
||||
## cache
|
||||
|
@ -609,10 +615,12 @@ Default: `<empty>`
|
|||
|
||||
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:** The frontend expects to be redirected after authentication by the third party
|
||||
**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.
|
||||
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 with your third party
|
||||
auth service accordingy if you're using the default vikunja frontend.
|
||||
Take a look at the [default config file](https://kolaente.dev/vikunja/api/src/branch/master/config.yml.sample) for more information about how to configure openid authentication.
|
||||
Take a look at the [default config file](https://kolaente.dev/vikunja/api/src/branch/main/config.yml.sample) for more information about how to configure openid authentication.
|
||||
|
||||
Default: `<empty>`
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ wget <download-url>
|
|||
### Verify the GPG signature
|
||||
|
||||
Starting with version `0.7`, all releases are signed using pgp.
|
||||
Releases from `master` will always be signed.
|
||||
Releases from `main` will always be signed.
|
||||
|
||||
To validate the downloaded zip file use the signiture file `.asc` and the key `FF054DACD908493A`:
|
||||
|
||||
|
|
20
go.mod
20
go.mod
|
@ -22,7 +22,7 @@ require (
|
|||
github.com/ThreeDotsLabs/watermill v1.1.1
|
||||
github.com/adlio/trello v1.9.0
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751
|
||||
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef
|
||||
github.com/asaskevich/govalidator/v11/v11 v11.0.1
|
||||
github.com/beevik/etree v1.1.0 // indirect
|
||||
github.com/c2h5oh/datasize v0.0.0-20200825124411-48ed595a09d2
|
||||
github.com/coreos/go-oidc v2.2.1+incompatible
|
||||
|
@ -31,21 +31,21 @@ require (
|
|||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/dustinkirkland/golang-petname v0.0.0-20191129215211-8e5a1ed0cff0
|
||||
github.com/gabriel-vasile/mimetype v1.2.0
|
||||
github.com/gabriel-vasile/mimetype v1.3.0
|
||||
github.com/getsentry/sentry-go v0.10.0
|
||||
github.com/go-errors/errors v1.1.1 // indirect
|
||||
github.com/go-redis/redis/v8 v8.6.0
|
||||
github.com/go-sql-driver/mysql v1.6.0
|
||||
github.com/go-testfixtures/testfixtures/v3 v3.6.0
|
||||
github.com/go-testfixtures/testfixtures/v3 v3.6.1
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
|
||||
github.com/golang/snappy v0.0.2 // indirect
|
||||
github.com/iancoleman/strcase v0.1.3
|
||||
github.com/imdario/mergo v0.3.12
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/labstack/echo/v4 v4.2.2
|
||||
github.com/labstack/echo/v4 v4.3.0
|
||||
github.com/labstack/gommon v0.3.0
|
||||
github.com/laurent22/ical-go v0.1.1-0.20181107184520-7e5d6ade8eef
|
||||
github.com/lib/pq v1.10.1
|
||||
github.com/lib/pq v1.10.2
|
||||
github.com/magefile/mage v1.11.0
|
||||
github.com/mailru/easyjson v0.7.6 // indirect
|
||||
github.com/mattn/go-colorable v0.1.8 // indirect
|
||||
|
@ -68,13 +68,13 @@ require (
|
|||
github.com/stretchr/testify v1.7.0
|
||||
github.com/swaggo/swag v1.7.0
|
||||
github.com/ulule/limiter/v3 v3.8.0
|
||||
github.com/yuin/goldmark v1.3.5
|
||||
golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf
|
||||
golang.org/x/image v0.0.0-20210504121937-7319ad40d33e
|
||||
golang.org/x/oauth2 v0.0.0-20210427180440-81ed05c6b58c
|
||||
github.com/yuin/goldmark v1.3.7
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a
|
||||
golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9
|
||||
golang.org/x/net v0.0.0-20210520170846-37e1c6afe023 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
golang.org/x/term v0.0.0-20210503060354-a79de5458b56
|
||||
golang.org/x/text v0.3.5 // indirect
|
||||
golang.org/x/tools v0.1.0 // indirect
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
|
||||
|
|
35
go.sum
35
go.sum
|
@ -148,6 +148,7 @@ github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73/go.mod h1:xb
|
|||
github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20200910202707-1e08a3fab204 h1:tI48fqaIkxxYuIylVv1tdDfBp6836GKSfmmzgSyP1CY=
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20200910202707-1e08a3fab204/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
||||
github.com/denisenkom/go-mssqldb v0.10.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
||||
github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
|
@ -182,6 +183,8 @@ github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWo
|
|||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/gabriel-vasile/mimetype v1.2.0 h1:A6z5J8OhjiWFV91sQ3dMI8apYu/tvP9keDaMM3Xu6p4=
|
||||
github.com/gabriel-vasile/mimetype v1.2.0/go.mod h1:6CDPel/o/3/s4+bp6kIbsWATq8pmgOisOPG40CJa6To=
|
||||
github.com/gabriel-vasile/mimetype v1.3.0 h1:4YOHITFLyYwF+iqG0ybSLGArRItynpfwdlWRmJnd75E=
|
||||
github.com/gabriel-vasile/mimetype v1.3.0/go.mod h1:fA8fi6KUiG7MgQQ+mEWotXoEOvmxRtOJlERCzSmRvr8=
|
||||
github.com/garyburd/redigo v1.6.0 h1:0VruCpn7yAIIu7pWVClQC8wxCJEcG3nyzpMSHKi1PQc=
|
||||
github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
|
||||
github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
|
||||
|
@ -233,6 +236,8 @@ github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB
|
|||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-testfixtures/testfixtures/v3 v3.6.0 h1:fHrJWcZ0TOHA0UcExV0Nwx+5MR9QXVDWYdVfwe4DfmM=
|
||||
github.com/go-testfixtures/testfixtures/v3 v3.6.0/go.mod h1:YUBpgqvleDRhkx4MQbzdA7A3G5ca2wLtf9bHbDqNaRQ=
|
||||
github.com/go-testfixtures/testfixtures/v3 v3.6.1 h1:n4Fv95Exp0D05G6l6CAZv22Ck1EJK0pa0TfPqE4ncSs=
|
||||
github.com/go-testfixtures/testfixtures/v3 v3.6.1/go.mod h1:Bsb2MoHAfHnNsPpSwAjtOs102mqDuM+1u3nE2OCi0N0=
|
||||
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
|
||||
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
|
||||
|
@ -457,6 +462,8 @@ github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvf
|
|||
github.com/labstack/echo/v4 v4.1.16/go.mod h1:awO+5TzAjvL8XpibdsfXxPgHr+orhtXZJZIQCVjogKI=
|
||||
github.com/labstack/echo/v4 v4.2.2 h1:bq2fdZCionY1jck8rzUpQEu2YSmI8QbX6LHrCa60IVs=
|
||||
github.com/labstack/echo/v4 v4.2.2/go.mod h1:AA49e0DZ8kk5jTOOCKNuPR6oTnBS0dYiM4FW1e6jwpg=
|
||||
github.com/labstack/echo/v4 v4.3.0 h1:DCP6cbtT+Zu++K6evHOJzSgA2115cPMuCx0xg55q1EQ=
|
||||
github.com/labstack/echo/v4 v4.3.0/go.mod h1:PvmtTvhVqKDzDQy4d3bWzPjZLzom4iQbAZy2sgZ/qI8=
|
||||
github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0=
|
||||
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
|
||||
github.com/laurent22/ical-go v0.1.1-0.20181107184520-7e5d6ade8eef h1:RZnRnSID1skF35j/15KJ6hKZkdIC/teQClJK5wP5LU4=
|
||||
|
@ -470,6 +477,8 @@ github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
|||
github.com/lib/pq v1.10.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lib/pq v1.10.1 h1:6VXZrLU0jHBYyAqrSPa+MgPfnSvTPuMgK+k0o5kVFWo=
|
||||
github.com/lib/pq v1.10.1/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
|
||||
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
|
||||
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
|
||||
github.com/lithammer/shortuuid/v3 v3.0.4 h1:uj4xhotfY92Y1Oa6n6HUiFn87CdoEHYUlTy0+IgbLrs=
|
||||
|
@ -742,6 +751,10 @@ github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
|
|||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5 h1:dPmz1Snjq0kmkz159iL7S6WzdahUTHnHB5M56WFVifs=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.3.6 h1:rvdBidUJAJM2O9VLcNTB4oRwxG33uIxY+zUq6yWUT8c=
|
||||
github.com/yuin/goldmark v1.3.6/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.3.7 h1:NSaHgaeJFCtWXCBkBKXw0rhgMuJ0VoE9FB5mWldcrQ4=
|
||||
github.com/yuin/goldmark v1.3.7/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
|
@ -790,8 +803,13 @@ golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPh
|
|||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf h1:B2n+Zi5QeYRDAEodEu72OS36gmTWjgpXr2+cWcBW90o=
|
||||
golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/crypto v0.0.0-20210513122933-cd7d49e622d5 h1:N6Jp/LCiEoIBX56BZSR2bepK5GtbSC2DDOYT742mMfE=
|
||||
golang.org/x/crypto v0.0.0-20210513122933-cd7d49e622d5/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc=
|
||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
|
@ -807,6 +825,8 @@ golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+o
|
|||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20210504121937-7319ad40d33e h1:PzJMNfFQx+QO9hrC1GwZ4BoPGeNGhfeQEgcQFArEjPk=
|
||||
golang.org/x/image v0.0.0-20210504121937-7319ad40d33e/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9 h1:D0iM1dTCbD5Dg1CbuvLC/v/agLc79efSj/L35Q3Vqhs=
|
||||
golang.org/x/image v0.0.0-20210607152325-775e3b0c77b9/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
|
@ -871,6 +891,12 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwY
|
|||
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125 h1:Ugb8sMTWuWRC3+sz5WeN/4kejDx9BvIwnPUiJBjJE+8=
|
||||
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20210520170846-37e1c6afe023 h1:ADo5wSpq2gqaCGQWzk7S5vd//0iyyLeAratkEoG5dLE=
|
||||
golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
|
@ -878,6 +904,8 @@ golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4Iltr
|
|||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20210427180440-81ed05c6b58c h1:SgVl/sCtkicsS7psKkje4H9YtjdEl3xsYh7N+5TDHqY=
|
||||
golang.org/x/oauth2 v0.0.0-20210427180440-81ed05c6b58c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c h1:pkQiBZBvdos9qq4wBAHqlzuZHEXo07pqV06ef90u1WI=
|
||||
golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
@ -947,6 +975,11 @@ golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2 h1:46ULzRKLh1CwgRq2dC5SlBzEqqNCi8rreOZnNrbqcIY=
|
||||
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57 h1:F5Gozwx4I1xtr/sr/8CFbb57iKi3297KFs0QDbGN60A=
|
||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210503060354-a79de5458b56 h1:b8jxX3zqjpqb2LklXPzKSGJhzyxCOZSz8ncv8Nv+y7w=
|
||||
golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY=
|
||||
|
@ -958,6 +991,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
|
|
@ -73,6 +73,7 @@ const (
|
|||
DatabaseMaxIdleConnections Key = `database.maxidleconnections`
|
||||
DatabaseMaxConnectionLifetime Key = `database.maxconnectionlifetime`
|
||||
DatabaseSslMode Key = `database.sslmode`
|
||||
DatabaseTLS Key = `database.tls`
|
||||
|
||||
CacheEnabled Key = `cache.enabled`
|
||||
CacheType Key = `cache.type`
|
||||
|
@ -258,6 +259,7 @@ func InitDefaultConfig() {
|
|||
DatabaseMaxIdleConnections.setDefault(50)
|
||||
DatabaseMaxConnectionLifetime.setDefault(10000)
|
||||
DatabaseSslMode.setDefault("disable")
|
||||
DatabaseTLS.setDefault("false")
|
||||
|
||||
// Cacher
|
||||
CacheEnabled.setDefault(false)
|
||||
|
@ -359,6 +361,10 @@ func InitConfig() {
|
|||
RateLimitStore.Set(KeyvalueType.GetString())
|
||||
}
|
||||
|
||||
if ServiceFrontendurl.GetString() != "" && !strings.HasSuffix(ServiceFrontendurl.GetString(), "/") {
|
||||
ServiceFrontendurl.Set(ServiceFrontendurl.GetString() + "/")
|
||||
}
|
||||
|
||||
if AuthOpenIDRedirectURL.GetString() == "" {
|
||||
AuthOpenIDRedirectURL.Set(ServiceFrontendurl.GetString() + "auth/openid/")
|
||||
}
|
||||
|
|
|
@ -113,11 +113,12 @@ func initMysqlEngine() (engine *xorm.Engine, err error) {
|
|||
// We're using utf8mb here instead of just utf8 because we want to use non-BMP characters.
|
||||
// See https://stackoverflow.com/a/30074553/10924593 for more info.
|
||||
connStr := fmt.Sprintf(
|
||||
"%s:%s@tcp(%s)/%s?charset=utf8mb4&parseTime=true",
|
||||
"%s:%s@tcp(%s)/%s?charset=utf8mb4&parseTime=true&tls=%s",
|
||||
config.DatabaseUser.GetString(),
|
||||
config.DatabasePassword.GetString(),
|
||||
config.DatabaseHost.GetString(),
|
||||
config.DatabaseDatabase.GetString())
|
||||
config.DatabaseDatabase.GetString(),
|
||||
config.DatabaseTLS.GetString())
|
||||
engine, err = xorm.NewEngine("mysql", connStr)
|
||||
if err != nil {
|
||||
return
|
||||
|
|
|
@ -47,6 +47,9 @@ func Dump() (data map[string][]byte, err error) {
|
|||
|
||||
// Restore restores a table with all its entries
|
||||
func Restore(table string, contents []map[string]interface{}) (err error) {
|
||||
if _, err := x.IsTableExist(table); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, content := range contents {
|
||||
if _, err := x.Table(table).Insert(content); err != nil {
|
||||
|
|
|
@ -91,12 +91,8 @@ func SetUserActive(a web.Auth) (err error) {
|
|||
|
||||
// getActiveUsers returns the active users from redis
|
||||
func getActiveUsers() (users activeUsersMap, err error) {
|
||||
u, _, err := keyvalue.Get(ActiveUsersKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
users = u.(activeUsersMap)
|
||||
users = activeUsersMap{}
|
||||
_, err = keyvalue.GetWithValue(ActiveUsersKey, &users)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
package metrics
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/modules/keyvalue"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
@ -132,7 +134,11 @@ func GetCount(key string) (count int64, err error) {
|
|||
return 0, nil
|
||||
}
|
||||
|
||||
if s, is := cnt.(string); is {
|
||||
count, err = strconv.ParseInt(s, 10, 64)
|
||||
} else {
|
||||
count = cnt.(int64)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
|
43
pkg/migration/20210527105701.go
Normal file
43
pkg/migration/20210527105701.go
Normal file
|
@ -0,0 +1,43 @@
|
|||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public Licensee as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public Licensee
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package migration
|
||||
|
||||
import (
|
||||
"src.techknowlogick.com/xormigrate"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
type users20210527105701 struct {
|
||||
DefaultListID int64 `xorm:"bigint null index" json:"-"`
|
||||
}
|
||||
|
||||
func (users20210527105701) TableName() string {
|
||||
return "users"
|
||||
}
|
||||
|
||||
func init() {
|
||||
migrations = append(migrations, &xormigrate.Migration{
|
||||
ID: "20210527105701",
|
||||
Description: "Add default list for new tasks setting to users",
|
||||
Migrate: func(tx *xorm.Engine) error {
|
||||
return tx.Sync2(users20210527105701{})
|
||||
},
|
||||
Rollback: func(tx *xorm.Engine) error {
|
||||
return nil
|
||||
},
|
||||
})
|
||||
}
|
43
pkg/migration/20210603174608.go
Normal file
43
pkg/migration/20210603174608.go
Normal file
|
@ -0,0 +1,43 @@
|
|||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public Licensee as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public Licensee for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public Licensee
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package migration
|
||||
|
||||
import (
|
||||
"src.techknowlogick.com/xormigrate"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
type users20210603174608 struct {
|
||||
WeekStart int `xorm:"null" json:"-"`
|
||||
}
|
||||
|
||||
func (users20210603174608) TableName() string {
|
||||
return "users"
|
||||
}
|
||||
|
||||
func init() {
|
||||
migrations = append(migrations, &xormigrate.Migration{
|
||||
ID: "20210603174608",
|
||||
Description: "Add week start user setting",
|
||||
Migrate: func(tx *xorm.Engine) error {
|
||||
return tx.Sync2(users20210603174608{})
|
||||
},
|
||||
Rollback: func(tx *xorm.Engine) error {
|
||||
return nil
|
||||
},
|
||||
})
|
||||
}
|
|
@ -61,7 +61,7 @@ func (Label) TableName() string {
|
|||
// @Produce json
|
||||
// @Security JWTKeyAuth
|
||||
// @Param label body models.Label true "The label object"
|
||||
// @Success 200 {object} models.Label "The created label object."
|
||||
// @Success 201 {object} models.Label "The created label object."
|
||||
// @Failure 400 {object} web.HTTPError "Invalid label object provided."
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /labels [put]
|
||||
|
|
|
@ -75,7 +75,7 @@ func (lt *LabelTask) Delete(s *xorm.Session, a web.Auth) (err error) {
|
|||
// @Security JWTKeyAuth
|
||||
// @Param task path int true "Task ID"
|
||||
// @Param label body models.LabelTask true "The label object"
|
||||
// @Success 200 {object} models.LabelTask "The created label relation object."
|
||||
// @Success 201 {object} models.LabelTask "The created label relation object."
|
||||
// @Failure 400 {object} web.HTTPError "Invalid label object provided."
|
||||
// @Failure 403 {object} web.HTTPError "Not allowed to add the label."
|
||||
// @Failure 404 {object} web.HTTPError "The label does not exist."
|
||||
|
@ -369,7 +369,7 @@ type LabelTaskBulk struct {
|
|||
// @Security JWTKeyAuth
|
||||
// @Param label body models.LabelTaskBulk true "The array of labels"
|
||||
// @Param taskID path int true "Task ID"
|
||||
// @Success 200 {object} models.LabelTaskBulk "The updated labels object."
|
||||
// @Success 201 {object} models.LabelTaskBulk "The updated labels object."
|
||||
// @Failure 400 {object} web.HTTPError "Invalid label object provided."
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /tasks/{taskID}/labels/bulk [post]
|
||||
|
|
|
@ -120,7 +120,7 @@ func (share *LinkSharing) toUser() *user.User {
|
|||
// @Security JWTKeyAuth
|
||||
// @Param list path int true "List ID"
|
||||
// @Param label body models.LinkSharing true "The new link share object"
|
||||
// @Success 200 {object} models.LinkSharing "The created link share object."
|
||||
// @Success 201 {object} models.LinkSharing "The created link share object."
|
||||
// @Failure 400 {object} web.HTTPError "Invalid link share object provided."
|
||||
// @Failure 403 {object} web.HTTPError "Not allowed to add the list share."
|
||||
// @Failure 404 {object} web.HTTPError "The list does not exist."
|
||||
|
|
|
@ -640,7 +640,7 @@ func updateListByTaskID(s *xorm.Session, taskID int64) (err error) {
|
|||
// @Security JWTKeyAuth
|
||||
// @Param namespaceID path int true "Namespace ID"
|
||||
// @Param list body models.List true "The list you want to create."
|
||||
// @Success 200 {object} models.List "The created list."
|
||||
// @Success 201 {object} models.List "The created list."
|
||||
// @Failure 400 {object} web.HTTPError "Invalid list object provided."
|
||||
// @Failure 403 {object} web.HTTPError "The user does not have access to the list"
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
|
|
|
@ -61,7 +61,7 @@ func (ld *ListDuplicate) CanCreate(s *xorm.Session, a web.Auth) (canCreate bool,
|
|||
// @Security JWTKeyAuth
|
||||
// @Param listID path int true "The list ID to duplicate"
|
||||
// @Param list body models.ListDuplicate true "The target namespace which should hold the copied list."
|
||||
// @Success 200 {object} models.ListDuplicate "The created list."
|
||||
// @Success 201 {object} models.ListDuplicate "The created list."
|
||||
// @Failure 400 {object} web.HTTPError "Invalid list duplicate object provided."
|
||||
// @Failure 403 {object} web.HTTPError "The user does not have access to the list or namespace"
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
|
@ -107,12 +107,111 @@ func (ld *ListDuplicate) Create(s *xorm.Session, doer web.Auth) (err error) {
|
|||
|
||||
log.Debugf("Duplicated all buckets from list %d into %d", ld.ListID, ld.List.ID)
|
||||
|
||||
err = duplicateTasks(s, doer, ld, bucketMap)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Background files + unsplash info
|
||||
if ld.List.BackgroundFileID != 0 {
|
||||
|
||||
log.Debugf("Duplicating background %d from list %d into %d", ld.List.BackgroundFileID, ld.ListID, ld.List.ID)
|
||||
|
||||
f := &files.File{ID: ld.List.BackgroundFileID}
|
||||
if err := f.LoadFileMetaByID(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := f.LoadFileByID(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.File.Close()
|
||||
|
||||
file, err := files.Create(f.File, f.Name, f.Size, doer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get unsplash info if applicable
|
||||
up, err := GetUnsplashPhotoByFileID(s, ld.List.BackgroundFileID)
|
||||
if err != nil && files.IsErrFileIsNotUnsplashFile(err) {
|
||||
return err
|
||||
}
|
||||
if up != nil {
|
||||
up.ID = 0
|
||||
up.FileID = file.ID
|
||||
if err := up.Save(s); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := SetListBackground(s, ld.List.ID, file); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("Duplicated list background from list %d into %d", ld.ListID, ld.List.ID)
|
||||
}
|
||||
|
||||
// Rights / Shares
|
||||
// To keep it simple(r) we will only copy rights which are directly used with the list, no namespace changes.
|
||||
users := []*ListUser{}
|
||||
err = s.Where("list_id = ?", ld.ListID).Find(&users)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, u := range users {
|
||||
u.ID = 0
|
||||
u.ListID = ld.List.ID
|
||||
if _, err := s.Insert(u); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("Duplicated user shares from list %d into %d", ld.ListID, ld.List.ID)
|
||||
|
||||
teams := []*TeamList{}
|
||||
err = s.Where("list_id = ?", ld.ListID).Find(&teams)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, t := range teams {
|
||||
t.ID = 0
|
||||
t.ListID = ld.List.ID
|
||||
if _, err := s.Insert(t); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Generate new link shares if any are available
|
||||
linkShares := []*LinkSharing{}
|
||||
err = s.Where("list_id = ?", ld.ListID).Find(&linkShares)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, share := range linkShares {
|
||||
share.ID = 0
|
||||
share.ListID = ld.List.ID
|
||||
share.Hash = utils.MakeRandomString(40)
|
||||
if _, err := s.Insert(share); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("Duplicated all link shares from list %d into %d", ld.ListID, ld.List.ID)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func duplicateTasks(s *xorm.Session, doer web.Auth, ld *ListDuplicate, bucketMap map[int64]int64) (err error) {
|
||||
// Get all tasks + all task details
|
||||
tasks, _, _, err := getTasksForLists(s, []*List{{ID: ld.ListID}}, doer, &taskOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(tasks) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// This map contains the old task id as key and the new duplicated task id as value.
|
||||
// It is used to map old task items to new ones.
|
||||
taskMap := make(map[int64]int64)
|
||||
|
@ -255,91 +354,5 @@ func (ld *ListDuplicate) Create(s *xorm.Session, doer web.Auth) (err error) {
|
|||
|
||||
log.Debugf("Duplicated all task relations from list %d into %d", ld.ListID, ld.List.ID)
|
||||
|
||||
// Background files + unsplash info
|
||||
if ld.List.BackgroundFileID != 0 {
|
||||
|
||||
log.Debugf("Duplicating background %d from list %d into %d", ld.List.BackgroundFileID, ld.ListID, ld.List.ID)
|
||||
|
||||
f := &files.File{ID: ld.List.BackgroundFileID}
|
||||
if err := f.LoadFileMetaByID(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := f.LoadFileByID(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.File.Close()
|
||||
|
||||
file, err := files.Create(f.File, f.Name, f.Size, doer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get unsplash info if applicable
|
||||
up, err := GetUnsplashPhotoByFileID(s, ld.List.BackgroundFileID)
|
||||
if err != nil && files.IsErrFileIsNotUnsplashFile(err) {
|
||||
return err
|
||||
}
|
||||
if up != nil {
|
||||
up.ID = 0
|
||||
up.FileID = file.ID
|
||||
if err := up.Save(s); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := SetListBackground(s, ld.List.ID, file); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("Duplicated list background from list %d into %d", ld.ListID, ld.List.ID)
|
||||
}
|
||||
|
||||
// Rights / Shares
|
||||
// To keep it simple(r) we will only copy rights which are directly used with the list, no namespace changes.
|
||||
users := []*ListUser{}
|
||||
err = s.Where("list_id = ?", ld.ListID).Find(&users)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, u := range users {
|
||||
u.ID = 0
|
||||
u.ListID = ld.List.ID
|
||||
if _, err := s.Insert(u); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("Duplicated user shares from list %d into %d", ld.ListID, ld.List.ID)
|
||||
|
||||
teams := []*TeamList{}
|
||||
err = s.Where("list_id = ?", ld.ListID).Find(&teams)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, t := range teams {
|
||||
t.ID = 0
|
||||
t.ListID = ld.List.ID
|
||||
if _, err := s.Insert(t); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Generate new link shares if any are available
|
||||
linkShares := []*LinkSharing{}
|
||||
err = s.Where("list_id = ?", ld.ListID).Find(&linkShares)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, share := range linkShares {
|
||||
share.ID = 0
|
||||
share.ListID = ld.List.ID
|
||||
share.Hash = utils.MakeRandomString(40)
|
||||
if _, err := s.Insert(share); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("Duplicated all link shares from list %d into %d", ld.ListID, ld.List.ID)
|
||||
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -65,7 +65,7 @@ type TeamWithRight struct {
|
|||
// @Security JWTKeyAuth
|
||||
// @Param id path int true "List ID"
|
||||
// @Param list body models.TeamList true "The team you want to add to the list."
|
||||
// @Success 200 {object} models.TeamList "The created team<->list relation."
|
||||
// @Success 201 {object} models.TeamList "The created team<->list relation."
|
||||
// @Failure 400 {object} web.HTTPError "Invalid team list object provided."
|
||||
// @Failure 404 {object} web.HTTPError "The team does not exist."
|
||||
// @Failure 403 {object} web.HTTPError "The user does not have access to the list"
|
||||
|
|
|
@ -68,7 +68,7 @@ type UserWithRight struct {
|
|||
// @Security JWTKeyAuth
|
||||
// @Param id path int true "List ID"
|
||||
// @Param list body models.ListUser true "The user you want to add to the list."
|
||||
// @Success 200 {object} models.ListUser "The created user<->list relation."
|
||||
// @Success 201 {object} models.ListUser "The created user<->list relation."
|
||||
// @Failure 400 {object} web.HTTPError "Invalid user list object provided."
|
||||
// @Failure 404 {object} web.HTTPError "The user does not exist."
|
||||
// @Failure 403 {object} web.HTTPError "The user does not have access to the list"
|
||||
|
|
|
@ -291,6 +291,11 @@ func getNamespaceOwnerIDs(namespaces map[int64]*NamespaceWithLists) (namespaceID
|
|||
}
|
||||
|
||||
func getNamespaceSubscriptions(s *xorm.Session, namespaceIDs []int64, userID int64) (map[int64]*Subscription, error) {
|
||||
subscriptionsMap := make(map[int64]*Subscription)
|
||||
if len(namespaceIDs) == 0 {
|
||||
return subscriptionsMap, nil
|
||||
}
|
||||
|
||||
subscriptions := []*Subscription{}
|
||||
err := s.
|
||||
Where("entity_type = ? AND user_id = ?", SubscriptionEntityNamespace, userID).
|
||||
|
@ -299,7 +304,6 @@ func getNamespaceSubscriptions(s *xorm.Session, namespaceIDs []int64, userID int
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
subscriptionsMap := make(map[int64]*Subscription)
|
||||
for _, sub := range subscriptions {
|
||||
sub.Entity = sub.EntityType.String()
|
||||
subscriptionsMap[sub.EntityID] = sub
|
||||
|
@ -483,6 +487,10 @@ func (n *Namespace) ReadAll(s *xorm.Session, a web.Auth, search string, page int
|
|||
|
||||
namespaceIDs, ownerIDs := getNamespaceOwnerIDs(namespaces)
|
||||
|
||||
if len(namespaceIDs) == 0 {
|
||||
return nil, 0, 0, nil
|
||||
}
|
||||
|
||||
subscriptionsMap, err := getNamespaceSubscriptions(s, namespaceIDs, doer.ID)
|
||||
if err != nil {
|
||||
return nil, 0, 0, err
|
||||
|
@ -570,7 +578,7 @@ func (n *Namespace) ReadAll(s *xorm.Session, a web.Auth, search string, page int
|
|||
// @Produce json
|
||||
// @Security JWTKeyAuth
|
||||
// @Param namespace body models.Namespace true "The namespace you want to create."
|
||||
// @Success 200 {object} models.Namespace "The created namespace."
|
||||
// @Success 201 {object} models.Namespace "The created namespace."
|
||||
// @Failure 400 {object} web.HTTPError "Invalid namespace object provided."
|
||||
// @Failure 403 {object} web.HTTPError "The user does not have access to the namespace"
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
|
|
|
@ -59,7 +59,7 @@ func (TeamNamespace) TableName() string {
|
|||
// @Security JWTKeyAuth
|
||||
// @Param id path int true "Namespace ID"
|
||||
// @Param namespace body models.TeamNamespace true "The team you want to add to the namespace."
|
||||
// @Success 200 {object} models.TeamNamespace "The created team<->namespace relation."
|
||||
// @Success 201 {object} models.TeamNamespace "The created team<->namespace relation."
|
||||
// @Failure 400 {object} web.HTTPError "Invalid team namespace object provided."
|
||||
// @Failure 404 {object} web.HTTPError "The team does not exist."
|
||||
// @Failure 403 {object} web.HTTPError "The team does not have access to the namespace"
|
||||
|
|
|
@ -346,4 +346,14 @@ func TestNamespace_ReadAll(t *testing.T) {
|
|||
// Assert the first namespace is not the favorites namespace
|
||||
assert.NotEqual(t, SavedFiltersPseudoNamespace.ID, namespaces[0].ID)
|
||||
})
|
||||
t.Run("no results", func(t *testing.T) {
|
||||
db.LoadAndAssertFixtures(t)
|
||||
s := db.NewSession()
|
||||
defer s.Close()
|
||||
|
||||
n := &Namespace{}
|
||||
nn, _, _, err := n.ReadAll(s, user1, "some search string which will never return results", 1, -1)
|
||||
assert.NoError(t, err)
|
||||
assert.Nil(t, nn)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -61,7 +61,7 @@ func (NamespaceUser) TableName() string {
|
|||
// @Security JWTKeyAuth
|
||||
// @Param id path int true "Namespace ID"
|
||||
// @Param namespace body models.NamespaceUser true "The user you want to add to the namespace."
|
||||
// @Success 200 {object} models.NamespaceUser "The created user<->namespace relation."
|
||||
// @Success 201 {object} models.NamespaceUser "The created user<->namespace relation."
|
||||
// @Failure 400 {object} web.HTTPError "Invalid user namespace object provided."
|
||||
// @Failure 404 {object} web.HTTPError "The user does not exist."
|
||||
// @Failure 403 {object} web.HTTPError "The user does not have access to the namespace"
|
||||
|
|
|
@ -112,7 +112,7 @@ func (sf *SavedFilter) toList() *List {
|
|||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security JWTKeyAuth
|
||||
// @Success 200 {object} models.SavedFilter "The Saved Filter"
|
||||
// @Success 201 {object} models.SavedFilter "The Saved Filter"
|
||||
// @Failure 403 {object} web.HTTPError "The user does not have access to that saved filter."
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /filters [put]
|
||||
|
|
|
@ -114,7 +114,7 @@ func (et SubscriptionEntityType) validate() error {
|
|||
// @Security JWTKeyAuth
|
||||
// @Param entity path string true "The entity the user subscribes to. Can be either `namespace`, `list` or `task`."
|
||||
// @Param entityID path string true "The numeric id of the entity to subscribe to."
|
||||
// @Success 200 {object} models.Subscription "The subscription"
|
||||
// @Success 201 {object} models.Subscription "The subscription"
|
||||
// @Failure 403 {object} web.HTTPError "The user does not have access to subscribe to this entity."
|
||||
// @Failure 412 {object} web.HTTPError "The subscription already exists."
|
||||
// @Failure 412 {object} web.HTTPError "The subscription entity is invalid."
|
||||
|
|
|
@ -187,7 +187,7 @@ func (la *TaskAssginee) Delete(s *xorm.Session, a web.Auth) (err error) {
|
|||
// @Security JWTKeyAuth
|
||||
// @Param assignee body models.TaskAssginee true "The assingee object"
|
||||
// @Param taskID path int true "Task ID"
|
||||
// @Success 200 {object} models.TaskAssginee "The created assingee object."
|
||||
// @Success 201 {object} models.TaskAssginee "The created assingee object."
|
||||
// @Failure 400 {object} web.HTTPError "Invalid assignee object provided."
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /tasks/{taskID}/assignees [put]
|
||||
|
@ -308,7 +308,7 @@ type BulkAssignees struct {
|
|||
// @Security JWTKeyAuth
|
||||
// @Param assignee body models.BulkAssignees true "The array of assignees"
|
||||
// @Param taskID path int true "Task ID"
|
||||
// @Success 200 {object} models.TaskAssginee "The created assingees object."
|
||||
// @Success 201 {object} models.TaskAssginee "The created assingees object."
|
||||
// @Failure 400 {object} web.HTTPError "Invalid assignee object provided."
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /tasks/{taskID}/assignees/bulk [post]
|
||||
|
|
|
@ -56,7 +56,7 @@ func (tc *TaskComment) TableName() string {
|
|||
// @Security JWTKeyAuth
|
||||
// @Param relation body models.TaskComment true "The task comment object"
|
||||
// @Param taskID path int true "Task ID"
|
||||
// @Success 200 {object} models.TaskComment "The created task comment object."
|
||||
// @Success 201 {object} models.TaskComment "The created task comment object."
|
||||
// @Failure 400 {object} web.HTTPError "Invalid task comment object provided."
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /tasks/{taskID}/comments [put]
|
||||
|
|
|
@ -114,7 +114,7 @@ type RelatedTaskMap map[RelationKind][]*Task
|
|||
// @Security JWTKeyAuth
|
||||
// @Param relation body models.TaskRelation true "The relation object"
|
||||
// @Param taskID path int true "Task ID"
|
||||
// @Success 200 {object} models.TaskRelation "The created task relation object."
|
||||
// @Success 201 {object} models.TaskRelation "The created task relation object."
|
||||
// @Failure 400 {object} web.HTTPError "Invalid task relation object provided."
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /tasks/{taskID}/relations [put]
|
||||
|
|
|
@ -801,7 +801,7 @@ func setTaskBucket(s *xorm.Session, task *Task, originalTask *Task, doCheckBucke
|
|||
// @Security JWTKeyAuth
|
||||
// @Param id path int true "List ID"
|
||||
// @Param task body models.Task true "The task object"
|
||||
// @Success 200 {object} models.Task "The created task object."
|
||||
// @Success 201 {object} models.Task "The created task object."
|
||||
// @Failure 400 {object} web.HTTPError "Invalid task object provided."
|
||||
// @Failure 403 {object} web.HTTPError "The user does not have access to the list"
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
|
|
|
@ -32,7 +32,7 @@ import (
|
|||
// @Security JWTKeyAuth
|
||||
// @Param id path int true "Team ID"
|
||||
// @Param team body models.TeamMember true "The user to be added to a team."
|
||||
// @Success 200 {object} models.TeamMember "The newly created member object"
|
||||
// @Success 201 {object} models.TeamMember "The newly created member object"
|
||||
// @Failure 400 {object} web.HTTPError "Invalid member object provided."
|
||||
// @Failure 403 {object} web.HTTPError "The user does not have access to the team"
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
|
|
|
@ -247,7 +247,7 @@ func (t *Team) ReadAll(s *xorm.Session, a web.Auth, search string, page int, per
|
|||
// @Produce json
|
||||
// @Security JWTKeyAuth
|
||||
// @Param team body models.Team true "The team you want to create."
|
||||
// @Success 200 {object} models.Team "The created team."
|
||||
// @Success 201 {object} models.Team "The created team."
|
||||
// @Failure 400 {object} web.HTTPError "Invalid team object provided."
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /teams [put]
|
||||
|
|
|
@ -23,6 +23,8 @@ import (
|
|||
"net/http"
|
||||
"time"
|
||||
|
||||
"code.vikunja.io/web/handler"
|
||||
|
||||
"code.vikunja.io/api/pkg/db"
|
||||
"xorm.io/xorm"
|
||||
|
||||
|
@ -46,10 +48,11 @@ type Callback struct {
|
|||
type Provider struct {
|
||||
Name string `json:"name"`
|
||||
Key string `json:"key"`
|
||||
OriginalAuthURL string `json:"-"`
|
||||
AuthURL string `json:"auth_url"`
|
||||
ClientID string `json:"client_id"`
|
||||
ClientSecret string `json:"-"`
|
||||
OpenIDProvider *oidc.Provider `json:"-"`
|
||||
openIDProvider *oidc.Provider
|
||||
Oauth2Config *oauth2.Config `json:"-"`
|
||||
}
|
||||
|
||||
|
@ -57,12 +60,18 @@ type claims struct {
|
|||
Email string `json:"email"`
|
||||
Name string `json:"name"`
|
||||
PreferredUsername string `json:"preferred_username"`
|
||||
Nickname string `json:"nickname"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
rand.Seed(time.Now().UTC().UnixNano())
|
||||
}
|
||||
|
||||
func (p *Provider) setOicdProvider() (err error) {
|
||||
p.openIDProvider, err = oidc.NewProvider(context.Background(), p.OriginalAuthURL)
|
||||
return err
|
||||
}
|
||||
|
||||
// HandleCallback handles the auth request callback after redirecting from the provider with an auth code
|
||||
// @Summary Authenticate a user with OpenID Connect
|
||||
// @Description After a redirect from the OpenID Connect provider to the frontend has been made with the authentication `code`, this endpoint can be used to obtain a jwt token for that user and thus log them in.
|
||||
|
@ -86,7 +95,7 @@ func HandleCallback(c echo.Context) error {
|
|||
provider, err := GetProvider(providerKey)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
return handler.HandleHTTPError(err, c)
|
||||
}
|
||||
if provider == nil {
|
||||
return c.JSON(http.StatusBadRequest, models.Message{Message: "Provider does not exist"})
|
||||
|
@ -100,7 +109,8 @@ func HandleCallback(c echo.Context) error {
|
|||
|
||||
details := make(map[string]interface{})
|
||||
if err := json.Unmarshal(rerr.Body, &details); err != nil {
|
||||
return err
|
||||
log.Errorf("Error unmarshaling token for provider %s: %v", provider.Name, err)
|
||||
return handler.HandleHTTPError(err, c)
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusBadRequest, map[string]interface{}{
|
||||
|
@ -109,7 +119,7 @@ func HandleCallback(c echo.Context) error {
|
|||
})
|
||||
}
|
||||
|
||||
return err
|
||||
return handler.HandleHTTPError(err, c)
|
||||
}
|
||||
|
||||
// Extract the ID Token from OAuth2 token.
|
||||
|
@ -118,19 +128,57 @@ func HandleCallback(c echo.Context) error {
|
|||
return c.JSON(http.StatusBadRequest, models.Message{Message: "Missing token"})
|
||||
}
|
||||
|
||||
verifier := provider.OpenIDProvider.Verifier(&oidc.Config{ClientID: provider.ClientID})
|
||||
verifier := provider.openIDProvider.Verifier(&oidc.Config{ClientID: provider.ClientID})
|
||||
|
||||
// Parse and verify ID Token payload.
|
||||
idToken, err := verifier.Verify(context.Background(), rawIDToken)
|
||||
if err != nil {
|
||||
return err
|
||||
log.Errorf("Error verifying token for provider %s: %v", provider.Name, err)
|
||||
return handler.HandleHTTPError(err, c)
|
||||
}
|
||||
|
||||
// Extract custom claims
|
||||
cl := &claims{}
|
||||
err = idToken.Claims(cl)
|
||||
if err != nil {
|
||||
return err
|
||||
log.Errorf("Error getting token claims for provider %s: %v", provider.Name, err)
|
||||
return handler.HandleHTTPError(err, c)
|
||||
}
|
||||
|
||||
if cl.Email == "" || cl.Name == "" || cl.PreferredUsername == "" {
|
||||
info, err := provider.openIDProvider.UserInfo(context.Background(), provider.Oauth2Config.TokenSource(context.Background(), oauth2Token))
|
||||
if err != nil {
|
||||
log.Errorf("Error getting userinfo for provider %s: %v", provider.Name, err)
|
||||
return handler.HandleHTTPError(err, c)
|
||||
}
|
||||
|
||||
cl2 := &claims{}
|
||||
err = info.Claims(cl2)
|
||||
if err != nil {
|
||||
log.Errorf("Error parsing userinfo claims for provider %s: %v", provider.Name, err)
|
||||
return handler.HandleHTTPError(err, c)
|
||||
}
|
||||
|
||||
if cl.Email == "" {
|
||||
cl.Email = cl2.Email
|
||||
}
|
||||
|
||||
if cl.Name == "" {
|
||||
cl.Name = cl2.Name
|
||||
}
|
||||
|
||||
if cl.PreferredUsername == "" {
|
||||
cl.PreferredUsername = cl2.PreferredUsername
|
||||
}
|
||||
|
||||
if cl.PreferredUsername == "" && cl2.Nickname != "" {
|
||||
cl.PreferredUsername = cl2.Nickname
|
||||
}
|
||||
|
||||
if cl.Email == "" {
|
||||
log.Errorf("Claim does not contain an email address for provider %s", provider.Name)
|
||||
return handler.HandleHTTPError(&user.ErrNoOpenIDEmailProvided{}, c)
|
||||
}
|
||||
}
|
||||
|
||||
s := db.NewSession()
|
||||
|
@ -140,12 +188,13 @@ func HandleCallback(c echo.Context) error {
|
|||
u, err := getOrCreateUser(s, cl, idToken.Issuer, idToken.Subject)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
return err
|
||||
log.Errorf("Error creating new user for provider %s: %v", provider.Name, err)
|
||||
return handler.HandleHTTPError(err, c)
|
||||
}
|
||||
|
||||
err = s.Commit()
|
||||
if err != nil {
|
||||
return err
|
||||
return handler.HandleHTTPError(err, c)
|
||||
}
|
||||
|
||||
// Create token
|
||||
|
|
|
@ -17,11 +17,12 @@
|
|||
package openid
|
||||
|
||||
import (
|
||||
"context"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/modules/keyvalue"
|
||||
"github.com/coreos/go-oidc"
|
||||
|
@ -34,7 +35,8 @@ func GetAllProviders() (providers []*Provider, err error) {
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
ps, exists, err := keyvalue.Get("openid_providers")
|
||||
providers = []*Provider{}
|
||||
exists, err := keyvalue.GetWithValue("openid_providers", &providers)
|
||||
if !exists {
|
||||
rawProviders := config.AuthOpenIDProviders.Get()
|
||||
if rawProviders == nil {
|
||||
|
@ -44,11 +46,26 @@ func GetAllProviders() (providers []*Provider, err error) {
|
|||
rawProvider := rawProviders.([]interface{})
|
||||
|
||||
for _, p := range rawProvider {
|
||||
pi := p.(map[interface{}]interface{})
|
||||
var pi map[string]interface{}
|
||||
var is bool
|
||||
pi, is = p.(map[string]interface{})
|
||||
// JSON config is a map[string]interface{}, other providers are not. Under the hood they are all strings so
|
||||
// it is save to cast.
|
||||
if !is {
|
||||
pis := p.(map[interface{}]interface{})
|
||||
pi = make(map[string]interface{}, len(pis))
|
||||
for i, s := range pis {
|
||||
pi[i.(string)] = s
|
||||
}
|
||||
}
|
||||
|
||||
provider, err := getProviderFromMap(pi)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if provider != nil {
|
||||
log.Errorf("Error while getting openid provider %s: %s", provider.Name, err)
|
||||
}
|
||||
log.Errorf("Error while getting openid provider: %s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
providers = append(providers, provider)
|
||||
|
@ -62,31 +79,30 @@ func GetAllProviders() (providers []*Provider, err error) {
|
|||
err = keyvalue.Put("openid_providers", providers)
|
||||
}
|
||||
|
||||
if ps != nil {
|
||||
return ps.([]*Provider), nil
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetProvider retrieves a provider from keyvalue
|
||||
func GetProvider(key string) (provider *Provider, err error) {
|
||||
var p interface{}
|
||||
p, exists, err := keyvalue.Get("openid_provider_" + key)
|
||||
if exists {
|
||||
provider = &Provider{}
|
||||
exists, err := keyvalue.GetWithValue("openid_provider_"+key, provider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !exists {
|
||||
_, err = GetAllProviders() // This will put all providers in cache
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p, _, err = keyvalue.Get("openid_provider_" + key)
|
||||
}
|
||||
|
||||
if p != nil {
|
||||
return p.(*Provider), nil
|
||||
}
|
||||
|
||||
_, err = keyvalue.GetWithValue("openid_provider_"+key, provider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
err = provider.setOicdProvider()
|
||||
return
|
||||
}
|
||||
|
||||
func getKeyFromName(name string) string {
|
||||
|
@ -94,7 +110,7 @@ func getKeyFromName(name string) string {
|
|||
return reg.ReplaceAllString(strings.ToLower(name), "")
|
||||
}
|
||||
|
||||
func getProviderFromMap(pi map[interface{}]interface{}) (*Provider, error) {
|
||||
func getProviderFromMap(pi map[string]interface{}) (provider *Provider, err error) {
|
||||
name, is := pi["name"].(string)
|
||||
if !is {
|
||||
return nil, nil
|
||||
|
@ -102,10 +118,11 @@ func getProviderFromMap(pi map[interface{}]interface{}) (*Provider, error) {
|
|||
|
||||
k := getKeyFromName(name)
|
||||
|
||||
provider := &Provider{
|
||||
provider = &Provider{
|
||||
Name: pi["name"].(string),
|
||||
Key: k,
|
||||
AuthURL: pi["authurl"].(string),
|
||||
OriginalAuthURL: pi["authurl"].(string),
|
||||
ClientSecret: pi["clientsecret"].(string),
|
||||
}
|
||||
|
||||
|
@ -116,10 +133,9 @@ func getProviderFromMap(pi map[interface{}]interface{}) (*Provider, error) {
|
|||
provider.ClientID = pi["clientid"].(string)
|
||||
}
|
||||
|
||||
var err error
|
||||
provider.OpenIDProvider, err = oidc.NewProvider(context.Background(), provider.AuthURL)
|
||||
err = provider.setOicdProvider()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return
|
||||
}
|
||||
|
||||
provider.Oauth2Config = &oauth2.Config{
|
||||
|
@ -128,7 +144,7 @@ func getProviderFromMap(pi map[interface{}]interface{}) (*Provider, error) {
|
|||
RedirectURL: config.AuthOpenIDRedirectURL.GetString() + k,
|
||||
|
||||
// Discovery returns the OAuth2 endpoints.
|
||||
Endpoint: provider.OpenIDProvider.Endpoint(),
|
||||
Endpoint: provider.openIDProvider.Endpoint(),
|
||||
|
||||
// "openid" is a required scope for OpenID Connect flows.
|
||||
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
|
||||
|
@ -136,5 +152,5 @@ func getProviderFromMap(pi map[interface{}]interface{}) (*Provider, error) {
|
|||
|
||||
provider.AuthURL = provider.Oauth2Config.Endpoint.AuthURL
|
||||
|
||||
return provider, nil
|
||||
return
|
||||
}
|
||||
|
|
|
@ -127,7 +127,8 @@ func getCacheKey(prefix string, keys ...int64) string {
|
|||
func getAvatarForUser(u *user.User) (fullSizeAvatar *image.RGBA64, err error) {
|
||||
cacheKey := getCacheKey("full", u.ID)
|
||||
|
||||
a, exists, err := keyvalue.Get(cacheKey)
|
||||
fullSizeAvatar = &image.RGBA64{}
|
||||
exists, err := keyvalue.GetWithValue(cacheKey, fullSizeAvatar)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -145,8 +146,6 @@ func getAvatarForUser(u *user.User) (fullSizeAvatar *image.RGBA64, err error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
fullSizeAvatar = a.(*image.RGBA64)
|
||||
}
|
||||
|
||||
return fullSizeAvatar, nil
|
||||
|
@ -156,7 +155,7 @@ func getAvatarForUser(u *user.User) (fullSizeAvatar *image.RGBA64, err error) {
|
|||
func (p *Provider) GetAvatar(u *user.User, size int64) (avatar []byte, mimeType string, err error) {
|
||||
cacheKey := getCacheKey("resized", u.ID, size)
|
||||
|
||||
a, exists, err := keyvalue.Get(cacheKey)
|
||||
exists, err := keyvalue.GetWithValue(cacheKey, &avatar)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
@ -180,7 +179,6 @@ func (p *Provider) GetAvatar(u *user.User, size int64) (avatar []byte, mimeType
|
|||
return nil, "", err
|
||||
}
|
||||
} else {
|
||||
avatar = a.([]byte)
|
||||
log.Debugf("Serving initials avatar for user %d and size %d from cache", u.ID, size)
|
||||
}
|
||||
|
||||
|
|
|
@ -39,22 +39,17 @@ func (p *Provider) GetAvatar(u *user.User, size int64) (avatar []byte, mimeType
|
|||
|
||||
cacheKey := "avatar_upload_" + strconv.Itoa(int(u.ID))
|
||||
|
||||
ai, exists, err := keyvalue.Get(cacheKey)
|
||||
var cached map[int64][]byte
|
||||
exists, err := keyvalue.GetWithValue(cacheKey, &cached)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
var cached map[int64][]byte
|
||||
|
||||
if ai != nil {
|
||||
cached = ai.(map[int64][]byte)
|
||||
}
|
||||
|
||||
if !exists {
|
||||
// Nothing ever cached for this user so we need to create the size map to avoid panics
|
||||
cached = make(map[int64][]byte)
|
||||
} else {
|
||||
a := ai.(map[int64][]byte)
|
||||
a := cached
|
||||
if a != nil && a[size] != nil {
|
||||
log.Debugf("Serving uploaded avatar for user %d and size %d from cache.", u.ID, size)
|
||||
return a[size], "", nil
|
||||
|
|
|
@ -122,7 +122,8 @@ func getImageID(fullURL string) string {
|
|||
// Gets an unsplash photo either from cache or directly from the unsplash api
|
||||
func getUnsplashPhotoInfoByID(photoID string) (photo *Photo, err error) {
|
||||
|
||||
p, exists, err := keyvalue.Get(cachePrefix + photoID)
|
||||
photo = &Photo{}
|
||||
exists, err := keyvalue.GetWithValue(cachePrefix+photoID, photo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -134,8 +135,6 @@ func getUnsplashPhotoInfoByID(photoID string) (photo *Photo, err error) {
|
|||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
photo = p.(*Photo)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ import (
|
|||
type Storage interface {
|
||||
Put(key string, value interface{}) (err error)
|
||||
Get(key string) (value interface{}, exists bool, err error)
|
||||
GetWithValue(key string, value interface{}) (exists bool, err error)
|
||||
Del(key string) (err error)
|
||||
IncrBy(key string, update int64) (err error)
|
||||
DecrBy(key string, update int64) (err error)
|
||||
|
@ -55,6 +56,10 @@ func Get(key string) (value interface{}, exists bool, err error) {
|
|||
return store.Get(key)
|
||||
}
|
||||
|
||||
func GetWithValue(key string, value interface{}) (exists bool, err error) {
|
||||
return store.GetWithValue(key, value)
|
||||
}
|
||||
|
||||
// Del removes a save value from a storage backend
|
||||
func Del(key string) (err error) {
|
||||
return store.Del(key)
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
package memory
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
e "code.vikunja.io/api/pkg/modules/keyvalue/error"
|
||||
|
@ -39,6 +40,14 @@ func NewStorage() *Storage {
|
|||
func (s *Storage) Put(key string, value interface{}) (err error) {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
|
||||
val := reflect.ValueOf(value)
|
||||
// Make sure to store the underlying value when value is a pointer to a value
|
||||
if val.Kind() == reflect.Ptr {
|
||||
s.store[key] = val.Elem().Interface()
|
||||
return nil
|
||||
}
|
||||
|
||||
s.store[key] = value
|
||||
return nil
|
||||
}
|
||||
|
@ -52,6 +61,25 @@ func (s *Storage) Get(key string) (value interface{}, exists bool, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
func (s *Storage) GetWithValue(key string, ptr interface{}) (exists bool, err error) {
|
||||
stored, exists, err := s.Get(key)
|
||||
if !exists {
|
||||
return exists, err
|
||||
}
|
||||
|
||||
val := reflect.ValueOf(ptr)
|
||||
if val.Kind() != reflect.Ptr {
|
||||
panic("value must be a pointer")
|
||||
}
|
||||
if val.IsNil() {
|
||||
panic("pointer must not be a nil-pointer")
|
||||
}
|
||||
|
||||
val.Elem().Set(reflect.ValueOf(stored))
|
||||
|
||||
return exists, err
|
||||
}
|
||||
|
||||
// Del removes a saved value from a memory storage
|
||||
func (s *Storage) Del(key string) (err error) {
|
||||
s.mutex.Lock()
|
||||
|
|
|
@ -17,8 +17,10 @@
|
|||
package redis
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"encoding/gob"
|
||||
"errors"
|
||||
|
||||
"code.vikunja.io/api/pkg/red"
|
||||
"github.com/go-redis/redis/v8"
|
||||
|
@ -40,23 +42,61 @@ func NewStorage() *Storage {
|
|||
|
||||
// Put puts a value into redis
|
||||
func (s *Storage) Put(key string, value interface{}) (err error) {
|
||||
v, err := json.Marshal(value)
|
||||
|
||||
var v interface{}
|
||||
|
||||
switch value.(type) {
|
||||
case int:
|
||||
v = value
|
||||
case int8:
|
||||
v = value
|
||||
case int16:
|
||||
v = value
|
||||
case int32:
|
||||
v = value
|
||||
case int64:
|
||||
v = value
|
||||
default:
|
||||
var buf bytes.Buffer
|
||||
enc := gob.NewEncoder(&buf)
|
||||
err = enc.Encode(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.client.Set(context.Background(), key, buf.Bytes(), 0).Err()
|
||||
}
|
||||
|
||||
return s.client.Set(context.Background(), key, v, 0).Err()
|
||||
}
|
||||
|
||||
// Get retrieves a saved value from redis
|
||||
func (s *Storage) Get(key string) (value interface{}, exists bool, err error) {
|
||||
value, err = s.client.Get(context.Background(), key).Result()
|
||||
if err != nil && errors.Is(err, redis.Nil) {
|
||||
return nil, false, nil
|
||||
}
|
||||
return value, true, err
|
||||
}
|
||||
|
||||
func (s *Storage) GetWithValue(key string, value interface{}) (exists bool, err error) {
|
||||
b, err := s.client.Get(context.Background(), key).Bytes()
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
if errors.Is(err, redis.Nil) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
err = json.Unmarshal(b, value)
|
||||
return
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
_, err = buf.Write(b)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
dec := gob.NewDecoder(&buf)
|
||||
err = dec.Decode(value)
|
||||
return true, err
|
||||
}
|
||||
|
||||
// Del removed a value from redis
|
||||
|
|
|
@ -58,7 +58,13 @@ func HandleTesting(c echo.Context) error {
|
|||
})
|
||||
}
|
||||
|
||||
truncate := c.QueryParam("truncate")
|
||||
if truncate == "true" || truncate == "" {
|
||||
err = db.RestoreAndTruncate(table, content)
|
||||
} else {
|
||||
err = db.Restore(table, content)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{
|
||||
"error": true,
|
||||
|
|
|
@ -45,6 +45,11 @@ type UserSettings struct {
|
|||
DiscoverableByEmail bool `json:"discoverable_by_email"`
|
||||
// If enabled, the user will get an email for their overdue tasks each morning.
|
||||
OverdueTasksRemindersEnabled bool `json:"overdue_tasks_reminders_enabled"`
|
||||
// If a task is created without a specified list this value should be used. Applies
|
||||
// to tasks made directly in API and from clients.
|
||||
DefaultListID int64 `json:"default_list_id"`
|
||||
// The day when the week starts for this user. 0 = sunday, 1 = monday, etc.
|
||||
WeekStart int `json:"week_start"`
|
||||
}
|
||||
|
||||
// GetUserAvatarProvider returns the currently set user avatar
|
||||
|
@ -170,6 +175,8 @@ func UpdateGeneralUserSettings(c echo.Context) error {
|
|||
user.DiscoverableByEmail = us.DiscoverableByEmail
|
||||
user.DiscoverableByName = us.DiscoverableByName
|
||||
user.OverdueTasksRemindersEnabled = us.OverdueTasksRemindersEnabled
|
||||
user.DefaultListID = us.DefaultListID
|
||||
user.WeekStart = us.WeekStart
|
||||
|
||||
_, err = user2.UpdateUser(s, user)
|
||||
if err != nil {
|
||||
|
|
|
@ -68,6 +68,8 @@ func UserShow(c echo.Context) error {
|
|||
DiscoverableByName: u.DiscoverableByName,
|
||||
DiscoverableByEmail: u.DiscoverableByEmail,
|
||||
OverdueTasksRemindersEnabled: u.OverdueTasksRemindersEnabled,
|
||||
DefaultListID: u.DefaultListID,
|
||||
WeekStart: u.WeekStart,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -243,7 +243,7 @@ var doc = `{
|
|||
],
|
||||
"summary": "Creates a new saved filter",
|
||||
"responses": {
|
||||
"200": {
|
||||
"201": {
|
||||
"description": "The Saved Filter",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.SavedFilter"
|
||||
|
@ -524,7 +524,7 @@ var doc = `{
|
|||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"201": {
|
||||
"description": "The created label object.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Label"
|
||||
|
@ -874,7 +874,7 @@ var doc = `{
|
|||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"201": {
|
||||
"description": "The created task object.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Task"
|
||||
|
@ -1572,7 +1572,7 @@ var doc = `{
|
|||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"201": {
|
||||
"description": "The created team\u003c-\u003elist relation.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.TeamList"
|
||||
|
@ -1710,7 +1710,7 @@ var doc = `{
|
|||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"201": {
|
||||
"description": "The created user\u003c-\u003elist relation.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ListUser"
|
||||
|
@ -1905,7 +1905,7 @@ var doc = `{
|
|||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"201": {
|
||||
"description": "The created list.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ListDuplicate"
|
||||
|
@ -2393,7 +2393,7 @@ var doc = `{
|
|||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"201": {
|
||||
"description": "The created link share object.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.LinkSharing"
|
||||
|
@ -3189,7 +3189,7 @@ var doc = `{
|
|||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"201": {
|
||||
"description": "The created namespace.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Namespace"
|
||||
|
@ -3478,7 +3478,7 @@ var doc = `{
|
|||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"201": {
|
||||
"description": "The created team\u003c-\u003enamespace relation.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.TeamNamespace"
|
||||
|
@ -3616,7 +3616,7 @@ var doc = `{
|
|||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"201": {
|
||||
"description": "The created user\u003c-\u003enamespace relation.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.NamespaceUser"
|
||||
|
@ -3686,7 +3686,7 @@ var doc = `{
|
|||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"201": {
|
||||
"description": "The created list.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.List"
|
||||
|
@ -4215,7 +4215,7 @@ var doc = `{
|
|||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"201": {
|
||||
"description": "The subscription",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Subscription"
|
||||
|
@ -4969,7 +4969,7 @@ var doc = `{
|
|||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"201": {
|
||||
"description": "The created assingee object.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.TaskAssginee"
|
||||
|
@ -5027,7 +5027,7 @@ var doc = `{
|
|||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"201": {
|
||||
"description": "The created assingees object.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.TaskAssginee"
|
||||
|
@ -5185,7 +5185,7 @@ var doc = `{
|
|||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"201": {
|
||||
"description": "The created task comment object.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.TaskComment"
|
||||
|
@ -5425,7 +5425,7 @@ var doc = `{
|
|||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"201": {
|
||||
"description": "The updated labels object.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.LabelTaskBulk"
|
||||
|
@ -5483,7 +5483,7 @@ var doc = `{
|
|||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"201": {
|
||||
"description": "The created task relation object.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.TaskRelation"
|
||||
|
@ -5680,7 +5680,7 @@ var doc = `{
|
|||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"201": {
|
||||
"description": "The created label relation object.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.LabelTask"
|
||||
|
@ -5860,7 +5860,7 @@ var doc = `{
|
|||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"201": {
|
||||
"description": "The created team.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Team"
|
||||
|
@ -6067,7 +6067,7 @@ var doc = `{
|
|||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"201": {
|
||||
"description": "The newly created member object",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.TeamMember"
|
||||
|
@ -8556,6 +8556,10 @@ var doc = `{
|
|||
"v1.UserSettings": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"default_list_id": {
|
||||
"description": "If a task is created without a specified list this value should be used. Applies\nto tasks made directly in API and from clients.",
|
||||
"type": "integer"
|
||||
},
|
||||
"discoverable_by_email": {
|
||||
"description": "If true, the user can be found when searching for their exact email.",
|
||||
"type": "boolean"
|
||||
|
@ -8575,6 +8579,10 @@ var doc = `{
|
|||
"overdue_tasks_reminders_enabled": {
|
||||
"description": "If enabled, the user will get an email for their overdue tasks each morning.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"week_start": {
|
||||
"description": "The day when the week starts for this user. 0 = sunday, 1 = monday, etc.",
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -226,7 +226,7 @@
|
|||
],
|
||||
"summary": "Creates a new saved filter",
|
||||
"responses": {
|
||||
"200": {
|
||||
"201": {
|
||||
"description": "The Saved Filter",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.SavedFilter"
|
||||
|
@ -507,7 +507,7 @@
|
|||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"201": {
|
||||
"description": "The created label object.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Label"
|
||||
|
@ -857,7 +857,7 @@
|
|||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"201": {
|
||||
"description": "The created task object.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Task"
|
||||
|
@ -1555,7 +1555,7 @@
|
|||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"201": {
|
||||
"description": "The created team\u003c-\u003elist relation.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.TeamList"
|
||||
|
@ -1693,7 +1693,7 @@
|
|||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"201": {
|
||||
"description": "The created user\u003c-\u003elist relation.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ListUser"
|
||||
|
@ -1888,7 +1888,7 @@
|
|||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"201": {
|
||||
"description": "The created list.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ListDuplicate"
|
||||
|
@ -2376,7 +2376,7 @@
|
|||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"201": {
|
||||
"description": "The created link share object.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.LinkSharing"
|
||||
|
@ -3172,7 +3172,7 @@
|
|||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"201": {
|
||||
"description": "The created namespace.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Namespace"
|
||||
|
@ -3461,7 +3461,7 @@
|
|||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"201": {
|
||||
"description": "The created team\u003c-\u003enamespace relation.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.TeamNamespace"
|
||||
|
@ -3599,7 +3599,7 @@
|
|||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"201": {
|
||||
"description": "The created user\u003c-\u003enamespace relation.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.NamespaceUser"
|
||||
|
@ -3669,7 +3669,7 @@
|
|||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"201": {
|
||||
"description": "The created list.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.List"
|
||||
|
@ -4198,7 +4198,7 @@
|
|||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"201": {
|
||||
"description": "The subscription",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Subscription"
|
||||
|
@ -4952,7 +4952,7 @@
|
|||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"201": {
|
||||
"description": "The created assingee object.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.TaskAssginee"
|
||||
|
@ -5010,7 +5010,7 @@
|
|||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"201": {
|
||||
"description": "The created assingees object.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.TaskAssginee"
|
||||
|
@ -5168,7 +5168,7 @@
|
|||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"201": {
|
||||
"description": "The created task comment object.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.TaskComment"
|
||||
|
@ -5408,7 +5408,7 @@
|
|||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"201": {
|
||||
"description": "The updated labels object.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.LabelTaskBulk"
|
||||
|
@ -5466,7 +5466,7 @@
|
|||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"201": {
|
||||
"description": "The created task relation object.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.TaskRelation"
|
||||
|
@ -5663,7 +5663,7 @@
|
|||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"201": {
|
||||
"description": "The created label relation object.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.LabelTask"
|
||||
|
@ -5843,7 +5843,7 @@
|
|||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"201": {
|
||||
"description": "The created team.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Team"
|
||||
|
@ -6050,7 +6050,7 @@
|
|||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"201": {
|
||||
"description": "The newly created member object",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.TeamMember"
|
||||
|
@ -8539,6 +8539,10 @@
|
|||
"v1.UserSettings": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"default_list_id": {
|
||||
"description": "If a task is created without a specified list this value should be used. Applies\nto tasks made directly in API and from clients.",
|
||||
"type": "integer"
|
||||
},
|
||||
"discoverable_by_email": {
|
||||
"description": "If true, the user can be found when searching for their exact email.",
|
||||
"type": "boolean"
|
||||
|
@ -8558,6 +8562,10 @@
|
|||
"overdue_tasks_reminders_enabled": {
|
||||
"description": "If enabled, the user will get an email for their overdue tasks each morning.",
|
||||
"type": "boolean"
|
||||
},
|
||||
"week_start": {
|
||||
"description": "The day when the week starts for this user. 0 = sunday, 1 = monday, etc.",
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1178,6 +1178,11 @@ definitions:
|
|||
type: object
|
||||
v1.UserSettings:
|
||||
properties:
|
||||
default_list_id:
|
||||
description: |-
|
||||
If a task is created without a specified list this value should be used. Applies
|
||||
to tasks made directly in API and from clients.
|
||||
type: integer
|
||||
discoverable_by_email:
|
||||
description: If true, the user can be found when searching for their exact
|
||||
email.
|
||||
|
@ -1196,6 +1201,10 @@ definitions:
|
|||
description: If enabled, the user will get an email for their overdue tasks
|
||||
each morning.
|
||||
type: boolean
|
||||
week_start:
|
||||
description: The day when the week starts for this user. 0 = sunday, 1 = monday,
|
||||
etc.
|
||||
type: integer
|
||||
type: object
|
||||
v1.authInfo:
|
||||
properties:
|
||||
|
@ -1462,7 +1471,7 @@ paths:
|
|||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
"201":
|
||||
description: The Saved Filter
|
||||
schema:
|
||||
$ref: '#/definitions/models.SavedFilter'
|
||||
|
@ -1645,7 +1654,7 @@ paths:
|
|||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
"201":
|
||||
description: The created label object.
|
||||
schema:
|
||||
$ref: '#/definitions/models.Label'
|
||||
|
@ -1945,7 +1954,7 @@ paths:
|
|||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
"201":
|
||||
description: The created task object.
|
||||
schema:
|
||||
$ref: '#/definitions/models.Task'
|
||||
|
@ -2338,7 +2347,7 @@ paths:
|
|||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
"201":
|
||||
description: The created team<->list relation.
|
||||
schema:
|
||||
$ref: '#/definitions/models.TeamList'
|
||||
|
@ -2429,7 +2438,7 @@ paths:
|
|||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
"201":
|
||||
description: The created user<->list relation.
|
||||
schema:
|
||||
$ref: '#/definitions/models.ListUser'
|
||||
|
@ -2517,7 +2526,7 @@ paths:
|
|||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
"201":
|
||||
description: The created link share object.
|
||||
schema:
|
||||
$ref: '#/definitions/models.LinkSharing'
|
||||
|
@ -2727,7 +2736,7 @@ paths:
|
|||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
"201":
|
||||
description: The created list.
|
||||
schema:
|
||||
$ref: '#/definitions/models.ListDuplicate'
|
||||
|
@ -3422,7 +3431,7 @@ paths:
|
|||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
"201":
|
||||
description: The created namespace.
|
||||
schema:
|
||||
$ref: '#/definitions/models.Namespace'
|
||||
|
@ -3610,7 +3619,7 @@ paths:
|
|||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
"201":
|
||||
description: The created team<->namespace relation.
|
||||
schema:
|
||||
$ref: '#/definitions/models.TeamNamespace'
|
||||
|
@ -3702,7 +3711,7 @@ paths:
|
|||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
"201":
|
||||
description: The created user<->namespace relation.
|
||||
schema:
|
||||
$ref: '#/definitions/models.NamespaceUser'
|
||||
|
@ -3748,7 +3757,7 @@ paths:
|
|||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
"201":
|
||||
description: The created list.
|
||||
schema:
|
||||
$ref: '#/definitions/models.List'
|
||||
|
@ -4139,7 +4148,7 @@ paths:
|
|||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
"201":
|
||||
description: The subscription
|
||||
schema:
|
||||
$ref: '#/definitions/models.Subscription'
|
||||
|
@ -4493,7 +4502,7 @@ paths:
|
|||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
"201":
|
||||
description: The created label relation object.
|
||||
schema:
|
||||
$ref: '#/definitions/models.LabelTask'
|
||||
|
@ -4622,7 +4631,7 @@ paths:
|
|||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
"201":
|
||||
description: The created assingee object.
|
||||
schema:
|
||||
$ref: '#/definitions/models.TaskAssginee'
|
||||
|
@ -4698,7 +4707,7 @@ paths:
|
|||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
"201":
|
||||
description: The created assingees object.
|
||||
schema:
|
||||
$ref: '#/definitions/models.TaskAssginee'
|
||||
|
@ -4765,7 +4774,7 @@ paths:
|
|||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
"201":
|
||||
description: The created task comment object.
|
||||
schema:
|
||||
$ref: '#/definitions/models.TaskComment'
|
||||
|
@ -4926,7 +4935,7 @@ paths:
|
|||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
"201":
|
||||
description: The updated labels object.
|
||||
schema:
|
||||
$ref: '#/definitions/models.LabelTaskBulk'
|
||||
|
@ -4966,7 +4975,7 @@ paths:
|
|||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
"201":
|
||||
description: The created task relation object.
|
||||
schema:
|
||||
$ref: '#/definitions/models.TaskRelation'
|
||||
|
@ -5206,7 +5215,7 @@ paths:
|
|||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
"201":
|
||||
description: The created team.
|
||||
schema:
|
||||
$ref: '#/definitions/models.Team'
|
||||
|
@ -5339,7 +5348,7 @@ paths:
|
|||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
"201":
|
||||
description: The newly created member object
|
||||
schema:
|
||||
$ref: '#/definitions/models.TeamMember'
|
||||
|
|
|
@ -399,3 +399,29 @@ func (err ErrInvalidAvatarProvider) HTTPError() web.HTTPError {
|
|||
Message: "Invalid avatar provider setting. See docs for valid types.",
|
||||
}
|
||||
}
|
||||
|
||||
// ErrNoOpenIDEmailProvided represents a "NoEmailProvided" kind of error.
|
||||
type ErrNoOpenIDEmailProvided struct {
|
||||
}
|
||||
|
||||
// IsErrNoEmailProvided checks if an error is a ErrNoOpenIDEmailProvided.
|
||||
func IsErrNoEmailProvided(err error) bool {
|
||||
_, ok := err.(*ErrNoOpenIDEmailProvided)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err *ErrNoOpenIDEmailProvided) Error() string {
|
||||
return "No email provided"
|
||||
}
|
||||
|
||||
// ErrCodeNoOpenIDEmailProvided holds the unique world-error code of this error
|
||||
const ErrCodeNoOpenIDEmailProvided = 1019
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err *ErrNoOpenIDEmailProvided) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{
|
||||
HTTPCode: http.StatusPreconditionFailed,
|
||||
Code: ErrCodeNoOpenIDEmailProvided,
|
||||
Message: "No email address available. Please make sure the openid provider publicly provides an email address for your account.",
|
||||
}
|
||||
}
|
||||
|
|
|
@ -71,6 +71,8 @@ type User struct {
|
|||
DiscoverableByName bool `xorm:"bool default false index" json:"-"`
|
||||
DiscoverableByEmail bool `xorm:"bool default false index" json:"-"`
|
||||
OverdueTasksRemindersEnabled bool `xorm:"bool default true index" json:"-"`
|
||||
DefaultListID int64 `xorm:"bigint null index" json:"-"`
|
||||
WeekStart int `xorm:"null" json:"-"`
|
||||
|
||||
// A timestamp when this task was created. You cannot change this value.
|
||||
Created time.Time `xorm:"created not null" json:"created"`
|
||||
|
@ -371,6 +373,8 @@ func UpdateUser(s *xorm.Session, user *User) (updatedUser *User, err error) {
|
|||
"discoverable_by_name",
|
||||
"discoverable_by_email",
|
||||
"overdue_tasks_reminders_enabled",
|
||||
"default_list_id",
|
||||
"week_start",
|
||||
).
|
||||
Update(user)
|
||||
if err != nil {
|
||||
|
|
Loading…
Reference in New Issue
Block a user