diff --git a/config.yml.sample b/config.yml.sample index b5e80b3a8..eebd69ef9 100644 --- a/config.yml.sample +++ b/config.yml.sample @@ -185,6 +185,9 @@ backgrounds: # Whether to enable backgrounds for lists at all. enabled: false providers: + upload: + # Whethere to enable uploaded list backgrounds + enabled: false unsplash: # Whether to enable setting backgrounds from unsplash as list backgrounds enabled: false diff --git a/docs/content/doc/setup/config.md b/docs/content/doc/setup/config.md index 2eb7bcacd..2bf086e91 100644 --- a/docs/content/doc/setup/config.md +++ b/docs/content/doc/setup/config.md @@ -228,6 +228,9 @@ backgrounds: # Whether to enable backgrounds for lists at all. enabled: false providers: + upload: + # Whethere to enable uploaded list backgrounds + enabled: false unsplash: # Whether to enable setting backgrounds from unsplash as list backgrounds enabled: false diff --git a/pkg/config/config.go b/pkg/config/config.go index 1c0dc8d2a..38cd8fe88 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -115,6 +115,7 @@ const ( AvatarGravaterExpiration Key = `avatar.gravatarexpiration` BackgroundsEnabled Key = `backgrounds.enabled` + BackgroundsUploadEnabled Key = `backgrounds.providers.upload.enabled` BackgroundsUnsplashEnabled Key = `backgrounds.providers.unsplash.enabled` BackgroundsUnsplashAccessToken Key = `backgrounds.providers.unsplash.accesstoken` BackgroundsUnsplashApplicationID Key = `backgrounds.providers.unsplash.applicationid` @@ -252,6 +253,7 @@ func InitDefaultConfig() { AvatarGravaterExpiration.setDefault(3600) // List Backgrounds BackgroundsEnabled.setDefault(false) + BackgroundsUploadEnabled.setDefault(false) BackgroundsUnsplashEnabled.setDefault(false) } diff --git a/pkg/models/list.go b/pkg/models/list.go index f54bd38b6..7195b8e45 100644 --- a/pkg/models/list.go +++ b/pkg/models/list.go @@ -66,6 +66,14 @@ type List struct { web.Rights `xorm:"-" json:"-"` } +// ListBackgroundType holds a list background type +type ListBackgroundType struct { + Type string +} + +// ListBackgroundUpload represents the list upload background type +const ListBackgroundUpload string = "upload" + // GetListsByNamespaceID gets all lists in a namespace func GetListsByNamespaceID(nID int64, doer *user.User) (lists []*List, err error) { if nID == -1 { @@ -175,11 +183,15 @@ func (l *List) ReadOne() (err error) { // Get any background information if there is one set if l.BackgroundFileID != 0 { - // Currently unsplash only + // Unsplash image l.BackgroundInformation, err = GetUnsplashPhotoByFileID(l.BackgroundFileID) if err != nil && !files.IsErrFileIsNotUnsplashFile(err) { return } + + if err != nil && files.IsErrFileIsNotUnsplashFile(err) { + l.BackgroundInformation = &ListBackgroundType{Type: ListBackgroundUpload} + } } return nil diff --git a/pkg/modules/background/handler/background.go b/pkg/modules/background/handler/background.go index 58a094cc7..0b5430c92 100644 --- a/pkg/modules/background/handler/background.go +++ b/pkg/modules/background/handler/background.go @@ -23,6 +23,7 @@ import ( "code.vikunja.io/api/pkg/modules/background" "code.vikunja.io/api/pkg/modules/background/unsplash" v1 "code.vikunja.io/api/pkg/routes/api/v1" + "code.vikunja.io/web" "code.vikunja.io/web/handler" "github.com/labstack/echo/v4" "net/http" @@ -62,35 +63,42 @@ func (bp *BackgroundProvider) SearchBackgrounds(c echo.Context) error { return c.JSON(http.StatusOK, result) } -// SetBackground sets an Image as list background -func (bp *BackgroundProvider) SetBackground(c echo.Context) error { - auth, err := v1.GetAuthFromClaims(c) +// This function does all kinds of preparations for setting and uploading a background +func (bp *BackgroundProvider) setBackgroundPreparations(c echo.Context) (list *models.List, auth web.Auth, err error) { + auth, err = v1.GetAuthFromClaims(c) if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, "Invalid auth token: "+err.Error()) + return nil, nil, echo.NewHTTPError(http.StatusBadRequest, "Invalid auth token: "+err.Error()) } - p := bp.Provider() - listID, err := strconv.ParseInt(c.Param("list"), 10, 64) if err != nil { - return echo.NewHTTPError(http.StatusBadRequest, "Invalid list ID: "+err.Error()) + return nil, nil, echo.NewHTTPError(http.StatusBadRequest, "Invalid list ID: "+err.Error()) } // Check if the user has the right to change the list background - list := &models.List{ID: listID} + list = &models.List{ID: listID} can, err := list.CanUpdate(auth) if err != nil { - return handler.HandleHTTPError(err, c) + return } if !can { log.Infof("Tried to update list background of list %d while not having the rights for it (User: %v)", listID, auth) - return echo.NewHTTPError(http.StatusForbidden) + return list, auth, models.ErrGenericForbidden{} } // Load the list - if err := list.GetSimpleByID(); err != nil { + err = list.GetSimpleByID() + return +} + +// SetBackground sets an Image as list background +func (bp *BackgroundProvider) SetBackground(c echo.Context) error { + list, auth, err := bp.setBackgroundPreparations(c) + if err != nil { return handler.HandleHTTPError(err, c) } + p := bp.Provider() + image := &background.Image{} err = c.Bind(image) if err != nil { @@ -104,6 +112,44 @@ func (bp *BackgroundProvider) SetBackground(c echo.Context) error { return c.JSON(http.StatusOK, list) } +// UploadBackground uploads a background and passes the id of the uploaded file as an Image to the Set function of the BackgroundProvider. +func (bp *BackgroundProvider) UploadBackground(c echo.Context) error { + list, auth, err := bp.setBackgroundPreparations(c) + if err != nil { + return handler.HandleHTTPError(err, c) + } + + p := bp.Provider() + + // Get + upload the image + file, err := c.FormFile("background") + if err != nil { + return err + } + src, err := file.Open() + if err != nil { + return err + } + defer src.Close() + + f, err := files.Create(src, file.Filename, uint64(file.Size), auth) + if err != nil { + if files.IsErrFileIsTooLarge(err) { + return echo.ErrBadRequest + } + + return handler.HandleHTTPError(err, c) + } + + image := &background.Image{ID: strconv.FormatInt(f.ID, 10)} + + err = p.Set(image, list, auth) + if err != nil { + return handler.HandleHTTPError(err, c) + } + return c.JSON(http.StatusOK, list) +} + // GetListBackground serves a previously set background from a list // It has no knowledge of the provider that was responsible for setting the background. // @Summary Get the list background diff --git a/pkg/modules/background/unsplash/unsplash.go b/pkg/modules/background/unsplash/unsplash.go index 6310a1e95..e6c45c050 100644 --- a/pkg/modules/background/unsplash/unsplash.go +++ b/pkg/modules/background/unsplash/unsplash.go @@ -220,7 +220,7 @@ func (p *Provider) Search(search string, page int64) (result []*background.Image // Set sets an unsplash photo as list background // @Summary Set an unsplash photo as list background -// @Description Sets a photo from unsplash as list background.1 +// @Description Sets a photo from unsplash as list background. // @tags list // @Accept json // @Produce json @@ -308,6 +308,9 @@ func Pingback(f *files.File) { // Check if the file is actually downloaded from unsplash unsplashPhoto, err := models.GetUnsplashPhotoByFileID(f.ID) if err != nil { + if files.IsErrFileIsNotUnsplashFile(err) { + return + } log.Errorf("Unsplash Pingback: %s", err.Error()) } diff --git a/pkg/modules/background/upload/upload.go b/pkg/modules/background/upload/upload.go new file mode 100644 index 000000000..d9b85b096 --- /dev/null +++ b/pkg/modules/background/upload/upload.go @@ -0,0 +1,69 @@ +// 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 . + +package upload + +import ( + "code.vikunja.io/api/pkg/files" + "code.vikunja.io/api/pkg/models" + "code.vikunja.io/api/pkg/modules/background" + "code.vikunja.io/web" + "strconv" +) + +// Provider represents an upload provider +type Provider struct { +} + +// Search is only used to implement the interface +func (p *Provider) Search(search string, page int64) (result []*background.Image, err error) { + return +} + +// Set handles setting a background through a file upload +// @Summary Upload a list background +// @Description Upload a list background. +// @tags list +// @Accept mpfd +// @Produce json +// @Param id path int true "List ID" +// @Param background formData string true "The file as single file." +// @Security JWTKeyAuth +// @Success 200 {object} models.Message "The background was set successfully." +// @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." +// @Failure 500 {object} models.Message "Internal error" +// @Router /lists/{id}/backgrounds/upload [put] +func (p *Provider) Set(image *background.Image, list *models.List, auth web.Auth) (err error) { + // Remove the old background if one exists + if list.BackgroundFileID != 0 { + file := files.File{ID: list.BackgroundFileID} + if err := file.Delete(); err != nil { + return err + } + } + + file := &files.File{} + file.ID, err = strconv.ParseInt(image.ID, 10, 64) + if err != nil { + return + } + + list.BackgroundInformation = &models.ListBackgroundType{Type: models.ListBackgroundUpload} + + return models.SetListBackground(list.ID, file) +} diff --git a/pkg/routes/api/v1/info.go b/pkg/routes/api/v1/info.go index f9034f8e7..503f28002 100644 --- a/pkg/routes/api/v1/info.go +++ b/pkg/routes/api/v1/info.go @@ -68,6 +68,9 @@ func Info(c echo.Context) error { } if config.BackgroundsEnabled.GetBool() { + if config.BackgroundsUploadEnabled.GetBool() { + info.EnabledBackgroundProviders = append(info.EnabledBackgroundProviders, "upload") + } if config.BackgroundsUnsplashEnabled.GetBool() { info.EnabledBackgroundProviders = append(info.EnabledBackgroundProviders, "unsplash") } diff --git a/pkg/routes/routes.go b/pkg/routes/routes.go index 23a4a4077..765b7d9ca 100644 --- a/pkg/routes/routes.go +++ b/pkg/routes/routes.go @@ -50,6 +50,7 @@ import ( "code.vikunja.io/api/pkg/modules/background" backgroundHandler "code.vikunja.io/api/pkg/modules/background/handler" "code.vikunja.io/api/pkg/modules/background/unsplash" + "code.vikunja.io/api/pkg/modules/background/upload" "code.vikunja.io/api/pkg/modules/migration" migrationHandler "code.vikunja.io/api/pkg/modules/migration/handler" "code.vikunja.io/api/pkg/modules/migration/todoist" @@ -454,6 +455,14 @@ func registerAPIRoutes(a *echo.Group) { // List Backgrounds if config.BackgroundsEnabled.GetBool() { a.GET("/lists/:list/background", backgroundHandler.GetListBackground) + if config.BackgroundsUploadEnabled.GetBool() { + uploadBackgroundProvider := &backgroundHandler.BackgroundProvider{ + Provider: func() background.Provider { + return &upload.Provider{} + }, + } + a.PUT("/lists/:list/backgrounds/upload", uploadBackgroundProvider.UploadBackground) + } if config.BackgroundsUnsplashEnabled.GetBool() { unsplashBackgroundProvider := &backgroundHandler.BackgroundProvider{ Provider: func() background.Provider {