91 lines
3.4 KiB
Go
91 lines
3.4 KiB
Go
package postgres
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"database/sql"
|
|
"encoding/hex"
|
|
"strings"
|
|
|
|
"gardomatic.kleiax.de/internal/storage"
|
|
)
|
|
|
|
// ImageModel implements storage.ImageModelInterface for PostgreSQL. Image
|
|
// retrieval is always constrained by the owning garden.
|
|
type ImageModel struct{ DB *sql.DB }
|
|
|
|
// Insert stores an image in its garden's library.
|
|
func (m ImageModel) Insert(image storage.Image) (storage.Image, error) {
|
|
ctx, cancel := contextWithTimeout()
|
|
defer cancel()
|
|
sum := sha256.Sum256(image.Data)
|
|
err := m.DB.QueryRowContext(ctx, `INSERT INTO images(garden_id,file_name,media_type,data,size,checksum,source,created_by)
|
|
VALUES($1,$2,$3,$4,$5,$6,$7,$8) RETURNING id,size,created_at`, image.GardenID, image.FileName,
|
|
image.MediaType, image.Data, len(image.Data), hex.EncodeToString(sum[:]), image.Source, nullableUserID(image.CreatedBy)).Scan(&image.ID, &image.Size, &image.CreatedAt)
|
|
if err != nil {
|
|
return storage.Image{}, recordError(err)
|
|
}
|
|
image.Data = nil
|
|
return image, nil
|
|
}
|
|
|
|
// Get returns an image within its garden.
|
|
func (m ImageModel) Get(gardenID, id int) (storage.Image, error) {
|
|
ctx, cancel := contextWithTimeout()
|
|
defer cancel()
|
|
var image storage.Image
|
|
err := m.DB.QueryRowContext(ctx, `SELECT id,garden_id,file_name,media_type,size,source,COALESCE(created_by,0),created_at,data FROM images WHERE garden_id=$1 AND id=$2`, gardenID, id).Scan(
|
|
&image.ID, &image.GardenID, &image.FileName, &image.MediaType, &image.Size, &image.Source, &image.CreatedBy, &image.CreatedAt, &image.Data)
|
|
if err != nil {
|
|
return storage.Image{}, recordError(err)
|
|
}
|
|
return image, nil
|
|
}
|
|
|
|
// GetAllForGarden lists image metadata matching filter.
|
|
func (m ImageModel) GetAllForGarden(gardenID int, filter storage.ImageFilter) ([]storage.Image, error) {
|
|
ctx, cancel := contextWithTimeout()
|
|
defer cancel()
|
|
rows, err := m.DB.QueryContext(ctx, `SELECT id,garden_id,file_name,media_type,size,source,COALESCE(created_by,0),created_at
|
|
FROM images WHERE garden_id=$1 AND ($2='' OR source=$2) AND ($3='' OR LOWER(file_name) LIKE '%'||LOWER($3)||'%') ORDER BY created_at DESC,id DESC`, gardenID, filter.Source, strings.TrimSpace(filter.Query))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
result := []storage.Image{}
|
|
for rows.Next() {
|
|
var image storage.Image
|
|
if err = rows.Scan(&image.ID, &image.GardenID, &image.FileName, &image.MediaType, &image.Size, &image.Source, &image.CreatedBy, &image.CreatedAt); err != nil {
|
|
return nil, err
|
|
}
|
|
result = append(result, image)
|
|
}
|
|
return result, rows.Err()
|
|
}
|
|
|
|
// CountForGarden returns the number of images in a garden library.
|
|
func (m ImageModel) CountForGarden(gardenID int) (int, error) {
|
|
ctx, cancel := contextWithTimeout()
|
|
defer cancel()
|
|
var count int
|
|
err := m.DB.QueryRowContext(ctx, `SELECT COUNT(*) FROM images WHERE garden_id=$1`, gardenID).Scan(&count)
|
|
return count, err
|
|
}
|
|
|
|
// RecordAssignment appends an entity image-change history record.
|
|
func (m ImageModel) RecordAssignment(c storage.ImageAssignment) error {
|
|
if sameImage(c.PreviousImageID, c.ImageID) {
|
|
return nil
|
|
}
|
|
ctx, cancel := contextWithTimeout()
|
|
defer cancel()
|
|
_, err := m.DB.ExecContext(ctx, `INSERT INTO entity_image_history(garden_id,entity_type,entity_id,previous_image_id,image_id,changed_by) VALUES($1,$2,$3,$4,$5,$6)`, c.GardenID, c.EntityType, c.EntityID, c.PreviousImageID, c.ImageID, nullableUserID(c.ChangedBy))
|
|
return err
|
|
}
|
|
|
|
func sameImage(a, b *int) bool {
|
|
if a == nil || b == nil {
|
|
return a == nil && b == nil
|
|
}
|
|
return *a == *b
|
|
}
|