2020-12-29 02:04:20 +01:00
// Vikunja is a to-do list application to facilitate your life.
2021-02-02 20:19:13 +01:00
// Copyright 2018-2021 Vikunja and contributors. All rights reserved.
2020-03-01 21:30:37 +01:00
//
// This program is free software: you can redistribute it and/or modify
2020-12-23 16:41:52 +01:00
// it under the terms of the GNU Affero General Public Licensee as published by
2020-03-01 21:30:37 +01:00
// 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
2020-12-23 16:41:52 +01:00
// GNU Affero General Public Licensee for more details.
2020-03-01 21:30:37 +01:00
//
2020-12-23 16:41:52 +01:00
// You should have received a copy of the GNU Affero General Public Licensee
2020-03-01 21:30:37 +01:00
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package v1
import (
2022-06-12 18:29:12 +02:00
"code.vikunja.io/api/pkg/config"
2020-12-23 16:32:28 +01:00
"code.vikunja.io/api/pkg/db"
2020-08-02 19:16:58 +02:00
"code.vikunja.io/api/pkg/files"
2020-03-01 21:30:37 +01:00
"code.vikunja.io/api/pkg/log"
2020-08-02 19:16:58 +02:00
"code.vikunja.io/api/pkg/models"
2020-03-01 22:10:25 +01:00
"code.vikunja.io/api/pkg/modules/avatar"
"code.vikunja.io/api/pkg/modules/avatar/empty"
2020-03-01 21:30:37 +01:00
"code.vikunja.io/api/pkg/modules/avatar/gravatar"
2020-08-02 19:16:58 +02:00
"code.vikunja.io/api/pkg/modules/avatar/initials"
2021-12-07 22:11:23 +01:00
"code.vikunja.io/api/pkg/modules/avatar/marble"
2020-08-02 19:16:58 +02:00
"code.vikunja.io/api/pkg/modules/avatar/upload"
"code.vikunja.io/api/pkg/user"
2020-03-01 21:30:37 +01:00
"code.vikunja.io/web/handler"
2020-08-02 19:16:58 +02:00
"bytes"
"image"
"image/png"
"io"
2020-03-01 21:30:37 +01:00
"net/http"
"strconv"
2020-08-02 19:16:58 +02:00
"strings"
2020-10-11 22:10:03 +02:00
"github.com/disintegration/imaging"
"github.com/gabriel-vasile/mimetype"
"github.com/labstack/echo/v4"
2020-03-01 21:30:37 +01:00
)
// GetAvatar returns a user's avatar
// @Summary User Avatar
// @Description Returns the user avatar as image.
// @tags user
// @Produce octet-stream
// @Param username path string true "The username of the user who's avatar you want to get"
2022-06-12 18:29:12 +02:00
// @Param size query int false "The size of the avatar you want to get. If bigger than the max configured size this will be adjusted to the maximum size."
2020-03-01 21:30:37 +01:00
// @Success 200 {} blob "The avatar"
// @Failure 404 {object} models.Message "The user does not exist."
// @Failure 500 {object} models.Message "Internal error"
// @Router /{username}/avatar [get]
func GetAvatar ( c echo . Context ) error {
// Get the username
username := c . Param ( "username" )
2020-12-23 16:32:28 +01:00
s := db . NewSession ( )
defer s . Close ( )
2020-03-01 21:30:37 +01:00
// Get the user
2020-12-23 16:32:28 +01:00
u , err := user . GetUserWithEmail ( s , & user . User { Username : username } )
2021-04-07 14:56:44 +02:00
if err != nil && ! user . IsErrUserDoesNotExist ( err ) {
2020-03-01 21:30:37 +01:00
log . Errorf ( "Error getting user for avatar: %v" , err )
return handler . HandleHTTPError ( err , c )
}
2021-04-07 14:56:44 +02:00
found := ! ( err != nil && user . IsErrUserDoesNotExist ( err ) )
2020-03-01 22:10:25 +01:00
var avatarProvider avatar . Provider
2020-08-02 19:16:58 +02:00
switch u . AvatarProvider {
2020-03-01 22:10:25 +01:00
case "gravatar" :
avatarProvider = & gravatar . Provider { }
2020-08-02 19:16:58 +02:00
case "initials" :
avatarProvider = & initials . Provider { }
case "upload" :
avatarProvider = & upload . Provider { }
2021-12-07 22:11:23 +01:00
case "marble" :
avatarProvider = & marble . Provider { }
2020-03-01 22:10:25 +01:00
default :
avatarProvider = & empty . Provider { }
}
2020-03-01 21:30:37 +01:00
2021-04-07 14:56:44 +02:00
if ! found {
avatarProvider = & empty . Provider { }
}
2020-03-01 21:30:37 +01:00
size := c . QueryParam ( "size" )
var sizeInt int64 = 250 // Default size of 250
if size != "" {
sizeInt , err = strconv . ParseInt ( size , 10 , 64 )
if err != nil {
log . Errorf ( "Error parsing size: %v" , err )
return handler . HandleHTTPError ( err , c )
}
}
2022-06-12 18:29:12 +02:00
if sizeInt > config . ServiceMaxAvatarSize . GetInt64 ( ) {
sizeInt = config . ServiceMaxAvatarSize . GetInt64 ( )
}
2020-03-01 21:30:37 +01:00
// Get the avatar
2020-08-02 19:16:58 +02:00
a , mimeType , err := avatarProvider . GetAvatar ( u , sizeInt )
2020-03-01 21:30:37 +01:00
if err != nil {
2020-08-02 19:16:58 +02:00
log . Errorf ( "Error getting avatar for user %d: %v" , u . ID , err )
2020-03-01 21:30:37 +01:00
return handler . HandleHTTPError ( err , c )
}
2020-03-01 22:10:25 +01:00
return c . Blob ( http . StatusOK , mimeType , a )
2020-03-01 21:30:37 +01:00
}
2020-08-02 19:16:58 +02:00
// 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 ) {
2020-12-23 16:32:28 +01:00
s := db . NewSession ( )
defer s . Close ( )
2020-08-02 19:16:58 +02:00
uc , err := user . GetCurrentUser ( c )
if err != nil {
return handler . HandleHTTPError ( err , c )
}
2020-12-23 16:32:28 +01:00
u , err := user . GetUserByID ( s , uc . ID )
2020-08-02 19:16:58 +02:00
if err != nil {
2020-12-23 16:32:28 +01:00
_ = s . Rollback ( )
2020-08-02 19:16:58 +02:00
return handler . HandleHTTPError ( err , c )
}
// Get + upload the image
file , err := c . FormFile ( "avatar" )
if err != nil {
2020-12-23 16:32:28 +01:00
_ = s . Rollback ( )
2020-08-02 19:16:58 +02:00
return err
}
src , err := file . Open ( )
if err != nil {
2020-12-23 16:32:28 +01:00
_ = s . Rollback ( )
2020-08-02 19:16:58 +02:00
return err
}
defer src . Close ( )
// Validate we're dealing with an image
mime , err := mimetype . DetectReader ( src )
if err != nil {
2020-12-23 16:32:28 +01:00
_ = s . Rollback ( )
2020-08-02 19:16:58 +02:00
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 ) {
2020-12-23 16:32:28 +01:00
_ = s . Rollback ( )
2020-08-02 19:16:58 +02:00
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 {
2020-12-23 16:32:28 +01:00
_ = s . Rollback ( )
2020-08-02 19:16:58 +02:00
return handler . HandleHTTPError ( err , c )
}
resizedImg := imaging . Resize ( img , 0 , 1024 , imaging . Lanczos )
buf := & bytes . Buffer { }
if err := png . Encode ( buf , resizedImg ) ; err != nil {
2020-12-23 16:32:28 +01:00
_ = s . Rollback ( )
2020-08-02 19:16:58 +02:00
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 {
2020-12-23 16:32:28 +01:00
_ = s . Rollback ( )
2020-08-02 19:16:58 +02:00
if files . IsErrFileIsTooLarge ( err ) {
return echo . ErrBadRequest
}
return handler . HandleHTTPError ( err , c )
}
u . AvatarFileID = f . ID
u . AvatarProvider = "upload"
2020-12-23 16:32:28 +01:00
if _ , err := user . UpdateUser ( s , u ) ; err != nil {
_ = s . Rollback ( )
return handler . HandleHTTPError ( err , c )
}
if err := s . Commit ( ) ; err != nil {
_ = s . Rollback ( )
2020-08-02 19:16:58 +02:00
return handler . HandleHTTPError ( err , c )
}
return c . JSON ( http . StatusOK , models . Message { Message : "Avatar was uploaded successfully." } )
}