diff --git a/pkg/modules/background/background.go b/pkg/modules/background/background.go index 17f25157..bb24b0fc 100644 --- a/pkg/modules/background/background.go +++ b/pkg/modules/background/background.go @@ -25,7 +25,7 @@ import ( type Image struct { ID string `json:"id"` URL string `json:"url"` - Thumb string `json:"thumb"` + Thumb string `json:"thumb,omitempty"` // This can be used to supply extra information from an image provider to clients Info interface{} `json:"info,omitempty"` } diff --git a/pkg/modules/background/unsplash/proxy.go b/pkg/modules/background/unsplash/proxy.go new file mode 100644 index 00000000..b6a66503 --- /dev/null +++ b/pkg/modules/background/unsplash/proxy.go @@ -0,0 +1,68 @@ +// 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 unsplash + +import ( + "github.com/labstack/echo/v4" + "net/http" +) + +// ProxyUnsplashImage proxies a thumbnail from unsplash for privacy reasons. +// @Summary Get an unsplash image +// @Description Get an unsplash image. **Returns json on error.** +// @tags list +// @Produce octet-stream +// @Param thumb path int true "Unsplash Image ID" +// @Security JWTKeyAuth +// @Success 200 {} string "The image" +// @Failure 404 {object} models.Message "The image does not exist." +// @Failure 500 {object} models.Message "Internal error" +// @Router /backgrounds/unsplash/image/{image} [get] +func ProxyUnsplashImage(c echo.Context) error { + imageID := c.Param("image") + resp, err := http.Get("https://images.unsplash.com/photo-" + imageID + "?ixlib=rb-1.2.1&fm=jpg&ixid=eyJhcHBfaWQiOjcyODAwfQ") + if err != nil { + return err + } + if resp.StatusCode > 399 { + return echo.ErrNotFound + } + return c.Stream(http.StatusOK, "image/jpg", resp.Body) +} + +// ProxyUnsplashThumb proxies a thumbnail from unsplash for privacy reasons. +// @Summary Get an unsplash thumbnail image +// @Description Get an unsplash thumbnail image. The thumbnail is cropped to a max width of 200px. **Returns json on error.** +// @tags list +// @Produce octet-stream +// @Param thumb path int true "Unsplash Image ID" +// @Security JWTKeyAuth +// @Success 200 {} string "The thumbnail" +// @Failure 404 {object} models.Message "The image does not exist." +// @Failure 500 {object} models.Message "Internal error" +// @Router /backgrounds/unsplash/image/{image}/thumb [get] +func ProxyUnsplashThumb(c echo.Context) error { + imageID := c.Param("image") + resp, err := http.Get("https://images.unsplash.com/photo-" + imageID + "?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=200&fit=max&ixid=eyJhcHBfaWQiOjcyODAwfQ") + if err != nil { + return err + } + if resp.StatusCode > 399 { + return echo.ErrNotFound + } + return c.Stream(http.StatusOK, "image/jpg", resp.Body) +} diff --git a/pkg/modules/background/unsplash/unsplash.go b/pkg/modules/background/unsplash/unsplash.go index 73df0936..b2ddd1f4 100644 --- a/pkg/modules/background/unsplash/unsplash.go +++ b/pkg/modules/background/unsplash/unsplash.go @@ -26,6 +26,7 @@ import ( "encoding/json" "net/http" "strconv" + "strings" ) // Provider represents an unsplash image provider @@ -90,6 +91,18 @@ func doGet(url string, result interface{}) (err error) { return } +func getImageID(fullURL string) string { + // Unsplash image urls have the form + // https://images.unsplash.com/photo-1590622878565-c662a7fd1394?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&w=200&fit=max&ixid=eyJhcHBfaWQiOjcyODAwfQ + // We only need the "photo-*" part of it. + parts := strings.Split(strings.Split(fullURL, "?")[0], "photo-") + if len(parts) < 2 { + log.Errorf("Unsplash thumb url does not contain enough parts [parts: %v]", parts) + return "" + } + return parts[1] +} + // Search is the implementation to search on unsplash // @Summary Search for a background from unsplash // @Description Search for a list background from unsplash @@ -114,9 +127,8 @@ func (p *Provider) Search(search string, page int64) (result []*background.Image result = []*background.Image{} for _, p := range collectionResult { result = append(result, &background.Image{ - ID: p.ID, - URL: p.Urls.Raw, - Thumb: p.Urls.Thumb, + ID: p.ID, + URL: getImageID(p.Urls.Raw), Info: &models.UnsplashPhoto{ UnsplashID: p.ID, Author: p.User.Username, @@ -137,9 +149,8 @@ func (p *Provider) Search(search string, page int64) (result []*background.Image result = []*background.Image{} for _, p := range searchResult.Results { result = append(result, &background.Image{ - ID: p.ID, - URL: p.Urls.Raw, - Thumb: p.Urls.Thumb, + ID: p.ID, + URL: getImageID(p.Urls.Raw), Info: &models.UnsplashPhoto{ UnsplashID: p.ID, Author: p.User.Username, diff --git a/pkg/routes/routes.go b/pkg/routes/routes.go index 9f5376ea..dc41778c 100644 --- a/pkg/routes/routes.go +++ b/pkg/routes/routes.go @@ -459,6 +459,8 @@ func registerAPIRoutes(a *echo.Group) { } a.GET("/backgrounds/unsplash/search", unsplashBackgroundProvider.SearchBackgrounds) a.POST("/lists/:list/backgrounds/unsplash", unsplashBackgroundProvider.SetBackground) + a.GET("/backgrounds/unsplash/images/:image/thumb", unsplash.ProxyUnsplashThumb) + a.GET("/backgrounds/unsplash/images/:image", unsplash.ProxyUnsplashImage) } } }