image storage added
This commit is contained in:
parent
f5ca21ca68
commit
212ce0a5c4
6 changed files with 190 additions and 65 deletions
|
|
@ -9,6 +9,7 @@ import (
|
|||
"merch-parser-api/internal/api/user"
|
||||
"merch-parser-api/internal/app"
|
||||
"merch-parser-api/internal/grpcService"
|
||||
"merch-parser-api/internal/imagesProvider"
|
||||
"merch-parser-api/internal/interfaces"
|
||||
"merch-parser-api/internal/mediaStorage"
|
||||
"merch-parser-api/internal/provider/auth"
|
||||
|
|
@ -62,6 +63,8 @@ func main() {
|
|||
"provider": mediaProvider,
|
||||
}).Debug("Media storage | Minio client created")
|
||||
|
||||
imageProvider := imagesProvider.NewClient(c.ImageConf.Host + ":" + c.ImageConf.Port)
|
||||
|
||||
//deps providers
|
||||
routerHandler := router.NewRouter(router.Deps{
|
||||
ApiPrefix: c.AppConf.ApiPrefix,
|
||||
|
|
@ -94,6 +97,7 @@ func main() {
|
|||
DB: database,
|
||||
Utils: utilsProvider,
|
||||
Media: mediaProvider,
|
||||
ImageStorage: imageProvider,
|
||||
})
|
||||
|
||||
//collect modules
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ type Config struct {
|
|||
JWTConf JWTConfig
|
||||
GrpcConf GrpcConfig
|
||||
MediaConf MediaConfig
|
||||
ImageConf ImageStorageConfig
|
||||
}
|
||||
|
||||
type AppConfig struct {
|
||||
|
|
@ -48,6 +49,11 @@ type MediaConfig struct {
|
|||
Secure string
|
||||
}
|
||||
|
||||
type ImageStorageConfig struct {
|
||||
Host string
|
||||
Port string
|
||||
}
|
||||
|
||||
func NewConfig() *Config {
|
||||
return &Config{
|
||||
AppConf: AppConfig{
|
||||
|
|
@ -87,5 +93,10 @@ func NewConfig() *Config {
|
|||
Password: getEnv("MEDIA_STORAGE_PASSWORD", ""),
|
||||
Secure: getEnv("MEDIA_STORAGE_SECURE", ""),
|
||||
},
|
||||
|
||||
ImageConf: ImageStorageConfig{
|
||||
Host: getEnv("IMAGE_STORAGE_HOST", ""),
|
||||
Port: getEnv("IMAGE_STORAGE_PORT", ""),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
// @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,40 +336,50 @@ 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"
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
|
||||
|
|
@ -17,6 +18,7 @@ type Deps struct {
|
|||
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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import (
|
|||
"image/jpeg"
|
||||
"io"
|
||||
"merch-parser-api/internal/interfaces"
|
||||
is "merch-parser-api/proto/imageStorage"
|
||||
"mime/multipart"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
|
@ -24,14 +25,24 @@ type service struct {
|
|||
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
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue