media.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. package main
  2. import (
  3. "bytes"
  4. "mime/multipart"
  5. "net/http"
  6. "path/filepath"
  7. "strconv"
  8. "github.com/disintegration/imaging"
  9. "github.com/gofrs/uuid"
  10. "github.com/knadh/listmonk/internal/media"
  11. "github.com/labstack/echo"
  12. )
  13. const (
  14. thumbPrefix = "thumb_"
  15. thumbnailSize = 90
  16. )
  17. // validMimes is the list of image types allowed to be uploaded.
  18. var (
  19. validMimes = []string{"image/jpg", "image/jpeg", "image/png", "image/gif"}
  20. validExts = []string{".jpg", ".jpeg", ".png", ".gif"}
  21. )
  22. // handleUploadMedia handles media file uploads.
  23. func handleUploadMedia(c echo.Context) error {
  24. var (
  25. app = c.Get("app").(*App)
  26. cleanUp = false
  27. )
  28. file, err := c.FormFile("file")
  29. if err != nil {
  30. return echo.NewHTTPError(http.StatusBadRequest,
  31. app.i18n.Ts("media.invalidFile", "error", err.Error()))
  32. }
  33. // Validate file extension.
  34. ext := filepath.Ext(file.Filename)
  35. if ok := inArray(ext, validExts); !ok {
  36. return echo.NewHTTPError(http.StatusBadRequest,
  37. app.i18n.Ts("media.unsupportedFileType", "type", ext))
  38. }
  39. // Validate file's mime.
  40. typ := file.Header.Get("Content-type")
  41. if ok := inArray(typ, validMimes); !ok {
  42. return echo.NewHTTPError(http.StatusBadRequest,
  43. app.i18n.Ts("media.unsupportedFileType", "type", typ))
  44. }
  45. // Generate filename
  46. fName := makeFilename(file.Filename)
  47. // Read file contents in memory
  48. src, err := file.Open()
  49. if err != nil {
  50. return echo.NewHTTPError(http.StatusInternalServerError,
  51. app.i18n.Ts("media.errorReadingFile", "error", err.Error()))
  52. }
  53. defer src.Close()
  54. // Upload the file.
  55. fName, err = app.media.Put(fName, typ, src)
  56. if err != nil {
  57. app.log.Printf("error uploading file: %v", err)
  58. cleanUp = true
  59. return echo.NewHTTPError(http.StatusInternalServerError,
  60. app.i18n.Ts("media.errorUploading", "error", err.Error()))
  61. }
  62. defer func() {
  63. // If any of the subroutines in this function fail,
  64. // the uploaded image should be removed.
  65. if cleanUp {
  66. app.media.Delete(fName)
  67. app.media.Delete(thumbPrefix + fName)
  68. }
  69. }()
  70. // Create thumbnail from file.
  71. thumbFile, err := createThumbnail(file)
  72. if err != nil {
  73. cleanUp = true
  74. app.log.Printf("error resizing image: %v", err)
  75. return echo.NewHTTPError(http.StatusInternalServerError,
  76. app.i18n.Ts("media.errorResizing", "error", err.Error()))
  77. }
  78. // Upload thumbnail.
  79. thumbfName, err := app.media.Put(thumbPrefix+fName, typ, thumbFile)
  80. if err != nil {
  81. cleanUp = true
  82. app.log.Printf("error saving thumbnail: %v", err)
  83. return echo.NewHTTPError(http.StatusInternalServerError,
  84. app.i18n.Ts("media.errorSavingThumbnail", "error", err.Error()))
  85. }
  86. uu, err := uuid.NewV4()
  87. if err != nil {
  88. app.log.Printf("error generating UUID: %v", err)
  89. return echo.NewHTTPError(http.StatusInternalServerError,
  90. app.i18n.Ts("globals.messages.errorUUID", "error", err.Error()))
  91. }
  92. // Write to the DB.
  93. if _, err := app.queries.InsertMedia.Exec(uu, fName, thumbfName, app.constants.MediaProvider); err != nil {
  94. cleanUp = true
  95. app.log.Printf("error inserting uploaded file to db: %v", err)
  96. return echo.NewHTTPError(http.StatusInternalServerError,
  97. app.i18n.Ts("globals.messages.errorCreating",
  98. "name", "{globals.terms.media}", "error", pqErrMsg(err)))
  99. }
  100. return c.JSON(http.StatusOK, okResp{true})
  101. }
  102. // handleGetMedia handles retrieval of uploaded media.
  103. func handleGetMedia(c echo.Context) error {
  104. var (
  105. app = c.Get("app").(*App)
  106. out = []media.Media{}
  107. )
  108. if err := app.queries.GetMedia.Select(&out, app.constants.MediaProvider); err != nil {
  109. return echo.NewHTTPError(http.StatusInternalServerError,
  110. app.i18n.Ts("globals.messages.errorFetching",
  111. "name", "{globals.terms.media}", "error", pqErrMsg(err)))
  112. }
  113. for i := 0; i < len(out); i++ {
  114. out[i].URL = app.media.Get(out[i].Filename)
  115. out[i].ThumbURL = app.media.Get(out[i].Thumb)
  116. }
  117. return c.JSON(http.StatusOK, okResp{out})
  118. }
  119. // deleteMedia handles deletion of uploaded media.
  120. func handleDeleteMedia(c echo.Context) error {
  121. var (
  122. app = c.Get("app").(*App)
  123. id, _ = strconv.Atoi(c.Param("id"))
  124. )
  125. if id < 1 {
  126. return echo.NewHTTPError(http.StatusBadRequest, app.i18n.T("globals.messages.invalidID"))
  127. }
  128. var m media.Media
  129. if err := app.queries.DeleteMedia.Get(&m, id); err != nil {
  130. return echo.NewHTTPError(http.StatusInternalServerError,
  131. app.i18n.Ts("globals.messages.errorDeleting",
  132. "name", "{globals.terms.media}", "error", pqErrMsg(err)))
  133. }
  134. app.media.Delete(m.Filename)
  135. app.media.Delete(thumbPrefix + m.Filename)
  136. return c.JSON(http.StatusOK, okResp{true})
  137. }
  138. // createThumbnail reads the file object and returns a smaller image
  139. func createThumbnail(file *multipart.FileHeader) (*bytes.Reader, error) {
  140. src, err := file.Open()
  141. if err != nil {
  142. return nil, err
  143. }
  144. defer src.Close()
  145. img, err := imaging.Decode(src)
  146. if err != nil {
  147. return nil, err
  148. }
  149. // Encode the image into a byte slice as PNG.
  150. var (
  151. thumb = imaging.Resize(img, thumbnailSize, 0, imaging.Lanczos)
  152. out bytes.Buffer
  153. )
  154. if err := imaging.Encode(&out, thumb, imaging.PNG); err != nil {
  155. return nil, err
  156. }
  157. return bytes.NewReader(out.Bytes()), nil
  158. }