Add arbitrary meta field to media. Closes #938.

- Add new `meta` JSONB field to `media` table.
- Start storing image width and height as meta with media uploads.
This commit is contained in:
Kailash Nadh 2022-10-02 23:04:51 +05:30
parent c3d04a5490
commit c38100427d
7 changed files with 48 additions and 19 deletions

View file

@ -8,6 +8,7 @@ import (
"strconv"
"github.com/disintegration/imaging"
"github.com/knadh/listmonk/models"
"github.com/labstack/echo/v4"
)
@ -78,7 +79,7 @@ func handleUploadMedia(c echo.Context) error {
}()
// Create thumbnail from file.
thumbFile, err := createThumbnail(file)
thumbFile, width, height, err := processImage(file)
if err != nil {
cleanUp = true
app.log.Printf("error resizing image: %v", err)
@ -96,7 +97,11 @@ func handleUploadMedia(c echo.Context) error {
}
// Write to the DB.
m, err := app.core.InsertMedia(fName, thumbfName, app.constants.MediaProvider, app.media)
meta := models.JSON{
"width": width,
"height": height,
}
m, err := app.core.InsertMedia(fName, thumbfName, meta, app.constants.MediaProvider, app.media)
if err != nil {
cleanUp = true
return err
@ -150,17 +155,18 @@ func handleDeleteMedia(c echo.Context) error {
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) {
// processImage reads the image file and returns thumbnail bytes and
// the original image's width, and height.
func processImage(file *multipart.FileHeader) (*bytes.Reader, int, int, error) {
src, err := file.Open()
if err != nil {
return nil, err
return nil, 0, 0, err
}
defer src.Close()
img, err := imaging.Decode(src)
if err != nil {
return nil, err
return nil, 0, 0, err
}
// Encode the image into a byte slice as PNG.
@ -169,7 +175,9 @@ func createThumbnail(file *multipart.FileHeader) (*bytes.Reader, error) {
out bytes.Buffer
)
if err := imaging.Encode(&out, thumb, imaging.PNG); err != nil {
return nil, err
return nil, 0, 0, err
}
return bytes.NewReader(out.Bytes()), nil
b := img.Bounds().Max
return bytes.NewReader(out.Bytes()), b.X, b.Y, nil
}

View file

@ -33,6 +33,7 @@ var migList = []migFunc{
{"v2.0.0", migrations.V2_0_0},
{"v2.1.0", migrations.V2_1_0},
{"v2.2.0", migrations.V2_2_0},
{"v2.3.0", migrations.V2_3_0},
}
// upgrade upgrades the database to the current version by running SQL migration files

View file

@ -5,6 +5,7 @@ import (
"github.com/gofrs/uuid"
"github.com/knadh/listmonk/internal/media"
"github.com/knadh/listmonk/models"
"github.com/labstack/echo/v4"
)
@ -45,7 +46,7 @@ func (c *Core) GetMedia(id int, uuid string, s media.Store) (media.Media, error)
}
// InsertMedia inserts a new media file into the DB.
func (c *Core) InsertMedia(fileName, thumbName string, provider string, s media.Store) (media.Media, error) {
func (c *Core) InsertMedia(fileName, thumbName string, meta models.JSON, provider string, s media.Store) (media.Media, error) {
uu, err := uuid.NewV4()
if err != nil {
c.log.Printf("error generating UUID: %v", err)
@ -55,7 +56,7 @@ func (c *Core) InsertMedia(fileName, thumbName string, provider string, s media.
// Write to the DB.
var newID int
if err := c.q.InsertMedia.Get(&newID, uu, fileName, thumbName, provider); err != nil {
if err := c.q.InsertMedia.Get(&newID, uu, fileName, thumbName, provider, meta); err != nil {
c.log.Printf("error inserting uploaded file to db: %v", err)
return media.Media{}, echo.NewHTTPError(http.StatusInternalServerError,
c.i18n.Ts("globals.messages.errorCreating", "name", "{globals.terms.media}", "error", pqErrMsg(err)))

View file

@ -3,19 +3,21 @@ package media
import (
"io"
"github.com/knadh/listmonk/models"
"gopkg.in/volatiletech/null.v6"
)
// Media represents an uploaded object.
type Media struct {
ID int `db:"id" json:"id"`
UUID string `db:"uuid" json:"uuid"`
Filename string `db:"filename" json:"filename"`
Thumb string `db:"thumb" json:"thumb"`
CreatedAt null.Time `db:"created_at" json:"created_at"`
ThumbURL string `json:"thumb_url"`
Provider string `json:"provider"`
URL string `json:"url"`
ID int `db:"id" json:"id"`
UUID string `db:"uuid" json:"uuid"`
Filename string `db:"filename" json:"filename"`
Thumb string `db:"thumb" json:"thumb"`
CreatedAt null.Time `db:"created_at" json:"created_at"`
ThumbURL string `json:"thumb_url"`
Provider string `json:"provider"`
Meta models.JSON `db:"meta" json:"meta"`
URL string `json:"url"`
}
// Store represents functions to store and retrieve media (files).

View file

@ -0,0 +1,16 @@
package migrations
import (
"github.com/jmoiron/sqlx"
"github.com/knadh/koanf"
"github.com/knadh/stuffbin"
)
// V2_2_0 performs the DB migrations for v.2.2.0.
func V2_3_0(db *sqlx.DB, fs stuffbin.FileSystem, ko *koanf.Koanf) error {
if _, err := db.Exec(`ALTER TABLE media ADD COLUMN IF NOT EXISTS "meta" JSONB NOT NULL DEFAULT '{}'`); err != nil {
return err
}
return nil
}

View file

@ -811,7 +811,7 @@ SELECT id FROM tpl;
-- media
-- name: insert-media
INSERT INTO media (uuid, filename, thumb, provider, created_at) VALUES($1, $2, $3, $4, NOW()) RETURNING id;
INSERT INTO media (uuid, filename, thumb, provider, meta, created_at) VALUES($1, $2, $3, $4, $5, NOW()) RETURNING id;
-- name: get-all-media
SELECT * FROM media WHERE provider=$1 ORDER BY created_at DESC;

View file

@ -139,6 +139,7 @@ CREATE TABLE media (
provider TEXT NOT NULL DEFAULT '',
filename TEXT NOT NULL,
thumb TEXT NOT NULL,
meta JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);