More avatar providers #622
2
Makefile
2
Makefile
|
@ -21,7 +21,7 @@ GOFLAGS := -v $(EXTRA_GOFLAGS)
|
|||
|
||||
LDFLAGS := -X "code.vikunja.io/api/pkg/version.Version=$(shell git describe --tags --always --abbrev=10 | sed 's/-/+/' | sed 's/^v//' | sed 's/-g/-/')" -X "main.Tags=$(TAGS)"
|
||||
|
||||
PACKAGES ?= $(filter-out code.vikunja.io/api/pkg/integrations,$(shell go list))
|
||||
PACKAGES ?= $(filter-out code.vikunja.io/api/pkg/integrations,$(shell go list all | grep code\.vikunja\.io\/api))
|
||||
SOURCES ?= $(shell find . -name "*.go" -type f)
|
||||
|
||||
TAGS ?=
|
||||
|
|
|
@ -178,10 +178,6 @@ migration:
|
|||
redirecturl:
|
||||
|
||||
avatar:
|
||||
# Switch between avatar providers. Possible values are gravatar and default.
|
||||
# gravatar will fetch the avatar based on the user email.
|
||||
# default will return a default avatar for every request.
|
||||
provider: gravatar
|
||||
# When using gravatar, this is the duration in seconds until a cached gravatar user avatar expires
|
||||
gravatarexpiration: 3600
|
||||
|
||||
|
|
|
@ -221,10 +221,6 @@ migration:
|
|||
redirecturl:
|
||||
|
||||
avatar:
|
||||
# Switch between avatar providers. Possible values are gravatar and default.
|
||||
# gravatar will fetch the avatar based on the user email.
|
||||
# default will return a default avatar for every request.
|
||||
provider: gravatar
|
||||
# When using gravatar, this is the duration in seconds until a cached gravatar user avatar expires
|
||||
gravatarexpiration: 3600
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@ This document describes the different errors Vikunja can return.
|
|||
| 1015 | 412 | Totp is already enabled for this user. |
|
||||
| 1016 | 412 | Totp is not enabled for this user. |
|
||||
| 1017 | 412 | The provided Totp passcode is invalid. |
|
||||
| 1018 | 412 | The provided user avatar provider type setting is invalid. |
|
||||
|
||||
### Validation
|
||||
|
||||
|
|
4
go.mod
4
go.mod
|
@ -28,12 +28,15 @@ require (
|
|||
github.com/cweill/gotests v1.5.3
|
||||
github.com/d4l3k/messagediff v1.2.1 // indirect
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
||||
github.com/fzipp/gocyclo v0.0.0-20150627053110-6acd4345c835
|
||||
github.com/gabriel-vasile/mimetype v1.1.1
|
||||
github.com/getsentry/sentry-go v0.7.0
|
||||
github.com/go-redis/redis/v7 v7.4.0
|
||||
github.com/go-sql-driver/mysql v1.5.0
|
||||
github.com/go-testfixtures/testfixtures/v3 v3.3.0
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
|
||||
github.com/gordonklaus/ineffassign v0.0.0-20200309095847-7953dde2c7bf
|
||||
github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334
|
||||
github.com/imdario/mergo v0.3.10
|
||||
|
@ -64,6 +67,7 @@ require (
|
|||
github.com/swaggo/swag v1.6.7
|
||||
github.com/ulule/limiter/v3 v3.5.0
|
||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de
|
||||
golang.org/x/image v0.0.0-20200801110659-972c09e46d76
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b
|
||||
golang.org/x/net v0.0.0-20200602114024-627f9648deb9 // indirect
|
||||
golang.org/x/text v0.3.3 // indirect
|
||||
|
|
22
go.sum
22
go.sum
|
@ -118,6 +118,8 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumC
|
|||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
||||
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
||||
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
||||
|
@ -134,11 +136,11 @@ 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/fzipp/gocyclo v0.0.0-20150627053110-6acd4345c835 h1:roDmqJ4Qes7hrDOsWsMCce0vQHz3xiMPjJ9m4c2eeNs=
|
||||
github.com/fzipp/gocyclo v0.0.0-20150627053110-6acd4345c835/go.mod h1:BjL/N0+C+j9uNX+1xcNuM9vdSIcXCZrQZUYbXOFbgN8=
|
||||
github.com/gabriel-vasile/mimetype v1.1.1 h1:qbN9MPuRf3bstHu9zkI9jDWNfH//9+9kHxr9oRBBBOA=
|
||||
github.com/gabriel-vasile/mimetype v1.1.1/go.mod h1:6CDPel/o/3/s4+bp6kIbsWATq8pmgOisOPG40CJa6To=
|
||||
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=
|
||||
github.com/getsentry/sentry-go v0.6.1 h1:K84dY1/57OtWhdyr5lbU78Q/+qgzkEyGc/ud+Sipi5k=
|
||||
github.com/getsentry/sentry-go v0.6.1/go.mod h1:0yZBuzSvbZwBnvaF9VwZIMen3kXscY8/uasKtAX1qG8=
|
||||
github.com/getsentry/sentry-go v0.7.0 h1:MR2yfR4vFfv/2+iBuSnkdQwVg7N9cJzihZ6KJu7srwQ=
|
||||
github.com/getsentry/sentry-go v0.7.0/go.mod h1:pLFpD2Y5RHIKF9Bw3KH6/68DeN2K/XBJd8awjdPnUwg=
|
||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
|
@ -205,6 +207,8 @@ github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a
|
|||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
|
@ -284,8 +288,6 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p
|
|||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||
github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334 h1:VHgatEHNcBFEB7inlalqfNqw65aNkM1lGX2yt3NmbS8=
|
||||
github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE=
|
||||
github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg=
|
||||
github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/imdario/mergo v0.3.10 h1:6q5mVkdH/vYmqngx7kZQTjJ5HRsx+ImorDIEQ+beJgc=
|
||||
github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
|
||||
github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=
|
||||
|
@ -417,10 +419,6 @@ github.com/lib/pq v1.3.0 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU=
|
|||
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.6.0 h1:I5DPxhYJChW9KYc66se+oKFFQX6VuQrKiprsX6ivRZc=
|
||||
github.com/lib/pq v1.6.0/go.mod h1:4vXEAYvW1fRQ2/FhZ78H73A60MHw1geSm145z2mdY1g=
|
||||
github.com/lib/pq v1.7.0 h1:h93mCPfUSkaul3Ka/VG8uZdmW1uMHDGxzu0NWHuJmHY=
|
||||
github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lib/pq v1.7.1 h1:FvD5XTVTDt+KON6oIoOmHq6B6HzGuYEhuTMpEG0yuBQ=
|
||||
github.com/lib/pq v1.7.1/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lib/pq v1.8.0 h1:9xohqzkUwzR4Ga4ivdTcawVS89YSDVxXMa3xJX3cGzg=
|
||||
github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
|
||||
|
@ -610,8 +608,6 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
|||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
|
||||
github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM=
|
||||
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
|
||||
github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk=
|
||||
github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
|
@ -703,8 +699,6 @@ golang.org/x/crypto v0.0.0-20200117160349-530e935923ad/go.mod h1:LzIPMQfyMNhhGPh
|
|||
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 h1:DZhuSZLsGlFL4CmhA8BcRA0mnthyA/nZ00AqCUo7vHg=
|
||||
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de h1:ikNHVSjEfnvz6sxdSPCaPt572qowuyMDMJLLm3Db3ig=
|
||||
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
|
@ -713,7 +707,11 @@ golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxT
|
|||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
|
||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20200801110659-972c09e46d76 h1:U7GPaoQyQmX+CBRWXKrvRzWTbd+slqeSh8uARsIyhAw=
|
||||
golang.org/x/image v0.0.0-20200801110659-972c09e46d76/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
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=
|
||||
|
|
|
@ -117,7 +117,6 @@ const (
|
|||
CorsOrigins Key = `cors.origins`
|
||||
CorsMaxAge Key = `cors.maxage`
|
||||
|
||||
AvatarProvider Key = `avatar.provider`
|
||||
AvatarGravaterExpiration Key = `avatar.gravatarexpiration`
|
||||
|
||||
BackgroundsEnabled Key = `backgrounds.enabled`
|
||||
|
@ -273,7 +272,6 @@ func InitDefaultConfig() {
|
|||
MigrationWunderlistEnable.setDefault(false)
|
||||
MigrationTodoistEnable.setDefault(false)
|
||||
// Avatar
|
||||
AvatarProvider.setDefault("gravatar")
|
||||
AvatarGravaterExpiration.setDefault(3600)
|
||||
// List Backgrounds
|
||||
BackgroundsEnabled.setDefault(true)
|
||||
|
|
|
@ -67,7 +67,12 @@ func (f *File) LoadFileMetaByID() (err error) {
|
|||
}
|
||||
|
||||
// Create creates a new file from an FileHeader
|
||||
func Create(f io.ReadCloser, realname string, realsize uint64, a web.Auth) (file *File, err error) {
|
||||
func Create(f io.Reader, realname string, realsize uint64, a web.Auth) (file *File, err error) {
|
||||
return CreateWithMime(f, realname, realsize, a, "")
|
||||
}
|
||||
|
||||
// CreateWithMime creates a new file from an FileHeader and sets its mime type
|
||||
func CreateWithMime(f io.Reader, realname string, realsize uint64, a web.Auth, mime string) (file *File, err error) {
|
||||
|
||||
// Get and parse the configured file size
|
||||
var maxSize datasize.ByteSize
|
||||
|
@ -84,6 +89,7 @@ func Create(f io.ReadCloser, realname string, realsize uint64, a web.Auth) (file
|
|||
Name: realname,
|
||||
Size: realsize,
|
||||
CreatedByID: a.GetID(),
|
||||
Mime: mime,
|
||||
}
|
||||
|
||||
_, err = x.Insert(file)
|
||||
|
@ -111,6 +117,6 @@ func (f *File) Delete() (err error) {
|
|||
}
|
||||
|
||||
// Save saves a file to storage
|
||||
func (f *File) Save(fcontent io.ReadCloser) error {
|
||||
func (f *File) Save(fcontent io.Reader) error {
|
||||
return afs.WriteReader(f.getFileName(), fcontent)
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ type ActiveUser struct {
|
|||
|
||||
type activeUsersMap map[int64]*ActiveUser
|
||||
|
||||
// ActiveUsersMap is the type used to save active users
|
||||
// ActiveUsers is the type used to save active users
|
||||
type ActiveUsers struct {
|
||||
users activeUsersMap
|
||||
mutex *sync.Mutex
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2020 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 General Public License 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 General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package migration
|
||||
|
||||
import (
|
||||
"src.techknowlogick.com/xormigrate"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
type user20200801183357 struct {
|
||||
AvatarProvider string `xorm:"varchar(255) null" json:"-"`
|
||||
AvatarFileID int64 `xorn:"null" json:"-"`
|
||||
}
|
||||
|
||||
func (s user20200801183357) TableName() string {
|
||||
return "users"
|
||||
}
|
||||
|
||||
func init() {
|
||||
migrations = append(migrations, &xormigrate.Migration{
|
||||
ID: "20200801183357",
|
||||
Description: "Add avatar provider setting to user",
|
||||
Migrate: func(tx *xorm.Engine) error {
|
||||
err := tx.Sync2(user20200801183357{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = tx.Cols("avatar_provider").Update(&user20200801183357{AvatarProvider: "initials"})
|
||||
return err
|
||||
},
|
||||
Rollback: func(tx *xorm.Engine) error {
|
||||
return nil
|
||||
},
|
||||
})
|
||||
}
|
|
@ -445,6 +445,9 @@ func addMoreInfoToTasks(taskMap map[int64]*Task) (err error) {
|
|||
|
||||
// Get task attachments
|
||||
attachments, err := getTaskAttachmentsByTaskIDs(taskIDs)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Get all users of a task
|
||||
// aka the ones who created a task
|
||||
|
|
|
@ -0,0 +1,175 @@
|
|||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2020 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 General Public License 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 General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package initials
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"github.com/disintegration/imaging"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"bytes"
|
||||
"github.com/golang/freetype/truetype"
|
||||
"golang.org/x/image/font"
|
||||
"golang.org/x/image/font/gofont/goregular"
|
||||
"golang.org/x/image/math/fixed"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/draw"
|
||||
"image/png"
|
||||
)
|
||||
|
||||
// Provider represents the provider implementation of the initials provider
|
||||
type Provider struct {
|
||||
}
|
||||
|
||||
var (
|
||||
avatarBgColors = []*color.RGBA{
|
||||
{69, 189, 243, 255},
|
||||
{224, 143, 112, 255},
|
||||
{77, 182, 172, 255},
|
||||
{149, 117, 205, 255},
|
||||
{176, 133, 94, 255},
|
||||
{240, 98, 146, 255},
|
||||
{163, 211, 108, 255},
|
||||
{121, 134, 203, 255},
|
||||
{241, 185, 29, 255},
|
||||
}
|
||||
|
||||
// Contain the created avatars with a size of defaultSize
|
||||
cache = map[int64]*image.RGBA64{}
|
||||
cacheLock = sync.Mutex{}
|
||||
cacheResized = map[string][]byte{}
|
||||
cacheResizedLock = sync.Mutex{}
|
||||
)
|
||||
|
||||
func init() {
|
||||
cache = make(map[int64]*image.RGBA64)
|
||||
cacheResized = make(map[string][]byte)
|
||||
}
|
||||
|
||||
const (
|
||||
dpi = 72
|
||||
defaultSize = 1024
|
||||
)
|
||||
|
||||
func drawImage(text rune, bg *color.RGBA) (img *image.RGBA64, err error) {
|
||||
|
||||
size := defaultSize
|
||||
fontSize := float64(size) * 0.8
|
||||
|
||||
// Inspired by https://github.com/holys/initials-avatar
|
||||
|
||||
// Get the font
|
||||
f, err := truetype.Parse(goregular.TTF)
|
||||
if err != nil {
|
||||
return img, err
|
||||
}
|
||||
|
||||
// Build the image background
|
||||
img = image.NewRGBA64(image.Rect(0, 0, size, size))
|
||||
draw.Draw(img, img.Bounds(), &image.Uniform{C: bg}, image.Point{}, draw.Src)
|
||||
|
||||
// Add the text
|
||||
drawer := &font.Drawer{
|
||||
Dst: img,
|
||||
Src: image.White,
|
||||
Face: truetype.NewFace(f, &truetype.Options{
|
||||
Size: fontSize,
|
||||
DPI: dpi,
|
||||
Hinting: font.HintingNone,
|
||||
}),
|
||||
}
|
||||
|
||||
// Font Index
|
||||
fi := f.Index(text)
|
||||
|
||||
// Glyph example: http://www.freetype.org/freetype2/docs/tutorial/metrics.png
|
||||
var gbuf truetype.GlyphBuf
|
||||
fsize := fixed.Int26_6(fontSize * dpi * (64.0 / 72.0))
|
||||
err = gbuf.Load(f, fsize, fi, font.HintingFull)
|
||||
if err != nil {
|
||||
drawer.DrawString("")
|
||||
return img, err
|
||||
}
|
||||
|
||||
// Center
|
||||
dY := (size - int(gbuf.Bounds.Max.Y-gbuf.Bounds.Min.Y)>>6) / 2
|
||||
dX := (size - int(gbuf.Bounds.Max.X-gbuf.Bounds.Min.X)>>6) / 2
|
||||
y := int(gbuf.Bounds.Max.Y>>6) + dY
|
||||
x := 0 - int(gbuf.Bounds.Min.X>>6) + dX
|
||||
|
||||
drawer.Dot = fixed.Point26_6{
|
||||
X: fixed.I(x),
|
||||
Y: fixed.I(y),
|
||||
}
|
||||
drawer.DrawString(string(text))
|
||||
|
||||
return img, err
|
||||
}
|
||||
|
||||
func getAvatarForUser(u *user.User) (fullSizeAvatar *image.RGBA64, err error) {
|
||||
var cached bool
|
||||
fullSizeAvatar, cached = cache[u.ID]
|
||||
if !cached {
|
||||
log.Debugf("Initials avatar for user %d not cached, creating...", u.ID)
|
||||
firstRune := []rune(strings.ToUpper(u.Username))[0]
|
||||
bg := avatarBgColors[int(u.ID)%len(avatarBgColors)] // Random color based on the user id
|
||||
|
||||
fullSizeAvatar, err = drawImage(firstRune, bg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cacheLock.Lock()
|
||||
cache[u.ID] = fullSizeAvatar
|
||||
cacheLock.Unlock()
|
||||
}
|
||||
|
||||
return fullSizeAvatar, err
|
||||
}
|
||||
|
||||
// GetAvatar returns an initials avatar for a user
|
||||
func (p *Provider) GetAvatar(u *user.User, size int64) (avatar []byte, mimeType string, err error) {
|
||||
|
||||
var cached bool
|
||||
cacheKey := strconv.Itoa(int(u.ID)) + "_" + strconv.Itoa(int(size))
|
||||
avatar, cached = cacheResized[cacheKey]
|
||||
if !cached {
|
||||
log.Debugf("Initials avatar for user %d and size %d not cached, creating...", u.ID, size)
|
||||
fullAvatar, err := getAvatarForUser(u)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
img := imaging.Resize(fullAvatar, int(size), int(size), imaging.Lanczos)
|
||||
buf := &bytes.Buffer{}
|
||||
err = png.Encode(buf, img)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
avatar = buf.Bytes()
|
||||
cacheResizedLock.Lock()
|
||||
cacheResized[cacheKey] = avatar
|
||||
cacheResizedLock.Unlock()
|
||||
} else {
|
||||
log.Debugf("Serving initials avatar for user %d and size %d from cache", u.ID, size)
|
||||
}
|
||||
|
||||
return avatar, "image/png", err
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2020 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 General Public License 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 General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package upload
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"code.vikunja.io/api/pkg/files"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"github.com/disintegration/imaging"
|
||||
"image"
|
||||
"image/png"
|
||||
"io/ioutil"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
// This is a map with a map so we're able to clear all cached avatar (in all sizes) for one user at once
|
||||
// The first map has as key the user id, the second one has the size as key
|
||||
resizedCache = map[int64]map[int64][]byte{}
|
||||
resizedCacheLock = sync.Mutex{}
|
||||
)
|
||||
|
||||
func init() {
|
||||
resizedCache = make(map[int64]map[int64][]byte)
|
||||
}
|
||||
|
||||
// Provider represents the upload avatar provider
|
||||
type Provider struct {
|
||||
}
|
||||
|
||||
// GetAvatar returns an uploaded user avatar
|
||||
func (p *Provider) GetAvatar(u *user.User, size int64) (avatar []byte, mimeType string, err error) {
|
||||
|
||||
a, cached := resizedCache[u.ID]
|
||||
if 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
|
||||
}
|
||||
// This means we have a map for the user, but nothing in it.
|
||||
if a == nil {
|
||||
resizedCache[u.ID] = make(map[int64][]byte)
|
||||
}
|
||||
} else {
|
||||
// Nothing ever cached for this user so we need to create the size map to avoid panics
|
||||
resizedCache[u.ID] = make(map[int64][]byte)
|
||||
}
|
||||
|
||||
log.Debugf("Uploaded avatar for user %d and size %d not cached, resizing and caching.", u.ID, size)
|
||||
|
||||
// If we get this far, the avatar is either not cached at all or not in this size
|
||||
f := &files.File{ID: u.AvatarFileID}
|
||||
if err := f.LoadFileByID(); err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
if err := f.LoadFileMetaByID(); err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
img, _, err := image.Decode(f.File)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
resizedImg := imaging.Resize(img, 0, int(size), imaging.Lanczos)
|
||||
buf := &bytes.Buffer{}
|
||||
if err := png.Encode(buf, resizedImg); err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
avatar, err = ioutil.ReadAll(buf)
|
||||
resizedCacheLock.Lock()
|
||||
resizedCache[u.ID][size] = avatar
|
||||
resizedCacheLock.Unlock()
|
||||
return avatar, f.Mime, err
|
||||
}
|
||||
|
||||
// InvalidateCache invalidates the avatar cache for a user
|
||||
func InvalidateCache(u *user.User) {
|
||||
resizedCacheLock.Lock()
|
||||
delete(resizedCache, u.ID)
|
||||
resizedCacheLock.Unlock()
|
||||
}
|
|
@ -25,9 +25,12 @@ import (
|
|||
v1 "code.vikunja.io/api/pkg/routes/api/v1"
|
||||
"code.vikunja.io/web"
|
||||
"code.vikunja.io/web/handler"
|
||||
"github.com/gabriel-vasile/mimetype"
|
||||
"github.com/labstack/echo/v4"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// BackgroundProvider represents a thing which holds a background provider
|
||||
|
@ -132,7 +135,18 @@ func (bp *BackgroundProvider) UploadBackground(c echo.Context) error {
|
|||
}
|
||||
defer src.Close()
|
||||
|
||||
f, err := files.Create(src, file.Filename, uint64(file.Size), auth)
|
||||
// Validate we're dealing with an image
|
||||
mime, err := mimetype.DetectReader(src)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err, c)
|
||||
}
|
||||
if !strings.HasPrefix(mime.String(), "image") {
|
||||
return c.JSON(http.StatusBadRequest, models.Message{Message: "Uploaded file is no image."})
|
||||
}
|
||||
_, _ = src.Seek(0, io.SeekStart)
|
||||
|
||||
// Save the file
|
||||
f, err := files.CreateWithMime(src, file.Filename, uint64(file.Size), auth, mime.String())
|
||||
if err != nil {
|
||||
if files.IsErrFileIsTooLarge(err) {
|
||||
return echo.ErrBadRequest
|
||||
|
|
|
@ -43,6 +43,7 @@ func (p *Provider) Search(search string, page int64) (result []*background.Image
|
|||
// @Param background formData string true "The file as single file."
|
||||
// @Security JWTKeyAuth
|
||||
// @Success 200 {object} models.Message "The background was set successfully."
|
||||
// @Failure 400 {object} models.Message "File is no image."
|
||||
// @Failure 403 {object} models.Message "No access to the list."
|
||||
// @Failure 403 {object} models.Message "File too large."
|
||||
// @Failure 404 {object} models.Message "The list does not exist."
|
||||
|
|
|
@ -17,16 +17,27 @@
|
|||
package v1
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/config"
|
||||
"code.vikunja.io/api/pkg/files"
|
||||
"code.vikunja.io/api/pkg/log"
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
"code.vikunja.io/api/pkg/modules/avatar"
|
||||
"code.vikunja.io/api/pkg/modules/avatar/empty"
|
||||
"code.vikunja.io/api/pkg/modules/avatar/gravatar"
|
||||
user2 "code.vikunja.io/api/pkg/user"
|
||||
"code.vikunja.io/api/pkg/modules/avatar/initials"
|
||||
"code.vikunja.io/api/pkg/modules/avatar/upload"
|
||||
"code.vikunja.io/api/pkg/user"
|
||||
"code.vikunja.io/web/handler"
|
||||
|
||||
"bytes"
|
||||
"github.com/disintegration/imaging"
|
||||
"github.com/gabriel-vasile/mimetype"
|
||||
"github.com/labstack/echo/v4"
|
||||
"image"
|
||||
"image/png"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GetAvatar returns a user's avatar
|
||||
|
@ -45,7 +56,7 @@ func GetAvatar(c echo.Context) error {
|
|||
username := c.Param("username")
|
||||
|
||||
// Get the user
|
||||
user, err := user2.GetUserWithEmail(&user2.User{Username: username})
|
||||
u, err := user.GetUserWithEmail(&user.User{Username: username})
|
||||
if err != nil {
|
||||
log.Errorf("Error getting user for avatar: %v", err)
|
||||
return handler.HandleHTTPError(err, c)
|
||||
|
@ -55,9 +66,13 @@ func GetAvatar(c echo.Context) error {
|
|||
// For now, we only have one avatar provider, in the future there could be multiple which
|
||||
// could be changed based on user settings etc.
|
||||
var avatarProvider avatar.Provider
|
||||
switch config.AvatarProvider.GetString() {
|
||||
switch u.AvatarProvider {
|
||||
case "gravatar":
|
||||
avatarProvider = &gravatar.Provider{}
|
||||
case "initials":
|
||||
avatarProvider = &initials.Provider{}
|
||||
case "upload":
|
||||
avatarProvider = &upload.Provider{}
|
||||
default:
|
||||
avatarProvider = &empty.Provider{}
|
||||
}
|
||||
|
@ -73,11 +88,100 @@ func GetAvatar(c echo.Context) error {
|
|||
}
|
||||
|
||||
// Get the avatar
|
||||
a, mimeType, err := avatarProvider.GetAvatar(user, sizeInt)
|
||||
a, mimeType, err := avatarProvider.GetAvatar(u, sizeInt)
|
||||
if err != nil {
|
||||
log.Errorf("Error getting avatar for user %d: %v", user.ID, err)
|
||||
log.Errorf("Error getting avatar for user %d: %v", u.ID, err)
|
||||
return handler.HandleHTTPError(err, c)
|
||||
}
|
||||
|
||||
return c.Blob(http.StatusOK, mimeType, a)
|
||||
}
|
||||
|
||||
// UploadAvatar uploads and sets a user avatar
|
||||
// @Summary Upload a user avatar
|
||||
// @Description Upload a user avatar. This will also set the user's avatar provider to "upload"
|
||||
// @tags user
|
||||
// @Accept mpfd
|
||||
// @Produce json
|
||||
// @Param avatar formData string true "The avatar as single file."
|
||||
// @Security JWTKeyAuth
|
||||
// @Success 200 {object} models.Message "The avatar was set successfully."
|
||||
// @Failure 400 {object} models.Message "File is no image."
|
||||
// @Failure 403 {object} models.Message "File too large."
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /user/settings/avatar/upload [put]
|
||||
func UploadAvatar(c echo.Context) (err error) {
|
||||
|
||||
uc, err := user.GetCurrentUser(c)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err, c)
|
||||
}
|
||||
u, err := user.GetUserByID(uc.ID)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err, c)
|
||||
}
|
||||
|
||||
// Get + upload the image
|
||||
file, err := c.FormFile("avatar")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
src, err := file.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
// Validate we're dealing with an image
|
||||
mime, err := mimetype.DetectReader(src)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err, c)
|
||||
}
|
||||
if !strings.HasPrefix(mime.String(), "image") {
|
||||
return c.JSON(http.StatusBadRequest, models.Message{Message: "Uploaded file is no image."})
|
||||
}
|
||||
_, _ = src.Seek(0, io.SeekStart)
|
||||
|
||||
// Remove the old file if one exists
|
||||
if u.AvatarFileID != 0 {
|
||||
f := &files.File{ID: u.AvatarFileID}
|
||||
if err := f.Delete(); err != nil {
|
||||
if !files.IsErrFileDoesNotExist(err) {
|
||||
return handler.HandleHTTPError(err, c)
|
||||
}
|
||||
}
|
||||
u.AvatarFileID = 0
|
||||
}
|
||||
|
||||
// Resize the new file to a max height of 1024
|
||||
img, _, err := image.Decode(src)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err, c)
|
||||
}
|
||||
resizedImg := imaging.Resize(img, 0, 1024, imaging.Lanczos)
|
||||
buf := &bytes.Buffer{}
|
||||
if err := png.Encode(buf, resizedImg); err != nil {
|
||||
return handler.HandleHTTPError(err, c)
|
||||
}
|
||||
|
||||
upload.InvalidateCache(u)
|
||||
|
||||
// Save the file
|
||||
f, err := files.CreateWithMime(buf, file.Filename, uint64(file.Size), u, "image/png")
|
||||
if err != nil {
|
||||
if files.IsErrFileIsTooLarge(err) {
|
||||
return echo.ErrBadRequest
|
||||
}
|
||||
|
||||
return handler.HandleHTTPError(err, c)
|
||||
}
|
||||
|
||||
u.AvatarFileID = f.ID
|
||||
u.AvatarProvider = "upload"
|
||||
|
||||
if _, err := user.UpdateUser(u); err != nil {
|
||||
return handler.HandleHTTPError(err, c)
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, models.Message{Message: "Avatar was uploaded successfully."})
|
||||
}
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
// Vikunja is a to-do list application to facilitate your life.
|
||||
// Copyright 2018-2020 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 General Public License 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 General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package v1
|
||||
|
||||
import (
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
user2 "code.vikunja.io/api/pkg/user"
|
||||
"code.vikunja.io/web/handler"
|
||||
"github.com/labstack/echo/v4"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// UserAvatarProvider holds the user avatar provider type
|
||||
type UserAvatarProvider struct {
|
||||
AvatarProvider string `json:"avatar_provider"`
|
||||
}
|
||||
|
||||
// GetUserAvatarProvider returns the currently set user avatar
|
||||
// @Summary Return user avatar setting
|
||||
// @Description Returns the current user's avatar setting.
|
||||
// @tags user
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security JWTKeyAuth
|
||||
// @Success 200 {object} UserAvatarProvider
|
||||
// @Failure 400 {object} web.HTTPError "Something's invalid."
|
||||
// @Failure 500 {object} models.Message "Internal server error."
|
||||
// @Router /user/settings/avatar [get]
|
||||
func GetUserAvatarProvider(c echo.Context) error {
|
||||
|
||||
u, err := user2.GetCurrentUser(c)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err, c)
|
||||
}
|
||||
|
||||
user, err := user2.GetUserWithEmail(u)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err, c)
|
||||
}
|
||||
|
||||
uap := &UserAvatarProvider{AvatarProvider: user.AvatarProvider}
|
||||
return c.JSON(http.StatusOK, uap)
|
||||
}
|
||||
|
||||
// ChangeUserAvatarProvider changes the user's avatar provider
|
||||
// @Summary Set the user's avatar
|
||||
// @Description Changes the user avatar. Valid types are gravatar (uses the user email), upload, initials, default.
|
||||
// @tags user
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security JWTKeyAuth
|
||||
// @Param avatar body UserAvatarProvider true "The user's avatar setting"
|
||||
// @Success 200 {object} UserAvatarProvider
|
||||
// @Failure 400 {object} web.HTTPError "Something's invalid."
|
||||
// @Failure 500 {object} models.Message "Internal server error."
|
||||
// @Router /user/settings/avatar [post]
|
||||
func ChangeUserAvatarProvider(c echo.Context) error {
|
||||
|
||||
uap := &UserAvatarProvider{}
|
||||
err := c.Bind(uap)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "Bad avatar type provided.")
|
||||
}
|
||||
|
||||
u, err := user2.GetCurrentUser(c)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err, c)
|
||||
}
|
||||
|
||||
user, err := user2.GetUserWithEmail(u)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err, c)
|
||||
}
|
||||
|
||||
user.AvatarProvider = uap.AvatarProvider
|
||||
|
||||
_, err = user2.UpdateUser(user)
|
||||
if err != nil {
|
||||
return handler.HandleHTTPError(err, c)
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, &models.Message{Message: "Avatar was changed successfully."})
|
||||
}
|
|
@ -254,6 +254,9 @@ func registerAPIRoutes(a *echo.Group) {
|
|||
u.GET("s", apiv1.UserList)
|
||||
u.POST("/token", apiv1.RenewToken)
|
||||
u.POST("/settings/email", apiv1.UpdateUserEmail)
|
||||
u.GET("/settings/avatar", apiv1.GetUserAvatarProvider)
|
||||
u.POST("/settings/avatar", apiv1.ChangeUserAvatarProvider)
|
||||
u.PUT("/settings/avatar/upload", apiv1.UploadAvatar)
|
||||
|
||||
if config.ServiceEnableTotp.GetBool() {
|
||||
u.GET("/settings/totp", apiv1.UserTOTP)
|
||||
|
|
|
@ -923,6 +923,12 @@ var doc = `{
|
|||
"$ref": "#/definitions/models.Message"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "File is no image.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Message"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "File too large.",
|
||||
"schema": {
|
||||
|
@ -1517,6 +1523,70 @@ var doc = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"/lists/{listID}/duplicate": {
|
||||
"put": {
|
||||
"security": [
|
||||
{
|
||||
"JWTKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Copies the list, tasks, files, kanban data, assignees, comments, attachments, lables, relations, backgrounds, user/team rights and link shares from one list to a new namespace. The user needs read access in the list and write access in the namespace of the new list.",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"list"
|
||||
],
|
||||
"summary": "Duplicate an existing list",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "The list ID to duplicate",
|
||||
"name": "listID",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "The target namespace which should hold the copied list.",
|
||||
"name": "list",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ListDuplicate"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The created list.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ListDuplicate"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid list duplicate object provided.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/web.HTTPError"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "The user does not have access to the list or namespace",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/web.HTTPError"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Message"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/lists/{listID}/tasks": {
|
||||
"get": {
|
||||
"security": [
|
||||
|
@ -5476,6 +5546,150 @@ var doc = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"/user/settings/avatar": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"JWTKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Returns the current user's avatar setting.",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"user"
|
||||
],
|
||||
"summary": "Return user avatar setting",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/v1.UserAvatarProvider"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Something's invalid.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/web.HTTPError"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Message"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"JWTKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Changes the user avatar. Valid types are gravatar (uses the user email), upload, initials, default.",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"user"
|
||||
],
|
||||
"summary": "Set the user's avatar",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "The user's avatar setting",
|
||||
"name": "avatar",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/v1.UserAvatarProvider"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/v1.UserAvatarProvider"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Something's invalid.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/web.HTTPError"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Message"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/user/settings/avatar/upload": {
|
||||
"put": {
|
||||
"security": [
|
||||
{
|
||||
"JWTKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Upload a user avatar. This will also set the user's avatar provider to \"upload\"",
|
||||
"consumes": [
|
||||
"multipart/form-data"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"user"
|
||||
],
|
||||
"summary": "Upload a user avatar",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "The avatar as single file.",
|
||||
"name": "avatar",
|
||||
"in": "formData",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The avatar was set successfully.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Message"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "File is no image.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Message"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "File too large.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Message"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Message"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/user/settings/email": {
|
||||
"post": {
|
||||
"security": [
|
||||
|
@ -6297,6 +6511,20 @@ var doc = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"models.ListDuplicate": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"list": {
|
||||
"description": "The copied list",
|
||||
"type": "object",
|
||||
"$ref": "#/definitions/models.List"
|
||||
},
|
||||
"namespace_id": {
|
||||
"description": "The target namespace ID",
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.ListUser": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -7037,6 +7265,14 @@ var doc = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"v1.UserAvatarProvider": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"avatar_provider": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1.UserPassword": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -7048,6 +7284,17 @@ var doc = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"v1.legalInfo": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"imprint_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"privacy_policy_url": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1.vikunjaInfos": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -7066,6 +7313,10 @@ var doc = `{
|
|||
"frontend_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"legal": {
|
||||
"type": "object",
|
||||
"$ref": "#/definitions/v1.legalInfo"
|
||||
},
|
||||
"link_sharing_enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
|
|
|
@ -906,6 +906,12 @@
|
|||
"$ref": "#/definitions/models.Message"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "File is no image.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Message"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "File too large.",
|
||||
"schema": {
|
||||
|
@ -1500,6 +1506,70 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/lists/{listID}/duplicate": {
|
||||
"put": {
|
||||
"security": [
|
||||
{
|
||||
"JWTKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Copies the list, tasks, files, kanban data, assignees, comments, attachments, lables, relations, backgrounds, user/team rights and link shares from one list to a new namespace. The user needs read access in the list and write access in the namespace of the new list.",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"list"
|
||||
],
|
||||
"summary": "Duplicate an existing list",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "The list ID to duplicate",
|
||||
"name": "listID",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "The target namespace which should hold the copied list.",
|
||||
"name": "list",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ListDuplicate"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The created list.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.ListDuplicate"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid list duplicate object provided.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/web.HTTPError"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "The user does not have access to the list or namespace",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/web.HTTPError"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Message"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/lists/{listID}/tasks": {
|
||||
"get": {
|
||||
"security": [
|
||||
|
@ -5459,6 +5529,150 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/user/settings/avatar": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"JWTKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Returns the current user's avatar setting.",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"user"
|
||||
],
|
||||
"summary": "Return user avatar setting",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/v1.UserAvatarProvider"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Something's invalid.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/web.HTTPError"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Message"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"JWTKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Changes the user avatar. Valid types are gravatar (uses the user email), upload, initials, default.",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"user"
|
||||
],
|
||||
"summary": "Set the user's avatar",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "The user's avatar setting",
|
||||
"name": "avatar",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/v1.UserAvatarProvider"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/v1.UserAvatarProvider"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Something's invalid.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/web.HTTPError"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Message"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/user/settings/avatar/upload": {
|
||||
"put": {
|
||||
"security": [
|
||||
{
|
||||
"JWTKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Upload a user avatar. This will also set the user's avatar provider to \"upload\"",
|
||||
"consumes": [
|
||||
"multipart/form-data"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"user"
|
||||
],
|
||||
"summary": "Upload a user avatar",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "The avatar as single file.",
|
||||
"name": "avatar",
|
||||
"in": "formData",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "The avatar was set successfully.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Message"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "File is no image.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Message"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "File too large.",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Message"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Message"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/user/settings/email": {
|
||||
"post": {
|
||||
"security": [
|
||||
|
@ -6279,6 +6493,20 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"models.ListDuplicate": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"list": {
|
||||
"description": "The copied list",
|
||||
"type": "object",
|
||||
"$ref": "#/definitions/models.List"
|
||||
},
|
||||
"namespace_id": {
|
||||
"description": "The target namespace ID",
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.ListUser": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -7019,6 +7247,14 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"v1.UserAvatarProvider": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"avatar_provider": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1.UserPassword": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -7030,6 +7266,17 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"v1.legalInfo": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"imprint_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"privacy_policy_url": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"v1.vikunjaInfos": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -7048,6 +7295,10 @@
|
|||
"frontend_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"legal": {
|
||||
"type": "object",
|
||||
"$ref": "#/definitions/v1.legalInfo"
|
||||
},
|
||||
"link_sharing_enabled": {
|
||||
"type": "boolean"
|
||||
},
|
||||
|
|
|
@ -320,6 +320,16 @@ definitions:
|
|||
this value.
|
||||
type: string
|
||||
type: object
|
||||
models.ListDuplicate:
|
||||
properties:
|
||||
list:
|
||||
$ref: '#/definitions/models.List'
|
||||
description: The copied list
|
||||
type: object
|
||||
namespace_id:
|
||||
description: The target namespace ID
|
||||
type: integer
|
||||
type: object
|
||||
models.ListUser:
|
||||
properties:
|
||||
created:
|
||||
|
@ -905,6 +915,11 @@ definitions:
|
|||
token:
|
||||
type: string
|
||||
type: object
|
||||
v1.UserAvatarProvider:
|
||||
properties:
|
||||
avatar_provider:
|
||||
type: string
|
||||
type: object
|
||||
v1.UserPassword:
|
||||
properties:
|
||||
new_password:
|
||||
|
@ -912,6 +927,13 @@ definitions:
|
|||
old_password:
|
||||
type: string
|
||||
type: object
|
||||
v1.legalInfo:
|
||||
properties:
|
||||
imprint_url:
|
||||
type: string
|
||||
privacy_policy_url:
|
||||
type: string
|
||||
type: object
|
||||
v1.vikunjaInfos:
|
||||
properties:
|
||||
available_migrators:
|
||||
|
@ -924,6 +946,9 @@ definitions:
|
|||
type: array
|
||||
frontend_url:
|
||||
type: string
|
||||
legal:
|
||||
$ref: '#/definitions/v1.legalInfo'
|
||||
type: object
|
||||
link_sharing_enabled:
|
||||
type: boolean
|
||||
max_file_size:
|
||||
|
@ -1578,6 +1603,10 @@ paths:
|
|||
description: The background was set successfully.
|
||||
schema:
|
||||
$ref: '#/definitions/models.Message'
|
||||
"400":
|
||||
description: File is no image.
|
||||
schema:
|
||||
$ref: '#/definitions/models.Message'
|
||||
"403":
|
||||
description: File too large.
|
||||
schema:
|
||||
|
@ -2139,6 +2168,50 @@ paths:
|
|||
summary: Update an existing bucket
|
||||
tags:
|
||||
- task
|
||||
/lists/{listID}/duplicate:
|
||||
put:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Copies the list, tasks, files, kanban data, assignees, comments,
|
||||
attachments, lables, relations, backgrounds, user/team rights and link shares
|
||||
from one list to a new namespace. The user needs read access in the list and
|
||||
write access in the namespace of the new list.
|
||||
parameters:
|
||||
- description: The list ID to duplicate
|
||||
in: path
|
||||
name: listID
|
||||
required: true
|
||||
type: integer
|
||||
- description: The target namespace which should hold the copied list.
|
||||
in: body
|
||||
name: list
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/models.ListDuplicate'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: The created list.
|
||||
schema:
|
||||
$ref: '#/definitions/models.ListDuplicate'
|
||||
"400":
|
||||
description: Invalid list duplicate object provided.
|
||||
schema:
|
||||
$ref: '#/definitions/web.HTTPError'
|
||||
"403":
|
||||
description: The user does not have access to the list or namespace
|
||||
schema:
|
||||
$ref: '#/definitions/web.HTTPError'
|
||||
"500":
|
||||
description: Internal error
|
||||
schema:
|
||||
$ref: '#/definitions/models.Message'
|
||||
security:
|
||||
- JWTKeyAuth: []
|
||||
summary: Duplicate an existing list
|
||||
tags:
|
||||
- list
|
||||
/lists/{listID}/tasks:
|
||||
get:
|
||||
consumes:
|
||||
|
@ -4603,6 +4676,99 @@ paths:
|
|||
summary: Request password reset token
|
||||
tags:
|
||||
- user
|
||||
/user/settings/avatar:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Returns the current user's avatar setting.
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/v1.UserAvatarProvider'
|
||||
"400":
|
||||
description: Something's invalid.
|
||||
schema:
|
||||
$ref: '#/definitions/web.HTTPError'
|
||||
"500":
|
||||
description: Internal server error.
|
||||
schema:
|
||||
$ref: '#/definitions/models.Message'
|
||||
security:
|
||||
- JWTKeyAuth: []
|
||||
summary: Return user avatar setting
|
||||
tags:
|
||||
- user
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Changes the user avatar. Valid types are gravatar (uses the user
|
||||
email), upload, initials, default.
|
||||
parameters:
|
||||
- description: The user's avatar setting
|
||||
in: body
|
||||
name: avatar
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/v1.UserAvatarProvider'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/v1.UserAvatarProvider'
|
||||
"400":
|
||||
description: Something's invalid.
|
||||
schema:
|
||||
$ref: '#/definitions/web.HTTPError'
|
||||
"500":
|
||||
description: Internal server error.
|
||||
schema:
|
||||
$ref: '#/definitions/models.Message'
|
||||
security:
|
||||
- JWTKeyAuth: []
|
||||
summary: Set the user's avatar
|
||||
tags:
|
||||
- user
|
||||
/user/settings/avatar/upload:
|
||||
put:
|
||||
consumes:
|
||||
- multipart/form-data
|
||||
description: Upload a user avatar. This will also set the user's avatar provider
|
||||
to "upload"
|
||||
parameters:
|
||||
- description: The avatar as single file.
|
||||
in: formData
|
||||
name: avatar
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: The avatar was set successfully.
|
||||
schema:
|
||||
$ref: '#/definitions/models.Message'
|
||||
"400":
|
||||
description: File is no image.
|
||||
schema:
|
||||
$ref: '#/definitions/models.Message'
|
||||
"403":
|
||||
description: File too large.
|
||||
schema:
|
||||
$ref: '#/definitions/models.Message'
|
||||
"500":
|
||||
description: Internal error
|
||||
schema:
|
||||
$ref: '#/definitions/models.Message'
|
||||
security:
|
||||
- JWTKeyAuth: []
|
||||
summary: Upload a user avatar
|
||||
tags:
|
||||
- user
|
||||
/user/settings/email:
|
||||
post:
|
||||
consumes:
|
||||
|
|
|
@ -366,3 +366,30 @@ func (err ErrInvalidTOTPPasscode) HTTPError() web.HTTPError {
|
|||
Message: "Invalid totp passcode.",
|
||||
}
|
||||
}
|
||||
|
||||
// ErrInvalidAvatarProvider represents a "InvalidAvatarProvider" kind of error.
|
||||
type ErrInvalidAvatarProvider struct {
|
||||
AvatarProvider string
|
||||
}
|
||||
|
||||
// IsErrInvalidAvatarProvider checks if an error is a ErrInvalidAvatarProvider.
|
||||
func IsErrInvalidAvatarProvider(err error) bool {
|
||||
_, ok := err.(ErrInvalidAvatarProvider)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrInvalidAvatarProvider) Error() string {
|
||||
return "Invalid avatar provider"
|
||||
}
|
||||
|
||||
// ErrCodeInvalidAvatarProvider holds the unique world-error code of this error
|
||||
const ErrCodeInvalidAvatarProvider = 1018
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrInvalidAvatarProvider) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{
|
||||
HTTPCode: http.StatusPreconditionFailed,
|
||||
Code: ErrCodeInvalidAvatarProvider,
|
||||
Message: "Invalid avatar provider setting. See docs for valid types.",
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,6 +55,9 @@ type User struct {
|
|||
PasswordResetToken string `xorm:"varchar(450) null" json:"-"`
|
||||
EmailConfirmToken string `xorm:"varchar(450) null" json:"-"`
|
||||
|
||||
AvatarProvider string `xorm:"varchar(255) null" json:"-"`
|
||||
AvatarFileID int64 `xorn:"null" json:"-"`
|
||||
|
||||
// A timestamp when this task was created. You cannot change this value.
|
||||
Created time.Time `xorm:"created not null" json:"created"`
|
||||
// A timestamp when this task was last updated. You cannot change this value.
|
||||
|
@ -269,6 +272,8 @@ func CreateUser(user *User) (newUser *User, err error) {
|
|||
newUser.EmailConfirmToken = utils.MakeRandomString(60)
|
||||
}
|
||||
|
||||
newUser.AvatarProvider = "initials"
|
||||
|
||||
// Insert it
|
||||
_, err = x.Insert(newUser)
|
||||
if err != nil {
|
||||
|
@ -323,6 +328,16 @@ func UpdateUser(user *User) (updatedUser *User, err error) {
|
|||
|
||||
user.Password = theUser.Password // set the password to the one in the database to not accedently resetting it
|
||||
|
||||
// Validate the avatar type
|
||||
if user.AvatarProvider != "" {
|
||||
if user.AvatarProvider != "default" &&
|
||||
user.AvatarProvider != "gravatar" &&
|
||||
user.AvatarProvider != "initials" &&
|
||||
user.AvatarProvider != "upload" {
|
||||
return updatedUser, &ErrInvalidAvatarProvider{AvatarProvider: user.AvatarProvider}
|
||||
}
|
||||
}
|
||||
|
||||
// Update it
|
||||
_, err = x.ID(user.ID).Update(user)
|
||||
if err != nil {
|
||||
|
|
Loading…
Reference in New Issue