feat: update image upload and preview for cms modules
This commit is contained in:
parent
706b3a4585
commit
9c51a0fb7b
|
|
@ -2,20 +2,22 @@ package service
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"mime"
|
||||
"mime/multipart"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"web-qudo-be/app/database/entity"
|
||||
"web-qudo-be/app/module/about_us_content_images/repository"
|
||||
minioStorage "web-qudo-be/config/config"
|
||||
"web-qudo-be/utils/storage"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
fileUtil "web-qudo-be/utils/file"
|
||||
)
|
||||
|
||||
type aboutUsContentImageService struct {
|
||||
Repo repository.AboutUsContentImageRepository
|
||||
MinioStorage *minioStorage.MinioStorage
|
||||
Log zerolog.Logger
|
||||
}
|
||||
|
||||
|
|
@ -29,10 +31,12 @@ type AboutUsContentImageService interface {
|
|||
|
||||
func NewAboutUsContentImageService(
|
||||
repo repository.AboutUsContentImageRepository,
|
||||
minio *minioStorage.MinioStorage,
|
||||
log zerolog.Logger,
|
||||
) AboutUsContentImageService {
|
||||
return &aboutUsContentImageService{
|
||||
Repo: repo,
|
||||
MinioStorage: minio,
|
||||
Log: log,
|
||||
}
|
||||
}
|
||||
|
|
@ -50,34 +54,28 @@ func (_i *aboutUsContentImageService) Save(aboutUsContentId uint, file *multipar
|
|||
_i.Log.Info().
|
||||
Uint("aboutUsContentId", aboutUsContentId).
|
||||
Str("filename", file.Filename).
|
||||
Msg("upload image")
|
||||
Msg("upload about us media")
|
||||
|
||||
ext := filepath.Ext(strings.ToLower(file.Filename))
|
||||
if ext != ".jpg" && ext != ".jpeg" && ext != ".png" && ext != ".mp4" && ext != ".webm" {
|
||||
return nil, fmt.Errorf("invalid file type")
|
||||
}
|
||||
|
||||
// generate filename
|
||||
filename := fmt.Sprintf("about_us_%d_%d%s", aboutUsContentId, time.Now().Unix(), ext)
|
||||
|
||||
filePath := fmt.Sprintf("./uploads/%s", filename)
|
||||
|
||||
// save file
|
||||
if err := fileUtil.SaveFile(file, filePath); err != nil {
|
||||
_i.Log.Error().Err(err).Msg("failed save file")
|
||||
key, url, err := storage.UploadCMSObject(_i.MinioStorage, "about-us", file, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// save ke DB
|
||||
mt := "image/" + strings.TrimPrefix(ext, ".")
|
||||
ext := strings.ToLower(filepath.Ext(file.Filename))
|
||||
mt := mime.TypeByExtension(ext)
|
||||
if mt == "" {
|
||||
if ext == ".mp4" || ext == ".webm" {
|
||||
mt = "video/" + strings.TrimPrefix(ext, ".")
|
||||
} else {
|
||||
mt = "application/octet-stream"
|
||||
}
|
||||
}
|
||||
|
||||
data := &entity.AboutUsContentImage{
|
||||
AboutUsContentID: aboutUsContentId,
|
||||
MediaPath: filePath,
|
||||
MediaPath: key,
|
||||
MediaType: mt,
|
||||
MediaURL: "/uploads/" + filename,
|
||||
MediaURL: url,
|
||||
}
|
||||
|
||||
result, err := _i.Repo.Create(data)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
package cms_media
|
||||
|
||||
import (
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"go.uber.org/fx"
|
||||
|
||||
"web-qudo-be/app/module/cms_media/controller"
|
||||
"web-qudo-be/app/module/cms_media/service"
|
||||
)
|
||||
|
||||
type CmsMediaRouter struct {
|
||||
App *fiber.App
|
||||
Ctrl *controller.CmsMediaController
|
||||
}
|
||||
|
||||
var NewCmsMediaModule = fx.Options(
|
||||
fx.Provide(service.NewCmsMediaService),
|
||||
fx.Provide(controller.NewCmsMediaController),
|
||||
fx.Provide(NewCmsMediaRouter),
|
||||
)
|
||||
|
||||
func NewCmsMediaRouter(app *fiber.App, ctrl *controller.CmsMediaController) *CmsMediaRouter {
|
||||
return &CmsMediaRouter{App: app, Ctrl: ctrl}
|
||||
}
|
||||
|
||||
func (r *CmsMediaRouter) RegisterCmsMediaRoutes() {
|
||||
r.App.Route("/cms-media", func(router fiber.Router) {
|
||||
router.Get("/viewer/*", r.Ctrl.Viewer)
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package controller
|
||||
|
||||
import (
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
"web-qudo-be/app/module/cms_media/service"
|
||||
)
|
||||
|
||||
type CmsMediaController struct {
|
||||
svc *service.CmsMediaService
|
||||
Log zerolog.Logger
|
||||
}
|
||||
|
||||
func NewCmsMediaController(svc *service.CmsMediaService, log zerolog.Logger) *CmsMediaController {
|
||||
return &CmsMediaController{svc: svc, Log: log}
|
||||
}
|
||||
|
||||
// Viewer streams CMS media from MinIO via API URL (for img/video src).
|
||||
// @Router /cms-media/viewer/{path} [get]
|
||||
func (ctrl *CmsMediaController) Viewer(c *fiber.Ctx) error {
|
||||
return ctrl.svc.Viewer(c)
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"mime"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/minio/minio-go/v7"
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
minioStorage "web-qudo-be/config/config"
|
||||
)
|
||||
|
||||
type CmsMediaService struct {
|
||||
Minio *minioStorage.MinioStorage
|
||||
Log zerolog.Logger
|
||||
}
|
||||
|
||||
func NewCmsMediaService(minio *minioStorage.MinioStorage, log zerolog.Logger) *CmsMediaService {
|
||||
return &CmsMediaService{Minio: minio, Log: log}
|
||||
}
|
||||
|
||||
// Viewer streams a CMS object from MinIO (same idea as article-files viewer).
|
||||
func (s *CmsMediaService) Viewer(c *fiber.Ctx) error {
|
||||
objectKey := strings.TrimSpace(c.Params("*"))
|
||||
objectKey = strings.TrimPrefix(objectKey, "/")
|
||||
if objectKey == "" {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
"success": false,
|
||||
"messages": []string{"object key required"},
|
||||
})
|
||||
}
|
||||
if !strings.HasPrefix(objectKey, "cms/") || strings.Contains(objectKey, "..") {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
"success": false,
|
||||
"messages": []string{"invalid object key"},
|
||||
})
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
bucket := s.Minio.Cfg.ObjectStorage.MinioStorage.BucketName
|
||||
|
||||
client, err := s.Minio.ConnectMinio()
|
||||
if err != nil {
|
||||
s.Log.Error().Err(err).Msg("cms media viewer: minio connect")
|
||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
|
||||
"success": false,
|
||||
"messages": []string{"storage unavailable"},
|
||||
})
|
||||
}
|
||||
|
||||
obj, err := client.GetObject(ctx, bucket, objectKey, minio.GetObjectOptions{})
|
||||
if err != nil {
|
||||
s.Log.Error().Err(err).Str("key", objectKey).Msg("cms media viewer: get object")
|
||||
return c.Status(fiber.StatusNotFound).SendString("not found")
|
||||
}
|
||||
defer obj.Close()
|
||||
|
||||
ext := strings.ToLower(filepath.Ext(objectKey))
|
||||
contentType := mime.TypeByExtension(ext)
|
||||
if contentType == "" {
|
||||
contentType = "application/octet-stream"
|
||||
}
|
||||
c.Set("Content-Type", contentType)
|
||||
c.Set("Cache-Control", "public, max-age=86400")
|
||||
|
||||
if _, err := io.Copy(c.Response().BodyWriter(), obj); err != nil {
|
||||
s.Log.Error().Err(err).Msg("cms media viewer: stream")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -84,6 +84,21 @@ func (_i *heroContentImagesController) Update(c *fiber.Ctx) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if strings.HasPrefix(c.Get("Content-Type"), "multipart/form-data") {
|
||||
file, err := c.FormFile("file")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := _i.service.UpdateWithFile(id, file); err != nil {
|
||||
_i.Log.Error().Err(err).Msg("failed update hero content image (upload)")
|
||||
return err
|
||||
}
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"Hero content image updated"},
|
||||
})
|
||||
}
|
||||
|
||||
req := new(request.HeroContentImageUpdateRequest)
|
||||
if err := utilVal.ParseAndValidate(c, req); err != nil {
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package controller
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/google/uuid"
|
||||
"github.com/rs/zerolog"
|
||||
|
|
@ -53,6 +55,28 @@ func (_i *ourProductContentImagesController) FindByOurProductContentID(c *fiber.
|
|||
}
|
||||
|
||||
func (_i *ourProductContentImagesController) Save(c *fiber.Ctx) error {
|
||||
if strings.HasPrefix(c.Get("Content-Type"), "multipart/form-data") {
|
||||
cidStr := c.FormValue("our_product_content_id")
|
||||
cid, err := uuid.Parse(cidStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
file, err := c.FormFile("file")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result, err := _i.service.SaveWithFile(cid, file)
|
||||
if err != nil {
|
||||
_i.Log.Error().Err(err).Msg("failed create our product content image (upload)")
|
||||
return err
|
||||
}
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"Our product content image created"},
|
||||
Data: result,
|
||||
})
|
||||
}
|
||||
|
||||
req := new(request.OurProductContentImageCreateRequest)
|
||||
|
||||
if err := utilVal.ParseAndValidate(c, req); err != nil {
|
||||
|
|
@ -82,6 +106,21 @@ func (_i *ourProductContentImagesController) Update(c *fiber.Ctx) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if strings.HasPrefix(c.Get("Content-Type"), "multipart/form-data") {
|
||||
file, err := c.FormFile("file")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := _i.service.UpdateWithFile(id, file); err != nil {
|
||||
_i.Log.Error().Err(err).Msg("failed update our product content image (upload)")
|
||||
return err
|
||||
}
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"Our product content image updated"},
|
||||
})
|
||||
}
|
||||
|
||||
req := new(request.OurProductContentImageUpdateRequest)
|
||||
if err := utilVal.ParseAndValidate(c, req); err != nil {
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -1,31 +1,40 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"mime/multipart"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
"web-qudo-be/app/database/entity"
|
||||
"web-qudo-be/app/module/our_product_content_images/repository"
|
||||
minioStorage "web-qudo-be/config/config"
|
||||
"web-qudo-be/utils/storage"
|
||||
)
|
||||
|
||||
type ourProductContentImagesService struct {
|
||||
Repo repository.OurProductContentImagesRepository
|
||||
MinioStorage *minioStorage.MinioStorage
|
||||
Log zerolog.Logger
|
||||
}
|
||||
|
||||
type OurProductContentImagesService interface {
|
||||
FindByContentID(contentID uuid.UUID) ([]entity.OurProductContentImage, error)
|
||||
Save(data *entity.OurProductContentImage) (*entity.OurProductContentImage, error)
|
||||
SaveWithFile(ourProductContentID uuid.UUID, file *multipart.FileHeader) (*entity.OurProductContentImage, error)
|
||||
Update(id uuid.UUID, data *entity.OurProductContentImage) error
|
||||
UpdateWithFile(id uuid.UUID, file *multipart.FileHeader) error
|
||||
Delete(id uuid.UUID) error
|
||||
}
|
||||
|
||||
func NewOurProductContentImagesService(
|
||||
repo repository.OurProductContentImagesRepository,
|
||||
minio *minioStorage.MinioStorage,
|
||||
log zerolog.Logger,
|
||||
) OurProductContentImagesService {
|
||||
return &ourProductContentImagesService{
|
||||
Repo: repo,
|
||||
MinioStorage: minio,
|
||||
Log: log,
|
||||
}
|
||||
}
|
||||
|
|
@ -52,6 +61,19 @@ func (s *ourProductContentImagesService) Save(data *entity.OurProductContentImag
|
|||
return result, nil
|
||||
}
|
||||
|
||||
func (s *ourProductContentImagesService) SaveWithFile(ourProductContentID uuid.UUID, file *multipart.FileHeader) (*entity.OurProductContentImage, error) {
|
||||
key, url, err := storage.UploadCMSObject(s.MinioStorage, "our-products", file, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := &entity.OurProductContentImage{
|
||||
OurProductContentID: ourProductContentID,
|
||||
ImagePath: key,
|
||||
ImageURL: url,
|
||||
}
|
||||
return s.Save(data)
|
||||
}
|
||||
|
||||
func (s *ourProductContentImagesService) Update(id uuid.UUID, data *entity.OurProductContentImage) error {
|
||||
err := s.Repo.Update(id, data)
|
||||
if err != nil {
|
||||
|
|
@ -62,6 +84,17 @@ func (s *ourProductContentImagesService) Update(id uuid.UUID, data *entity.OurPr
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *ourProductContentImagesService) UpdateWithFile(id uuid.UUID, file *multipart.FileHeader) error {
|
||||
key, url, err := storage.UploadCMSObject(s.MinioStorage, "our-products", file, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.Repo.Update(id, &entity.OurProductContentImage{
|
||||
ImagePath: key,
|
||||
ImageURL: url,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *ourProductContentImagesService) Delete(id uuid.UUID) error {
|
||||
err := s.Repo.Delete(id)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package controller
|
|||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/rs/zerolog"
|
||||
|
|
@ -56,6 +57,29 @@ func (_i *ourServiceContentImagesController) FindByOurServiceContentID(c *fiber.
|
|||
}
|
||||
|
||||
func (_i *ourServiceContentImagesController) Save(c *fiber.Ctx) error {
|
||||
if strings.HasPrefix(c.Get("Content-Type"), "multipart/form-data") {
|
||||
cidStr := c.FormValue("our_service_content_id")
|
||||
cidInt, err := strconv.Atoi(cidStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cid := uint(cidInt)
|
||||
file, err := c.FormFile("file")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result, err := _i.service.SaveWithFile(cid, file)
|
||||
if err != nil {
|
||||
_i.Log.Error().Err(err).Msg("failed create our service content image (upload)")
|
||||
return err
|
||||
}
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"Our service content image created"},
|
||||
Data: result,
|
||||
})
|
||||
}
|
||||
|
||||
req := new(request.OurServiceContentImageCreateRequest)
|
||||
|
||||
if err := utilVal.ParseAndValidate(c, req); err != nil {
|
||||
|
|
@ -87,6 +111,21 @@ func (_i *ourServiceContentImagesController) Update(c *fiber.Ctx) error {
|
|||
|
||||
id := uint(idInt)
|
||||
|
||||
if strings.HasPrefix(c.Get("Content-Type"), "multipart/form-data") {
|
||||
file, err := c.FormFile("file")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := _i.service.UpdateWithFile(id, file); err != nil {
|
||||
_i.Log.Error().Err(err).Msg("failed update our service content image (upload)")
|
||||
return err
|
||||
}
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"Our service content image updated"},
|
||||
})
|
||||
}
|
||||
|
||||
req := new(request.OurServiceContentImageUpdateRequest)
|
||||
if err := utilVal.ParseAndValidate(c, req); err != nil {
|
||||
return err
|
||||
|
|
|
|||
|
|
@ -1,30 +1,39 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"mime/multipart"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
"web-qudo-be/app/database/entity"
|
||||
"web-qudo-be/app/module/our_service_content_images/repository"
|
||||
minioStorage "web-qudo-be/config/config"
|
||||
"web-qudo-be/utils/storage"
|
||||
)
|
||||
|
||||
type ourServiceContentImagesService struct {
|
||||
Repo repository.OurServiceContentImagesRepository
|
||||
MinioStorage *minioStorage.MinioStorage
|
||||
Log zerolog.Logger
|
||||
}
|
||||
|
||||
type OurServiceContentImagesService interface {
|
||||
FindByContentID(contentID uint) ([]entity.OurServiceContentImage, error)
|
||||
Save(data *entity.OurServiceContentImage) (*entity.OurServiceContentImage, error)
|
||||
SaveWithFile(ourServiceContentID uint, file *multipart.FileHeader) (*entity.OurServiceContentImage, error)
|
||||
Update(id uint, data *entity.OurServiceContentImage) error
|
||||
UpdateWithFile(id uint, file *multipart.FileHeader) error
|
||||
Delete(id uint) error
|
||||
}
|
||||
|
||||
func NewOurServiceContentImagesService(
|
||||
repo repository.OurServiceContentImagesRepository,
|
||||
minio *minioStorage.MinioStorage,
|
||||
log zerolog.Logger,
|
||||
) OurServiceContentImagesService {
|
||||
return &ourServiceContentImagesService{
|
||||
Repo: repo,
|
||||
MinioStorage: minio,
|
||||
Log: log,
|
||||
}
|
||||
}
|
||||
|
|
@ -49,6 +58,19 @@ func (s *ourServiceContentImagesService) Save(data *entity.OurServiceContentImag
|
|||
return result, nil
|
||||
}
|
||||
|
||||
func (s *ourServiceContentImagesService) SaveWithFile(ourServiceContentID uint, file *multipart.FileHeader) (*entity.OurServiceContentImage, error) {
|
||||
key, url, err := storage.UploadCMSObject(s.MinioStorage, "our-services", file, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := &entity.OurServiceContentImage{
|
||||
OurServiceContentID: ourServiceContentID,
|
||||
ImagePath: key,
|
||||
ImageURL: url,
|
||||
}
|
||||
return s.Save(data)
|
||||
}
|
||||
|
||||
func (s *ourServiceContentImagesService) Update(id uint, data *entity.OurServiceContentImage) error {
|
||||
err := s.Repo.Update(id, data)
|
||||
if err != nil {
|
||||
|
|
@ -59,6 +81,17 @@ func (s *ourServiceContentImagesService) Update(id uint, data *entity.OurService
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *ourServiceContentImagesService) UpdateWithFile(id uint, file *multipart.FileHeader) error {
|
||||
key, url, err := storage.UploadCMSObject(s.MinioStorage, "our-services", file, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.Repo.Update(id, &entity.OurServiceContentImage{
|
||||
ImagePath: key,
|
||||
ImageURL: url,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *ourServiceContentImagesService) Delete(id uint) error {
|
||||
err := s.Repo.Delete(id)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ type PartnerContentController interface {
|
|||
Show(c *fiber.Ctx) error
|
||||
Save(c *fiber.Ctx) error
|
||||
Update(c *fiber.Ctx) error
|
||||
UploadLogo(c *fiber.Ctx) error
|
||||
Delete(c *fiber.Ctx) error
|
||||
}
|
||||
|
||||
|
|
@ -94,6 +95,26 @@ func (_i *partnerContentController) Update(c *fiber.Ctx) error {
|
|||
})
|
||||
}
|
||||
|
||||
func (_i *partnerContentController) UploadLogo(c *fiber.Ctx) error {
|
||||
idStr := c.Params("id")
|
||||
id, err := uuid.Parse(idStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
file, err := c.FormFile("file")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := _i.service.UploadLogo(id, file); err != nil {
|
||||
_i.Log.Error().Err(err).Msg("failed upload partner logo")
|
||||
return err
|
||||
}
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"Partner logo uploaded"},
|
||||
})
|
||||
}
|
||||
|
||||
func (_i *partnerContentController) Delete(c *fiber.Ctx) error {
|
||||
idStr := c.Params("id")
|
||||
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ func (_i *PartnerContentsRouter) RegisterPartnerContentsRoutes() {
|
|||
_i.App.Route("/partner-contents", func(router fiber.Router) {
|
||||
router.Get("/", partnerController.Show)
|
||||
router.Post("/", partnerController.Save)
|
||||
router.Post("/:id/logo", partnerController.UploadLogo)
|
||||
router.Put("/:id", partnerController.Update)
|
||||
router.Delete("/:id", partnerController.Delete)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ type PartnerContentRepository interface {
|
|||
Get() ([]entity.PartnerContent, error)
|
||||
Create(data *entity.PartnerContent) (*entity.PartnerContent, error)
|
||||
Update(id uuid.UUID, data *entity.PartnerContent) error
|
||||
UpdateImageFields(id uuid.UUID, imagePath, imageURL string) error
|
||||
Delete(id uuid.UUID) error
|
||||
FindByID(id uuid.UUID) (*entity.PartnerContent, error) // opsional (buat soft delete)
|
||||
}
|
||||
|
|
@ -68,6 +69,21 @@ func (r *partnerContentRepository) Update(id uuid.UUID, data *entity.PartnerCont
|
|||
return nil
|
||||
}
|
||||
|
||||
func (r *partnerContentRepository) UpdateImageFields(id uuid.UUID, imagePath, imageURL string) error {
|
||||
err := r.DB.DB.
|
||||
Model(&entity.PartnerContent{}).
|
||||
Where("id = ?", id).
|
||||
Updates(map[string]interface{}{
|
||||
"image_path": imagePath,
|
||||
"image_url": imageURL,
|
||||
}).Error
|
||||
if err != nil {
|
||||
r.Log.Error().Err(err).Msg("failed update partner logo")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *partnerContentRepository) Delete(id uuid.UUID) error {
|
||||
err := r.DB.DB.Delete(&entity.PartnerContent{}, id).Error
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -1,15 +1,20 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"mime/multipart"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
"web-qudo-be/app/database/entity"
|
||||
"web-qudo-be/app/module/partner_contents/repository"
|
||||
minioStorage "web-qudo-be/config/config"
|
||||
"web-qudo-be/utils/storage"
|
||||
)
|
||||
|
||||
type partnerContentService struct {
|
||||
Repo repository.PartnerContentRepository
|
||||
MinioStorage *minioStorage.MinioStorage
|
||||
Log zerolog.Logger
|
||||
}
|
||||
|
||||
|
|
@ -17,15 +22,18 @@ type PartnerContentService interface {
|
|||
Show() ([]entity.PartnerContent, error)
|
||||
Save(data *entity.PartnerContent) (*entity.PartnerContent, error)
|
||||
Update(id uuid.UUID, data *entity.PartnerContent) error
|
||||
UploadLogo(id uuid.UUID, file *multipart.FileHeader) error
|
||||
Delete(id uuid.UUID) error
|
||||
}
|
||||
|
||||
func NewPartnerContentService(
|
||||
repo repository.PartnerContentRepository,
|
||||
minio *minioStorage.MinioStorage,
|
||||
log zerolog.Logger,
|
||||
) PartnerContentService {
|
||||
return &partnerContentService{
|
||||
Repo: repo,
|
||||
MinioStorage: minio,
|
||||
Log: log,
|
||||
}
|
||||
}
|
||||
|
|
@ -62,6 +70,14 @@ func (s *partnerContentService) Update(id uuid.UUID, data *entity.PartnerContent
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *partnerContentService) UploadLogo(id uuid.UUID, file *multipart.FileHeader) error {
|
||||
key, url, err := storage.UploadCMSObject(s.MinioStorage, "partners", file, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.Repo.UpdateImageFields(id, key, url)
|
||||
}
|
||||
|
||||
func (s *partnerContentService) Delete(id uuid.UUID) error {
|
||||
err := s.Repo.Delete(id)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package controller
|
|||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
|
||||
|
|
@ -39,6 +40,32 @@ func NewPopupNewsContentImagesController(s service.PopupNewsContentImagesService
|
|||
// @Failure 500 {object} response.Response
|
||||
// @Router /popup-news-content-images [post]
|
||||
func (_i *popupNewsContentImagesController) Save(c *fiber.Ctx) error {
|
||||
if strings.HasPrefix(c.Get("Content-Type"), "multipart/form-data") {
|
||||
cidStr := c.FormValue("popup_news_content_id")
|
||||
cid64, err := strconv.ParseUint(cidStr, 10, 32)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
file, err := c.FormFile("file")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var isThumb *bool
|
||||
if v := strings.TrimSpace(c.FormValue("is_thumbnail")); v != "" {
|
||||
b := v == "true" || v == "1" || v == "on"
|
||||
isThumb = &b
|
||||
}
|
||||
result, err := _i.service.SaveWithFile(uint(cid64), file, isThumb)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"Popup news content image successfully uploaded"},
|
||||
Data: result,
|
||||
})
|
||||
}
|
||||
|
||||
req := new(request.PopupNewsContentImagesCreateRequest)
|
||||
if err := utilVal.ParseAndValidate(c, req); err != nil {
|
||||
return err
|
||||
|
|
@ -50,6 +77,7 @@ func (_i *popupNewsContentImagesController) Save(c *fiber.Ctx) error {
|
|||
}
|
||||
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"Popup news content image successfully uploaded"},
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,40 +1,76 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"mime/multipart"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
"web-qudo-be/app/database/entity"
|
||||
"web-qudo-be/app/module/popup_news_content_images/repository"
|
||||
"web-qudo-be/app/module/popup_news_content_images/request"
|
||||
minioStorage "web-qudo-be/config/config"
|
||||
"web-qudo-be/utils/storage"
|
||||
)
|
||||
|
||||
// service struct
|
||||
type popupNewsContentImagesService struct {
|
||||
Repo repository.PopupNewsContentImagesRepository
|
||||
MinioStorage *minioStorage.MinioStorage
|
||||
Log zerolog.Logger
|
||||
}
|
||||
|
||||
// interface
|
||||
type PopupNewsContentImagesService interface {
|
||||
Save(req request.PopupNewsContentImagesCreateRequest) error
|
||||
SaveWithFile(popupNewsContentID uint, file *multipart.FileHeader, isThumbnail *bool) (*entity.PopupNewsContentImages, error)
|
||||
Delete(id uint) error
|
||||
}
|
||||
|
||||
// constructor
|
||||
func NewPopupNewsContentImagesService(repo repository.PopupNewsContentImagesRepository, log zerolog.Logger) PopupNewsContentImagesService {
|
||||
func NewPopupNewsContentImagesService(
|
||||
repo repository.PopupNewsContentImagesRepository,
|
||||
minio *minioStorage.MinioStorage,
|
||||
log zerolog.Logger,
|
||||
) PopupNewsContentImagesService {
|
||||
return &popupNewsContentImagesService{
|
||||
Repo: repo,
|
||||
MinioStorage: minio,
|
||||
Log: log,
|
||||
}
|
||||
}
|
||||
|
||||
// Save
|
||||
func (_i *popupNewsContentImagesService) Save(req request.PopupNewsContentImagesCreateRequest) error {
|
||||
_i.Log.Info().Interface("data", req).Msg("upload popup news content image")
|
||||
_i.Log.Info().Interface("data", req).Msg("create popup news content image (json)")
|
||||
|
||||
return _i.Repo.Create(req.ToEntity())
|
||||
}
|
||||
|
||||
// Delete
|
||||
func (_i *popupNewsContentImagesService) SaveWithFile(popupNewsContentID uint, file *multipart.FileHeader, isThumbnail *bool) (*entity.PopupNewsContentImages, error) {
|
||||
_i.Log.Info().
|
||||
Uint("popup_news_content_id", popupNewsContentID).
|
||||
Str("filename", file.Filename).
|
||||
Msg("upload popup news content image")
|
||||
|
||||
key, url, err := storage.UploadCMSObject(_i.MinioStorage, "popup-news", file, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if isThumbnail != nil && *isThumbnail {
|
||||
if err := _i.Repo.ResetThumbnail(popupNewsContentID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
row := &entity.PopupNewsContentImages{
|
||||
PopupNewsContentID: popupNewsContentID,
|
||||
MediaPath: key,
|
||||
MediaURL: url,
|
||||
IsThumbnail: isThumbnail,
|
||||
}
|
||||
if err := _i.Repo.Create(row); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return row, nil
|
||||
}
|
||||
|
||||
func (_i *popupNewsContentImagesService) Delete(id uint) error {
|
||||
return _i.Repo.Delete(id)
|
||||
}
|
||||
|
|
@ -20,6 +20,7 @@ import (
|
|||
"web-qudo-be/app/module/cities"
|
||||
"web-qudo-be/app/module/client_approval_settings"
|
||||
"web-qudo-be/app/module/clients"
|
||||
"web-qudo-be/app/module/cms_media"
|
||||
"web-qudo-be/app/module/custom_static_pages"
|
||||
"web-qudo-be/app/module/districts"
|
||||
"web-qudo-be/app/module/feedbacks"
|
||||
|
|
@ -72,6 +73,7 @@ type Router struct {
|
|||
CitiesRouter *cities.CitiesRouter
|
||||
ClientApprovalSettingsRouter *client_approval_settings.ClientApprovalSettingsRouter
|
||||
ClientsRouter *clients.ClientsRouter
|
||||
CmsMediaRouter *cms_media.CmsMediaRouter
|
||||
HeroContentsRouter *hero_content.HeroContentsRouter
|
||||
HeroContentImagesRouter *hero_content_image.HeroContentImagesRouter
|
||||
CustomStaticPagesRouter *custom_static_pages.CustomStaticPagesRouter
|
||||
|
|
@ -119,6 +121,7 @@ func NewRouter(
|
|||
citiesRouter *cities.CitiesRouter,
|
||||
clientApprovalSettingsRouter *client_approval_settings.ClientApprovalSettingsRouter,
|
||||
clientsRouter *clients.ClientsRouter,
|
||||
cmsMediaRouter *cms_media.CmsMediaRouter,
|
||||
heroContentsRouter *hero_content.HeroContentsRouter,
|
||||
heroContentImagesRouter *hero_content_image.HeroContentImagesRouter,
|
||||
customStaticPagesRouter *custom_static_pages.CustomStaticPagesRouter,
|
||||
|
|
@ -165,6 +168,7 @@ func NewRouter(
|
|||
CitiesRouter: citiesRouter,
|
||||
ClientApprovalSettingsRouter: clientApprovalSettingsRouter,
|
||||
ClientsRouter: clientsRouter,
|
||||
CmsMediaRouter: cmsMediaRouter,
|
||||
HeroContentsRouter: heroContentsRouter,
|
||||
HeroContentImagesRouter: heroContentImagesRouter,
|
||||
CustomStaticPagesRouter: customStaticPagesRouter,
|
||||
|
|
@ -221,6 +225,7 @@ func (r *Router) Register() {
|
|||
r.CitiesRouter.RegisterCitiesRoutes()
|
||||
r.ClientApprovalSettingsRouter.RegisterClientApprovalSettingsRoutes()
|
||||
r.ClientsRouter.RegisterClientsRoutes()
|
||||
r.CmsMediaRouter.RegisterCmsMediaRoutes()
|
||||
r.HeroContentsRouter.RegisterHeroContentsRoutes()
|
||||
r.HeroContentImagesRouter.RegisterHeroContentImagesRoutes()
|
||||
r.CustomStaticPagesRouter.RegisterCustomStaticPagesRoutes()
|
||||
|
|
|
|||
|
|
@ -128,6 +128,23 @@ type Config struct {
|
|||
Smtp smtp
|
||||
}
|
||||
|
||||
// APIPublicBaseURL is the base URL embedded in links returned to clients (e.g. CMS image preview).
|
||||
// If app.production is true, it uses app.domain (trimmed). Otherwise it uses http://localhost plus app.port
|
||||
// so local runs match opening the API in the browser without rewriting https://qudo.id/api.
|
||||
func (c *Config) APIPublicBaseURL() string {
|
||||
if c.App.Production {
|
||||
return strings.TrimSuffix(c.App.Domain, "/")
|
||||
}
|
||||
port := strings.TrimSpace(c.App.Port)
|
||||
if port == "" {
|
||||
return "http://localhost"
|
||||
}
|
||||
if !strings.HasPrefix(port, ":") {
|
||||
port = ":" + port
|
||||
}
|
||||
return "http://localhost" + port
|
||||
}
|
||||
|
||||
// NewConfig : initialize config
|
||||
func NewConfig() *Config {
|
||||
config, err := ParseConfig("config")
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ external-port = ":8812"
|
|||
idle-timeout = 5 # As seconds
|
||||
print-routes = false
|
||||
prefork = false
|
||||
# false: CMS preview URLs use http://localhost + port above. true: use domain (e.g. https://qudo.id/api).
|
||||
production = false
|
||||
body-limit = 1048576000 # "100 * 1024 * 1024"
|
||||
|
||||
|
|
|
|||
2
main.go
2
main.go
|
|
@ -23,6 +23,7 @@ import (
|
|||
"web-qudo-be/app/module/cities"
|
||||
"web-qudo-be/app/module/client_approval_settings"
|
||||
"web-qudo-be/app/module/clients"
|
||||
"web-qudo-be/app/module/cms_media"
|
||||
"web-qudo-be/app/module/custom_static_pages"
|
||||
"web-qudo-be/app/module/districts"
|
||||
"web-qudo-be/app/module/feedbacks"
|
||||
|
|
@ -104,6 +105,7 @@ func main() {
|
|||
cities.NewCitiesModule,
|
||||
client_approval_settings.NewClientApprovalSettingsModule,
|
||||
clients.NewClientsModule,
|
||||
cms_media.NewCmsMediaModule,
|
||||
custom_static_pages.NewCustomStaticPagesModule,
|
||||
districts.NewDistrictsModule,
|
||||
feedbacks.NewFeedbacksModule,
|
||||
|
|
|
|||
|
|
@ -15,6 +15,13 @@ import (
|
|||
appcfg "web-qudo-be/config/config"
|
||||
)
|
||||
|
||||
// CMSPreviewURL is the absolute URL served by this API (GET /cms-media/viewer/...) for DB image_url / media_url fields.
|
||||
func CMSPreviewURL(cfg *appcfg.Config, objectKey string) string {
|
||||
base := cfg.APIPublicBaseURL()
|
||||
key := strings.TrimPrefix(strings.TrimSpace(objectKey), "/")
|
||||
return base + "/cms-media/viewer/" + key
|
||||
}
|
||||
|
||||
var imageExts = map[string]bool{
|
||||
".jpg": true, ".jpeg": true, ".png": true, ".gif": true, ".webp": true,
|
||||
}
|
||||
|
|
@ -24,8 +31,8 @@ var mediaExts = map[string]bool{
|
|||
".mp4": true, ".webm": true,
|
||||
}
|
||||
|
||||
// UploadCMSObject stores a file in MinIO under cms/{folder}/YYYY/MM/{uuid}{ext} and returns object key + public URL.
|
||||
func UploadCMSObject(ms *appcfg.MinioStorage, folder string, file *multipart.FileHeader, allowVideo bool) (objectKey string, publicURL string, err error) {
|
||||
// UploadCMSObject stores a file in MinIO under cms/{folder}/YYYY/MM/{uuid}{ext} and returns object key + preview URL (API viewer, not direct MinIO).
|
||||
func UploadCMSObject(ms *appcfg.MinioStorage, folder string, file *multipart.FileHeader, allowVideo bool) (objectKey string, previewURL string, err error) {
|
||||
if file == nil {
|
||||
return "", "", fmt.Errorf("file is required")
|
||||
}
|
||||
|
|
@ -65,5 +72,5 @@ func UploadCMSObject(ms *appcfg.MinioStorage, folder string, file *multipart.Fil
|
|||
return "", "", err
|
||||
}
|
||||
|
||||
return objectKey, ms.PublicObjectURL(objectKey), nil
|
||||
return objectKey, CMSPreviewURL(ms.Cfg, objectKey), nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue