refac: decouple persistence and image storage behind driver registries
This commit is contained in:
1 parent
421cfafec3
commit
81e299f09a
46 files changed
+1036
-374
No files matched your search
@@ -0,0 +1,90 @@
|
||||
package useCases
|
||||
|
||||
import (
|
||||
"RenewCMS/internal/domain/image"
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
stdimage "image"
|
||||
"io"
|
||||
"path"
|
||||
|
||||
_ "golang.org/x/image/webp"
|
||||
_ "image/jpeg"
|
||||
_ "image/png"
|
||||
)
|
||||
|
||||
var extensionContentTypes = map[string]string{
|
||||
".png": "image/png",
|
||||
".jpg": "image/jpeg",
|
||||
".jpeg": "image/jpeg",
|
||||
".webp": "image/webp",
|
||||
".svg": "image/svg+xml",
|
||||
}
|
||||
|
||||
type BackfillImageMetadataUseCase struct {
|
||||
imageRepository image.Repository
|
||||
storage image.Storage
|
||||
}
|
||||
|
||||
func NewBackfillImageMetadataUseCase(imageRepository image.Repository, storage image.Storage) *BackfillImageMetadataUseCase {
|
||||
return &BackfillImageMetadataUseCase{imageRepository, storage}
|
||||
}
|
||||
|
||||
func (u *BackfillImageMetadataUseCase) Run() error {
|
||||
pending, err := u.imageRepository.ListMissingMetadata()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, row := range pending {
|
||||
metadata, ok := u.readStoredImage(row.Path)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if err := u.imageRepository.UpdateMetadata(row.ID, metadata); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *BackfillImageMetadataUseCase) readStoredImage(storedPath string) (image.Metadata, bool) {
|
||||
fileName := image.StoredName(storedPath)
|
||||
contentType, known := extensionContentTypes[path.Ext(fileName)]
|
||||
if !known {
|
||||
return image.Metadata{}, false
|
||||
}
|
||||
|
||||
data, err := u.read(fileName)
|
||||
if err != nil {
|
||||
return image.Metadata{}, false
|
||||
}
|
||||
|
||||
width, height := 0, 0
|
||||
if contentType != "image/svg+xml" {
|
||||
config, _, err := stdimage.DecodeConfig(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return image.Metadata{}, false
|
||||
}
|
||||
width, height = config.Width, config.Height
|
||||
}
|
||||
|
||||
checksum := sha256.Sum256(data)
|
||||
return image.Metadata{
|
||||
Filename: fileName,
|
||||
Size: int64(len(data)),
|
||||
ContentType: contentType,
|
||||
Width: width,
|
||||
Height: height,
|
||||
SHA256: hex.EncodeToString(checksum[:]),
|
||||
}, true
|
||||
}
|
||||
|
||||
func (u *BackfillImageMetadataUseCase) read(storedName string) ([]byte, error) {
|
||||
blob, err := u.storage.Open(storedName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer blob.Content.Close()
|
||||
return io.ReadAll(blob.Content)
|
||||
}
|
||||
@@ -24,10 +24,11 @@ var rasterFormats = map[string]string{
|
||||
|
||||
type CreateImageUseCase struct {
|
||||
imageRepository image.Repository
|
||||
storage image.Storage
|
||||
}
|
||||
|
||||
func NewCreateImageUseCase(imageRepository image.Repository) *CreateImageUseCase {
|
||||
return &CreateImageUseCase{imageRepository}
|
||||
func NewCreateImageUseCase(imageRepository image.Repository, storage image.Storage) *CreateImageUseCase {
|
||||
return &CreateImageUseCase{imageRepository, storage}
|
||||
}
|
||||
|
||||
func (g *CreateImageUseCase) CreateImage(input image.ImageInput) (image.Image, error) {
|
||||
@@ -46,8 +47,13 @@ func (g *CreateImageUseCase) CreateImage(input image.ImageInput) (image.Image, e
|
||||
}
|
||||
|
||||
checksum := sha256.Sum256(data)
|
||||
return g.imageRepository.Create(image.ImageInput{
|
||||
Content: bytes.NewReader(data),
|
||||
storedName, err := g.storage.Put(input.ContentType, bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return image.Image{}, err
|
||||
}
|
||||
|
||||
created, err := g.imageRepository.Create(image.Image{
|
||||
Path: image.PublicPath(storedName),
|
||||
Filename: image.NormalizeUploadFilename(input.Filename, input.ContentType),
|
||||
Size: int64(len(data)),
|
||||
ContentType: input.ContentType,
|
||||
@@ -56,6 +62,11 @@ func (g *CreateImageUseCase) CreateImage(input image.ImageInput) (image.Image, e
|
||||
SHA256: hex.EncodeToString(checksum[:]),
|
||||
UploadedByID: input.UploadedByID,
|
||||
})
|
||||
if err != nil {
|
||||
_ = g.storage.Delete(storedName)
|
||||
return image.Image{}, err
|
||||
}
|
||||
return created, nil
|
||||
}
|
||||
|
||||
func readWithinLimit(content io.Reader) ([]byte, error) {
|
||||
|
||||
@@ -4,12 +4,17 @@ import "RenewCMS/internal/domain/image"
|
||||
|
||||
type DeleteMediaUseCase struct {
|
||||
imageRepository image.Repository
|
||||
remover mediaRemover
|
||||
}
|
||||
|
||||
func NewDeleteMediaUseCase(imageRepository image.Repository) *DeleteMediaUseCase {
|
||||
return &DeleteMediaUseCase{imageRepository}
|
||||
func NewDeleteMediaUseCase(imageRepository image.Repository, storage image.Storage) *DeleteMediaUseCase {
|
||||
return &DeleteMediaUseCase{imageRepository, mediaRemover{imageRepository, storage}}
|
||||
}
|
||||
|
||||
func (u *DeleteMediaUseCase) Delete(id uint32) error {
|
||||
return u.imageRepository.Delete(id)
|
||||
img, err := u.imageRepository.Get(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return u.remover.remove(img)
|
||||
}
|
||||
@@ -18,10 +18,11 @@ type MergeMediaResult struct {
|
||||
type MergeMediaUseCase struct {
|
||||
imageRepository image.Repository
|
||||
articleRepository article.Repository
|
||||
remover mediaRemover
|
||||
}
|
||||
|
||||
func NewMergeMediaUseCase(imageRepository image.Repository, articleRepository article.Repository) *MergeMediaUseCase {
|
||||
return &MergeMediaUseCase{imageRepository, articleRepository}
|
||||
func NewMergeMediaUseCase(imageRepository image.Repository, articleRepository article.Repository, storage image.Storage) *MergeMediaUseCase {
|
||||
return &MergeMediaUseCase{imageRepository, articleRepository, mediaRemover{imageRepository, storage}}
|
||||
}
|
||||
|
||||
type bodyRewrite struct {
|
||||
@@ -53,7 +54,7 @@ func (u *MergeMediaUseCase) Merge(keepId uint32, mergeIds []uint32) (MergeMediaR
|
||||
}
|
||||
|
||||
for _, duplicate := range copies {
|
||||
if err := u.imageRepository.Delete(duplicate.ID); err != nil {
|
||||
if err := u.remover.remove(duplicate); err != nil {
|
||||
return MergeMediaResult{}, err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package useCases
|
||||
|
||||
import "RenewCMS/internal/domain/image"
|
||||
|
||||
type OpenMediaUseCase struct {
|
||||
storage image.Storage
|
||||
}
|
||||
|
||||
func NewOpenMediaUseCase(storage image.Storage) *OpenMediaUseCase {
|
||||
return &OpenMediaUseCase{storage}
|
||||
}
|
||||
|
||||
func (u *OpenMediaUseCase) Open(storedName string) (image.Blob, error) {
|
||||
return u.storage.Open(storedName)
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package useCases
|
||||
|
||||
import "RenewCMS/internal/domain/image"
|
||||
|
||||
type mediaRemover struct {
|
||||
imageRepository image.Repository
|
||||
storage image.Storage
|
||||
}
|
||||
|
||||
func (r mediaRemover) remove(img image.Image) error {
|
||||
if err := r.storage.Delete(image.StoredName(img.Path)); err != nil {
|
||||
return err
|
||||
}
|
||||
return r.imageRepository.Delete(img.ID)
|
||||
}
|
||||
Reference in new issue
Block a user