image storage added

This commit is contained in:
nquidox 2025-10-26 19:54:34 +03:00
parent f5ca21ca68
commit 212ce0a5c4
6 changed files with 190 additions and 65 deletions

View file

@ -266,16 +266,15 @@ func (co *controller) getDistinctPrices(c *gin.Context) {
c.JSON(http.StatusOK, response)
}
// @Summary Загрузить картинки по merch_uuid и query параметрам
// @Description Загрузить картинки по merch_uuid и query параметрам
// @Summary Загрузить картинку по merch_uuid
// @Description Загрузить картинку по merch_uuid. В ответ будут выданы ссылки на созданные картинки.
// @Tags Merch images
// @Security BearerAuth
// @Accept multipart/form-data
// @Produce json
// @Param uuid path string true "Merch UUID"
// @Param file formData file true "Image file"
// @Param imageType formData string true "Image type: thumbnail, full or all" Enums(thumbnail, full, all)
// @Success 200
// @Param uuid path string true "Merch UUID"
// @Param file formData file true "Image file"
// @Success 200 {object} imageStorage.UploadMerchImageResponse
// @Failure 400 {object} responses.ErrorResponse400
// @Failure 500 {object} responses.ErrorResponse500
// @Router /merch/images/{uuid} [post]
@ -294,13 +293,14 @@ func (co *controller) uploadMerchImage(c *gin.Context) {
return
}
imageType := c.PostForm("imageType")
types := map[string]struct{}{"thumbnail": {}, "full": {}, "all": {}}
if _, allowed := types[imageType]; !allowed {
c.JSON(http.StatusBadRequest, responses.ErrorResponse400{Error: "imageType must be one of: thumbnail, full, all"})
log.WithError(err).Error("Merch | imageType must be one of: thumbnail, full, all")
return
}
//Uncomment for MinIO use
//imageType := c.PostForm("imageType")
//types := map[string]struct{}{"thumbnail": {}, "full": {}, "all": {}}
//if _, allowed := types[imageType]; !allowed {
// c.JSON(http.StatusBadRequest, responses.ErrorResponse400{Error: "imageType must be one of: thumbnail, full, all"})
// log.WithError(err).Error("Merch | imageType must be one of: thumbnail, full, all")
// return
//}
file, err := c.FormFile("file")
if err != nil {
@ -312,14 +312,17 @@ func (co *controller) uploadMerchImage(c *gin.Context) {
ctx, cancel := context.WithTimeout(c.Request.Context(), co.expires)
defer cancel()
err = co.service.uploadMerchImage(ctx, userUuid, merchUuid, imageType, file)
//Uncomment for MinIO use
//err = co.service.uploadMerchImage(ctx, userUuid, merchUuid, imageType, file)
response, err := co.service.mtUploadMerchImage(ctx, userUuid, merchUuid, file)
if err != nil {
c.JSON(http.StatusInternalServerError, responses.ErrorResponse500{Error: err.Error()})
log.WithError(err).Error("Merch | Failed to upload merch image")
return
}
c.Status(http.StatusOK)
//c.Status(http.StatusOK)
c.JSON(http.StatusOK, response)
}
// @Summary Получить картинки по merch_uuid и query параметрам
@ -333,43 +336,53 @@ func (co *controller) uploadMerchImage(c *gin.Context) {
// @Failure 500 {object} responses.ErrorResponse500
// @Router /merch/images/{uuid} [get]
func (co *controller) getMerchImage(c *gin.Context) {
typeQuery := strings.ToLower(c.Query("type"))
if typeQuery == "" {
c.JSON(http.StatusBadRequest, responses.ErrorResponse400{Error: "Image type query param is empty"})
return
}
//Uncomment for MinIO use
userUuid, err := co.utils.GetUserUuidFromContext(c)
if err != nil {
c.JSON(http.StatusInternalServerError, responses.ErrorResponse500{Error: err.Error()})
log.WithError(err).Error("Merch | Failed to get user uuid from context")
return
}
merchUuid := c.Param("uuid")
if merchUuid == "" {
c.JSON(http.StatusBadRequest, responses.ErrorResponse400{Error: "MerchUuid is empty"})
log.WithError(err).Error("Merch | Failed to get single merch")
return
}
ctx, cancel := context.WithTimeout(c.Request.Context(), co.expires)
defer cancel()
link, err := co.service.getPublicImageLink(ctx, userUuid, merchUuid, typeQuery)
if err != nil {
c.JSON(http.StatusInternalServerError, responses.ErrorResponse500{Error: err.Error()})
log.WithError(err).Error("Merch | Failed to get merch image")
return
}
c.JSON(http.StatusOK, link)
//typeQuery := strings.ToLower(c.Query("type"))
//if typeQuery == "" {
// c.JSON(http.StatusBadRequest, responses.ErrorResponse400{Error: "Image type query param is empty"})
// return
//}
//
//userUuid, err := co.utils.GetUserUuidFromContext(c)
//if err != nil {
// c.JSON(http.StatusInternalServerError, responses.ErrorResponse500{Error: err.Error()})
// log.WithError(err).Error("Merch | Failed to get user uuid from context")
// return
//}
//
//merchUuid := c.Param("uuid")
//if merchUuid == "" {
// c.JSON(http.StatusBadRequest, responses.ErrorResponse400{Error: "MerchUuid is empty"})
// log.WithError(err).Error("Merch | Failed to get single merch")
// return
//}
//
//ctx, cancel := context.WithTimeout(c.Request.Context(), co.expires)
//defer cancel()
//
//link, err := co.service.getPublicImageLink(ctx, userUuid, merchUuid, typeQuery)
//if err != nil {
// c.JSON(http.StatusInternalServerError, responses.ErrorResponse500{Error: err.Error()})
// log.WithError(err).Error("Merch | Failed to get merch image")
// return
//}
//
//if link.Link == "" {
// log.Debug("Merch | No image")
// c.Status(http.StatusNoContent)
// return
//}
//
//c.JSON(http.StatusOK, link)
c.JSON(http.StatusNotImplemented, gin.H{"msg": "Method deprecated. Request images from image storage."})
}
// @Summary Удалить (безвозвратно) картинки по merch_uuid и query параметрам
// @Description Удалить (безвозвратно) картинки по merch_uuid и query параметрам
// @Summary Удалить (безвозвратно) картинки по merch_uuid
// @Description Удалить (безвозвратно) картинки по merch_uuid
// @Tags Merch images
// @Security BearerAuth
// @Param uuid path string true "merch_uuid"
// @Param uuid path string true "merch_uuid"
// @Success 200
// @Failure 400 {object} responses.ErrorResponse400
// @Failure 500 {object} responses.ErrorResponse500
@ -385,14 +398,17 @@ func (co *controller) deleteMerchImage(c *gin.Context) {
merchUuid := c.Param("uuid")
if merchUuid == "" {
c.JSON(http.StatusBadRequest, responses.ErrorResponse400{Error: "MerchUuid is empty"})
log.WithError(err).Error("Merch | Failed to get single merch")
log.WithError(err).Error("Merch | Failed to get merch uuid")
return
}
ctx, cancel := context.WithTimeout(c.Request.Context(), co.expires)
defer cancel()
if err := co.service.deleteMerchImage(ctx, userUuid, merchUuid); err != nil {
//Uncomment for MinIO use
//if err := co.service.deleteMerchImage(ctx, userUuid, merchUuid); err != nil {
if err := co.service.mtDeleteMerchImage(ctx, userUuid, merchUuid); err != nil {
c.JSON(http.StatusInternalServerError, responses.ErrorResponse500{Error: err.Error()})
log.WithError(err).Error("Merch | Failed to delete merch image")
return

View file

@ -4,6 +4,7 @@ import (
log "github.com/sirupsen/logrus"
"gorm.io/gorm"
"merch-parser-api/internal/interfaces"
is "merch-parser-api/proto/imageStorage"
"time"
)
@ -14,9 +15,10 @@ type Handler struct {
}
type Deps struct {
DB *gorm.DB
Utils interfaces.Utils
Media interfaces.MediaStorage
DB *gorm.DB
Utils interfaces.Utils
Media interfaces.MediaStorage
ImageStorage is.ImageStorageClient
}
func NewHandler(deps Deps) *Handler {
@ -24,7 +26,13 @@ func NewHandler(deps Deps) *Handler {
expires := time.Minute * 5
r := NewRepo(deps.DB)
s := newService(r, deps.Media, packageBucketName, expires)
s := newService(serviceDeps{
repo: r,
media: deps.Media,
bucketName: packageBucketName,
expires: expires,
imageStorage: deps.ImageStorage,
})
c := newController(s, deps.Utils, expires)
media := deps.Media

View file

@ -63,6 +63,7 @@ func (r *Repo) merchRecordExists(userUuid, merchUuid string) (bool, error) {
FROM merch
WHERE user_uuid = ?
AND merch_uuid = ?
AND deleted_at IS NULL
);`, userUuid, merchUuid).Scan(&exists).Error
return exists, err

View file

@ -13,6 +13,7 @@ import (
"image/jpeg"
"io"
"merch-parser-api/internal/interfaces"
is "merch-parser-api/proto/imageStorage"
"mime/multipart"
"path/filepath"
"strings"
@ -20,18 +21,28 @@ import (
)
type service struct {
repo repository
media interfaces.MediaStorage
bucketName string
expires time.Duration
repo repository
media interfaces.MediaStorage
bucketName string
expires time.Duration
imageStorage is.ImageStorageClient
}
func newService(repo repository, media interfaces.MediaStorage, bucketName string, expires time.Duration) *service {
type serviceDeps struct {
repo repository
media interfaces.MediaStorage
bucketName string
expires time.Duration
imageStorage is.ImageStorageClient
}
func newService(deps serviceDeps) *service {
return &service{
repo: repo,
media: media,
bucketName: bucketName,
expires: expires,
repo: deps.repo,
media: deps.media,
bucketName: deps.bucketName,
expires: deps.expires,
imageStorage: deps.imageStorage,
}
}
@ -210,6 +221,9 @@ func (s *service) getDistinctPrices(userUuid, merchUuid, days string) (PricesRes
}, nil
}
// uploadMerchImage
// Deprecated.
// Use only with MinIO storage. Use mtUploadMerchImage for merch-tracker images storage.
func (s *service) uploadMerchImage(ctx context.Context, userUuid, merchUuid, imageType string, file *multipart.FileHeader) error {
exists, err := s.repo.merchRecordExists(userUuid, merchUuid)
if err != nil {
@ -303,6 +317,9 @@ func (s *service) uploadMerchImage(ctx context.Context, userUuid, merchUuid, ima
return nil
}
// getPublicImageLink
// Deprecated.
// Use only with MinIO storage.
func (s *service) getPublicImageLink(ctx context.Context, userUuid, merchUuid, imageType string) (ImageLink, error) {
object, err := s.makeObject(userUuid, merchUuid, imageType)
if err != nil {
@ -320,6 +337,9 @@ func (s *service) getPublicImageLink(ctx context.Context, userUuid, merchUuid, i
}, nil
}
// getPresignedImageLink
// Deprecated.
// Use only with MinIO storage.
func (s *service) getPresignedImageLink(ctx context.Context, userUuid, merchUuid, imageType string) (ImageLink, error) {
exists, err := s.repo.merchRecordExists(userUuid, merchUuid)
if err != nil {
@ -351,6 +371,9 @@ func (s *service) getPresignedImageLink(ctx context.Context, userUuid, merchUuid
}, nil
}
// deleteMerchImage
// Deprecated.
// Use only with MinIO storage.
func (s *service) deleteMerchImage(ctx context.Context, userUuid, merchUuid string) error {
exists, err := s.repo.merchRecordExists(userUuid, merchUuid)
if err != nil {
@ -398,3 +421,65 @@ func (s *service) _uploadToStorage(params uploadImageParams) error {
return nil
}
// mtUploadMerchImage
// Upload new/rewrite existing image to merch-tracker images storage
func (s *service) mtUploadMerchImage(ctx context.Context, userUuid, merchUuid string, file *multipart.FileHeader) (*is.UploadMerchImageResponse, error) {
const uploadMerchImage = "Merch service | Upload merch image"
exists, err := s.repo.merchRecordExists(userUuid, merchUuid)
if err != nil {
log.WithError(err).Error(uploadMerchImage)
return nil, err
}
if !exists {
err = fmt.Errorf("no merch found for user %s with uuid %s", userUuid, merchUuid)
log.WithError(err).Error(uploadMerchImage)
return nil, err
}
f, err := file.Open()
if err != nil {
log.WithError(err).Error(uploadMerchImage)
return nil, err
}
defer f.Close()
data, err := io.ReadAll(f)
if err != nil {
log.WithError(err).Error(uploadMerchImage)
return nil, err
}
response, err := s.imageStorage.UploadImage(ctx, &is.UploadMerchImageRequest{
ImageData: data,
UserUuid: userUuid,
MerchUuid: merchUuid,
})
if err != nil {
log.WithError(err).Error(uploadMerchImage)
return nil, err
}
return response, nil
}
// mtDeleteMerchImage
// Delete all merch images for given user and merch uuid-s from merch-tracker images storage
func (s *service) mtDeleteMerchImage(ctx context.Context, userUuid, merchUuid string) error {
exists, err := s.repo.merchRecordExists(userUuid, merchUuid)
if err != nil {
return err
}
if !exists {
return fmt.Errorf("no merch found for user %s with uuid %s", userUuid, merchUuid)
}
s.imageStorage.DeleteImage(ctx, &is.DeleteImageRequest{
UserUuid: userUuid,
MerchUuid: merchUuid,
})
return nil
}