123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184 |
- package main
- import (
- "bytes"
- "mime/multipart"
- "net/http"
- "path/filepath"
- "strconv"
- "github.com/disintegration/imaging"
- "github.com/gofrs/uuid"
- "github.com/knadh/listmonk/internal/media"
- "github.com/labstack/echo"
- )
- const (
- thumbPrefix = "thumb_"
- thumbnailSize = 90
- )
- // validMimes is the list of image types allowed to be uploaded.
- var (
- validMimes = []string{"image/jpg", "image/jpeg", "image/png", "image/gif"}
- validExts = []string{".jpg", ".jpeg", ".png", ".gif"}
- )
- // handleUploadMedia handles media file uploads.
- func handleUploadMedia(c echo.Context) error {
- var (
- app = c.Get("app").(*App)
- cleanUp = false
- )
- file, err := c.FormFile("file")
- if err != nil {
- return echo.NewHTTPError(http.StatusBadRequest,
- app.i18n.Ts("media.invalidFile", "error", err.Error()))
- }
- // Validate file extension.
- ext := filepath.Ext(file.Filename)
- if ok := inArray(ext, validExts); !ok {
- return echo.NewHTTPError(http.StatusBadRequest,
- app.i18n.Ts("media.unsupportedFileType", "type", ext))
- }
- // Validate file's mime.
- typ := file.Header.Get("Content-type")
- if ok := inArray(typ, validMimes); !ok {
- return echo.NewHTTPError(http.StatusBadRequest,
- app.i18n.Ts("media.unsupportedFileType", "type", typ))
- }
- // Generate filename
- fName := makeFilename(file.Filename)
- // Read file contents in memory
- src, err := file.Open()
- if err != nil {
- return echo.NewHTTPError(http.StatusInternalServerError,
- app.i18n.Ts("media.errorReadingFile", "error", err.Error()))
- }
- defer src.Close()
- // Upload the file.
- fName, err = app.media.Put(fName, typ, src)
- if err != nil {
- app.log.Printf("error uploading file: %v", err)
- cleanUp = true
- return echo.NewHTTPError(http.StatusInternalServerError,
- app.i18n.Ts("media.errorUploading", "error", err.Error()))
- }
- defer func() {
- // If any of the subroutines in this function fail,
- // the uploaded image should be removed.
- if cleanUp {
- app.media.Delete(fName)
- app.media.Delete(thumbPrefix + fName)
- }
- }()
- // Create thumbnail from file.
- thumbFile, err := createThumbnail(file)
- if err != nil {
- cleanUp = true
- app.log.Printf("error resizing image: %v", err)
- return echo.NewHTTPError(http.StatusInternalServerError,
- app.i18n.Ts("media.errorResizing", "error", err.Error()))
- }
- // Upload thumbnail.
- thumbfName, err := app.media.Put(thumbPrefix+fName, typ, thumbFile)
- if err != nil {
- cleanUp = true
- app.log.Printf("error saving thumbnail: %v", err)
- return echo.NewHTTPError(http.StatusInternalServerError,
- app.i18n.Ts("media.errorSavingThumbnail", "error", err.Error()))
- }
- uu, err := uuid.NewV4()
- if err != nil {
- app.log.Printf("error generating UUID: %v", err)
- return echo.NewHTTPError(http.StatusInternalServerError,
- app.i18n.Ts("globals.messages.errorUUID", "error", err.Error()))
- }
- // Write to the DB.
- if _, err := app.queries.InsertMedia.Exec(uu, fName, thumbfName, app.constants.MediaProvider); err != nil {
- cleanUp = true
- app.log.Printf("error inserting uploaded file to db: %v", err)
- return echo.NewHTTPError(http.StatusInternalServerError,
- app.i18n.Ts("globals.messages.errorCreating",
- "name", "{globals.terms.media}", "error", pqErrMsg(err)))
- }
- return c.JSON(http.StatusOK, okResp{true})
- }
- // handleGetMedia handles retrieval of uploaded media.
- func handleGetMedia(c echo.Context) error {
- var (
- app = c.Get("app").(*App)
- out = []media.Media{}
- )
- if err := app.queries.GetMedia.Select(&out, app.constants.MediaProvider); err != nil {
- return echo.NewHTTPError(http.StatusInternalServerError,
- app.i18n.Ts("globals.messages.errorFetching",
- "name", "{globals.terms.media}", "error", pqErrMsg(err)))
- }
- for i := 0; i < len(out); i++ {
- out[i].URL = app.media.Get(out[i].Filename)
- out[i].ThumbURL = app.media.Get(out[i].Thumb)
- }
- return c.JSON(http.StatusOK, okResp{out})
- }
- // deleteMedia handles deletion of uploaded media.
- func handleDeleteMedia(c echo.Context) error {
- var (
- app = c.Get("app").(*App)
- id, _ = strconv.Atoi(c.Param("id"))
- )
- if id < 1 {
- return echo.NewHTTPError(http.StatusBadRequest, app.i18n.T("globals.messages.invalidID"))
- }
- var m media.Media
- if err := app.queries.DeleteMedia.Get(&m, id); err != nil {
- return echo.NewHTTPError(http.StatusInternalServerError,
- app.i18n.Ts("globals.messages.errorDeleting",
- "name", "{globals.terms.media}", "error", pqErrMsg(err)))
- }
- app.media.Delete(m.Filename)
- app.media.Delete(thumbPrefix + m.Filename)
- return c.JSON(http.StatusOK, okResp{true})
- }
- // createThumbnail reads the file object and returns a smaller image
- func createThumbnail(file *multipart.FileHeader) (*bytes.Reader, error) {
- src, err := file.Open()
- if err != nil {
- return nil, err
- }
- defer src.Close()
- img, err := imaging.Decode(src)
- if err != nil {
- return nil, err
- }
- // Encode the image into a byte slice as PNG.
- var (
- thumb = imaging.Resize(img, thumbnailSize, 0, imaging.Lanczos)
- out bytes.Buffer
- )
- if err := imaging.Encode(&out, thumb, imaging.PNG); err != nil {
- return nil, err
- }
- return bytes.NewReader(out.Bytes()), nil
- }
|