feat: update major for my content, my library, and fixing content web
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
This commit is contained in:
parent
0d2fcad83e
commit
a22dda8c8b
|
|
@ -0,0 +1,26 @@
|
|||
package entity
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// CmsContentSubmission stores pending Content Website changes until an approver applies them.
|
||||
type CmsContentSubmission struct {
|
||||
ID uuid.UUID `json:"id" gorm:"type:uuid;primaryKey"`
|
||||
ClientID uuid.UUID `json:"client_id" gorm:"type:uuid;index"`
|
||||
Domain string `json:"domain" gorm:"size:32;index"`
|
||||
Title string `json:"title" gorm:"size:512"`
|
||||
Status string `json:"status" gorm:"size:24;index"` // pending | approved | rejected
|
||||
Payload string `json:"payload" gorm:"type:text"` // JSON
|
||||
SubmittedByID uint `json:"submitted_by_id" gorm:"index"`
|
||||
ReviewedByID *uint `json:"reviewed_by_id"`
|
||||
ReviewNote string `json:"review_note" gorm:"size:512"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
func (CmsContentSubmission) TableName() string {
|
||||
return "cms_content_submissions"
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package entity
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// MediaLibraryItem stores metadata for a single logical media asset (one public URL).
|
||||
// The file may also be referenced from article_files, CMS image tables, etc.
|
||||
type MediaLibraryItem struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
|
||||
PublicURL string `json:"public_url" gorm:"type:varchar(2048);not null;uniqueIndex:ux_media_library_public_url"`
|
||||
ObjectKey *string `json:"object_key" gorm:"type:varchar(1024)"`
|
||||
OriginalFilename *string `json:"original_filename" gorm:"type:varchar(512)"`
|
||||
FileCategory string `json:"file_category" gorm:"type:varchar(32);not null;default:other"` // image, video, audio, document, other
|
||||
SizeBytes *int64 `json:"size_bytes" gorm:"type:int8"`
|
||||
SourceType string `json:"source_type" gorm:"type:varchar(64);not null"` // article_file, cms, upload
|
||||
SourceLabel *string `json:"source_label" gorm:"type:varchar(255)"`
|
||||
ArticleFileID *uint `json:"article_file_id" gorm:"type:int4"`
|
||||
CreatedByID int `json:"created_by_id" gorm:"type:int4;default:0"`
|
||||
ClientID *uuid.UUID `json:"client_id" gorm:"type:UUID"`
|
||||
IsActive bool `json:"is_active" gorm:"type:bool;default:true"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"`
|
||||
}
|
||||
|
|
@ -105,6 +105,7 @@ func Models() []interface{} {
|
|||
entity.Clients{},
|
||||
entity.HeroContents{},
|
||||
entity.HeroContentImages{},
|
||||
entity.CmsContentSubmission{},
|
||||
entity.ClientApprovalSettings{},
|
||||
entity.CsrfTokenRecords{},
|
||||
entity.CustomStaticPages{},
|
||||
|
|
@ -113,6 +114,7 @@ func Models() []interface{} {
|
|||
entity.ForgotPasswords{},
|
||||
entity.Magazines{},
|
||||
entity.MagazineFiles{},
|
||||
entity.MediaLibraryItem{},
|
||||
entity.MasterMenus{},
|
||||
entity.MasterModules{},
|
||||
entity.MasterStatuses{},
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
|
||||
"web-qudo-be/app/database/entity"
|
||||
"web-qudo-be/app/module/about_us_content_images/repository"
|
||||
medialib "web-qudo-be/app/module/media_library/service"
|
||||
minioStorage "web-qudo-be/config/config"
|
||||
"web-qudo-be/utils/storage"
|
||||
|
||||
|
|
@ -18,6 +19,7 @@ import (
|
|||
type aboutUsContentImageService struct {
|
||||
Repo repository.AboutUsContentImageRepository
|
||||
MinioStorage *minioStorage.MinioStorage
|
||||
MediaLib medialib.MediaLibraryService
|
||||
Log zerolog.Logger
|
||||
}
|
||||
|
||||
|
|
@ -32,11 +34,13 @@ type AboutUsContentImageService interface {
|
|||
func NewAboutUsContentImageService(
|
||||
repo repository.AboutUsContentImageRepository,
|
||||
minio *minioStorage.MinioStorage,
|
||||
mediaLib medialib.MediaLibraryService,
|
||||
log zerolog.Logger,
|
||||
) AboutUsContentImageService {
|
||||
return &aboutUsContentImageService{
|
||||
Repo: repo,
|
||||
MinioStorage: minio,
|
||||
MediaLib: mediaLib,
|
||||
Log: log,
|
||||
}
|
||||
}
|
||||
|
|
@ -83,7 +87,9 @@ func (_i *aboutUsContentImageService) Save(aboutUsContentId uint, file *multipar
|
|||
_i.Log.Error().Err(err).Msg("failed save to DB")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _i.MediaLib != nil {
|
||||
_ = _i.MediaLib.RegisterCMSAsset(url, key, "about_us", file)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
|
|
@ -105,7 +111,11 @@ func (_i *aboutUsContentImageService) SaveRemoteURL(aboutUsContentID uint, media
|
|||
MediaURL: mediaURL,
|
||||
MediaType: mt,
|
||||
}
|
||||
return _i.Repo.Create(data)
|
||||
img, err := _i.Repo.Create(data)
|
||||
if err == nil && _i.MediaLib != nil {
|
||||
_ = _i.MediaLib.RegisterCMSAsset(mediaURL, "", "about_us_remote", nil)
|
||||
}
|
||||
return img, err
|
||||
}
|
||||
|
||||
func (_i *aboutUsContentImageService) Delete(id uint) error {
|
||||
|
|
|
|||
|
|
@ -134,7 +134,11 @@ func (_i *articleFilesController) Save(c *fiber.Ctx) error {
|
|||
return err
|
||||
}
|
||||
|
||||
err = _i.articleFilesService.Save(clientId, c, uint(id))
|
||||
uid := 0
|
||||
if u := middleware.GetUser(c); u != nil {
|
||||
uid = int(u.ID)
|
||||
}
|
||||
err = _i.articleFilesService.Save(clientId, c, uint(id), uid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import (
|
|||
"web-qudo-be/app/module/article_files/repository"
|
||||
"web-qudo-be/app/module/article_files/request"
|
||||
"web-qudo-be/app/module/article_files/response"
|
||||
medialib "web-qudo-be/app/module/media_library/service"
|
||||
config "web-qudo-be/config/config"
|
||||
"web-qudo-be/utils/paginator"
|
||||
|
||||
|
|
@ -33,13 +34,14 @@ type articleFilesService struct {
|
|||
Log zerolog.Logger
|
||||
Cfg *config.Config
|
||||
MinioStorage *config.MinioStorage
|
||||
MediaLib medialib.MediaLibraryService
|
||||
}
|
||||
|
||||
// ArticleFilesService define interface of IArticleFilesService
|
||||
type ArticleFilesService interface {
|
||||
All(clientId *uuid.UUID, req request.ArticleFilesQueryRequest) (articleFiles []*response.ArticleFilesResponse, paging paginator.Pagination, err error)
|
||||
Show(clientId *uuid.UUID, id uint) (articleFiles *response.ArticleFilesResponse, err error)
|
||||
Save(clientId *uuid.UUID, c *fiber.Ctx, id uint) error
|
||||
Save(clientId *uuid.UUID, c *fiber.Ctx, articleID uint, createdByUserID int) error
|
||||
SaveAsync(clientId *uuid.UUID, c *fiber.Ctx, id uint) error
|
||||
Update(clientId *uuid.UUID, id uint, req request.ArticleFilesUpdateRequest) (err error)
|
||||
GetUploadStatus(c *fiber.Ctx) (progress int, err error)
|
||||
|
|
@ -48,13 +50,14 @@ type ArticleFilesService interface {
|
|||
}
|
||||
|
||||
// NewArticleFilesService init ArticleFilesService
|
||||
func NewArticleFilesService(repo repository.ArticleFilesRepository, log zerolog.Logger, cfg *config.Config, minioStorage *config.MinioStorage) ArticleFilesService {
|
||||
func NewArticleFilesService(repo repository.ArticleFilesRepository, log zerolog.Logger, cfg *config.Config, minioStorage *config.MinioStorage, mediaLib medialib.MediaLibraryService) ArticleFilesService {
|
||||
|
||||
return &articleFilesService{
|
||||
Repo: repo,
|
||||
Log: log,
|
||||
Cfg: cfg,
|
||||
MinioStorage: minioStorage,
|
||||
MediaLib: mediaLib,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -195,7 +198,7 @@ func (_i *articleFilesService) SaveAsync(clientId *uuid.UUID, c *fiber.Ctx, id u
|
|||
return
|
||||
}
|
||||
|
||||
func (_i *articleFilesService) Save(clientId *uuid.UUID, c *fiber.Ctx, id uint) (err error) {
|
||||
func (_i *articleFilesService) Save(clientId *uuid.UUID, c *fiber.Ctx, articleID uint, createdByUserID int) (err error) {
|
||||
bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName
|
||||
|
||||
form, err := c.MultipartForm()
|
||||
|
|
@ -250,14 +253,19 @@ func (_i *articleFilesService) Save(clientId *uuid.UUID, c *fiber.Ctx, id uint)
|
|||
fileSize := strconv.FormatInt(fileHeader.Size, 10)
|
||||
|
||||
req := request.ArticleFilesCreateRequest{
|
||||
ArticleId: id,
|
||||
ArticleId: articleID,
|
||||
FilePath: &objectName,
|
||||
FileName: &newFilename,
|
||||
FileAlt: &filenameAlt,
|
||||
Size: &fileSize,
|
||||
}
|
||||
|
||||
err = _i.Repo.Create(clientId, req.ToEntity())
|
||||
ent := req.ToEntity()
|
||||
ent.CreatedById = createdByUserID
|
||||
if ent.StatusId == 0 {
|
||||
ent.StatusId = 1
|
||||
}
|
||||
err = _i.Repo.Create(clientId, ent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -267,6 +275,29 @@ func (_i *articleFilesService) Save(clientId *uuid.UUID, c *fiber.Ctx, id uint)
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _i.MediaLib != nil && ent.FileName != nil {
|
||||
pub := medialib.ArticleFilePublicURL(_i.Cfg, *ent.FileName)
|
||||
var sizePtr *int64
|
||||
if ent.Size != nil {
|
||||
if n, perr := strconv.ParseInt(*ent.Size, 10, 64); perr == nil {
|
||||
sizePtr = &n
|
||||
}
|
||||
}
|
||||
lbl := fmt.Sprintf("article:%d", articleID)
|
||||
_ = _i.MediaLib.UpsertRegister(medialib.RegisterInput{
|
||||
ClientID: clientId,
|
||||
UserID: createdByUserID,
|
||||
PublicURL: pub,
|
||||
ObjectKey: ent.FilePath,
|
||||
OriginalFilename: ent.FileName,
|
||||
FileCategory: medialib.CategoryFromFilename(*ent.FileName),
|
||||
SizeBytes: sizePtr,
|
||||
SourceType: "article_file",
|
||||
SourceLabel: &lbl,
|
||||
ArticleFileID: &ent.ID,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -88,6 +88,8 @@ func (_i *articlesController) All(c *fiber.Ctx) error {
|
|||
Source: c.Query("source"),
|
||||
StartDate: c.Query("startDate"),
|
||||
EndDate: c.Query("endDate"),
|
||||
CreatedById: c.Query("createdById"),
|
||||
MyContentMode: c.Query("myContentMode"),
|
||||
}
|
||||
req := reqContext.ToParamRequest()
|
||||
req.Pagination = paginate
|
||||
|
|
|
|||
|
|
@ -111,6 +111,15 @@ func (_i *articlesRepository) GetAll(clientId *uuid.UUID, userLevelId *uint, req
|
|||
query = query.Where("articles.client_id = ?", clientId)
|
||||
}
|
||||
|
||||
if req.MyContentMode != nil {
|
||||
mode := strings.ToLower(strings.TrimSpace(*req.MyContentMode))
|
||||
if mode == "approver" {
|
||||
query = query.Where("articles.is_draft = ?", false)
|
||||
query = query.Joins("JOIN users acu ON acu.id = articles.created_by_id").
|
||||
Where("acu.user_level_id = ?", 2)
|
||||
}
|
||||
}
|
||||
|
||||
if req.Title != nil && *req.Title != "" {
|
||||
title := strings.ToLower(*req.Title)
|
||||
query = query.Where("LOWER(articles.title) LIKE ?", "%"+strings.ToLower(title)+"%")
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package request
|
|||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"web-qudo-be/app/database/entity"
|
||||
"web-qudo-be/utils/paginator"
|
||||
|
|
@ -29,6 +30,8 @@ type ArticlesQueryRequest struct {
|
|||
StartDate *time.Time `json:"startDate"`
|
||||
EndDate *time.Time `json:"endDate"`
|
||||
Pagination *paginator.Pagination `json:"pagination"`
|
||||
// myContentMode: "own" = current user's articles (any level); "approver" = non-draft from contributors (level 2) for approver history
|
||||
MyContentMode *string `json:"myContentMode"`
|
||||
}
|
||||
|
||||
type ArticlesCreateRequest struct {
|
||||
|
|
@ -137,6 +140,7 @@ type ArticlesQueryRequestContext struct {
|
|||
CustomCreatorName string `json:"customCreatorName"`
|
||||
StartDate string `json:"startDate"`
|
||||
EndDate string `json:"endDate"`
|
||||
MyContentMode string `json:"myContentMode"`
|
||||
}
|
||||
|
||||
func (req ArticlesQueryRequestContext) ToParamRequest() ArticlesQueryRequest {
|
||||
|
|
@ -213,6 +217,9 @@ func (req ArticlesQueryRequestContext) ToParamRequest() ArticlesQueryRequest {
|
|||
request.EndDate = &endDate
|
||||
}
|
||||
}
|
||||
if m := strings.TrimSpace(req.MyContentMode); m != "" {
|
||||
request.MyContentMode = &m
|
||||
}
|
||||
|
||||
return request
|
||||
}
|
||||
|
|
|
|||
|
|
@ -120,27 +120,49 @@ func NewArticlesService(
|
|||
}
|
||||
}
|
||||
|
||||
const myContentApproverMinLevel = uint(3)
|
||||
|
||||
// All implement interface of ArticlesService
|
||||
func (_i *articlesService) All(clientId *uuid.UUID, authToken string, req request.ArticlesQueryRequest) (articless []*response.ArticlesResponse, paging paginator.Pagination, err error) {
|
||||
// Extract userLevelId from authToken
|
||||
var userLevelId *uint
|
||||
reqScoped := req
|
||||
|
||||
if authToken != "" {
|
||||
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
||||
if user != nil {
|
||||
if req.MyContentMode != nil {
|
||||
mode := strings.ToLower(strings.TrimSpace(*req.MyContentMode))
|
||||
switch mode {
|
||||
case "own":
|
||||
cb := int(user.ID)
|
||||
reqScoped.CreatedById = &cb
|
||||
userLevelId = nil
|
||||
_i.Log.Info().Uint("userId", user.ID).Msg("myContentMode=own: list own articles without level visibility filter")
|
||||
case "approver":
|
||||
if user.UserLevelId != myContentApproverMinLevel {
|
||||
return nil, paging, errors.New("myContentMode approver requires user level 3")
|
||||
}
|
||||
userLevelId = nil
|
||||
_i.Log.Info().Msg("myContentMode=approver: list contributor non-draft articles")
|
||||
default:
|
||||
userLevelId = &user.UserLevelId
|
||||
_i.Log.Info().Interface("userLevelId", userLevelId).Msg("Extracted userLevelId from auth token")
|
||||
}
|
||||
} else {
|
||||
userLevelId = &user.UserLevelId
|
||||
}
|
||||
_i.Log.Info().Interface("userLevelId", userLevelId).Msg("Articles.All visibility")
|
||||
}
|
||||
}
|
||||
|
||||
if req.Category != nil {
|
||||
findCategory, err := _i.ArticleCategoriesRepo.FindOneBySlug(clientId, *req.Category)
|
||||
if reqScoped.Category != nil {
|
||||
findCategory, err := _i.ArticleCategoriesRepo.FindOneBySlug(clientId, *reqScoped.Category)
|
||||
if err != nil {
|
||||
return nil, paging, err
|
||||
}
|
||||
req.CategoryId = &findCategory.ID
|
||||
reqScoped.CategoryId = &findCategory.ID
|
||||
}
|
||||
|
||||
results, paging, err := _i.Repo.GetAll(clientId, userLevelId, req)
|
||||
results, paging, err := _i.Repo.GetAll(clientId, userLevelId, reqScoped)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
package cms_content_submissions
|
||||
|
||||
import (
|
||||
"web-qudo-be/app/middleware"
|
||||
"web-qudo-be/app/module/cms_content_submissions/controller"
|
||||
"web-qudo-be/app/module/cms_content_submissions/repository"
|
||||
"web-qudo-be/app/module/cms_content_submissions/service"
|
||||
usersRepo "web-qudo-be/app/module/users/repository"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"go.uber.org/fx"
|
||||
)
|
||||
|
||||
type CmsContentSubmissionsRouter struct {
|
||||
App fiber.Router
|
||||
Controller *controller.Controller
|
||||
UsersRepo usersRepo.UsersRepository
|
||||
}
|
||||
|
||||
var NewCmsContentSubmissionsModule = fx.Options(
|
||||
fx.Provide(repository.NewCmsContentSubmissionsRepository),
|
||||
fx.Provide(service.NewCmsContentSubmissionsService),
|
||||
fx.Provide(controller.NewController),
|
||||
fx.Provide(NewCmsContentSubmissionsRouter),
|
||||
)
|
||||
|
||||
func NewCmsContentSubmissionsRouter(
|
||||
fiber *fiber.App,
|
||||
ctrl *controller.Controller,
|
||||
usersRepo usersRepo.UsersRepository,
|
||||
) *CmsContentSubmissionsRouter {
|
||||
return &CmsContentSubmissionsRouter{
|
||||
App: fiber,
|
||||
Controller: ctrl,
|
||||
UsersRepo: usersRepo,
|
||||
}
|
||||
}
|
||||
|
||||
func (_i *CmsContentSubmissionsRouter) RegisterCmsContentSubmissionsRoutes() {
|
||||
h := _i.Controller.CmsContentSubmissions
|
||||
_i.App.Route("/cms-content-submissions", func(router fiber.Router) {
|
||||
router.Use(middleware.UserMiddleware(_i.UsersRepo))
|
||||
router.Post("/", h.Submit)
|
||||
router.Get("/", h.List)
|
||||
router.Post("/:id/approve", h.Approve)
|
||||
router.Post("/:id/reject", h.Reject)
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
package controller
|
||||
|
||||
import (
|
||||
"web-qudo-be/app/middleware"
|
||||
"web-qudo-be/app/module/cms_content_submissions/request"
|
||||
"web-qudo-be/app/module/cms_content_submissions/service"
|
||||
"web-qudo-be/utils/paginator"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/google/uuid"
|
||||
|
||||
utilRes "web-qudo-be/utils/response"
|
||||
utilVal "web-qudo-be/utils/validator"
|
||||
)
|
||||
|
||||
type CmsContentSubmissionsController interface {
|
||||
Submit(c *fiber.Ctx) error
|
||||
List(c *fiber.Ctx) error
|
||||
Approve(c *fiber.Ctx) error
|
||||
Reject(c *fiber.Ctx) error
|
||||
}
|
||||
|
||||
type cmsContentSubmissionsController struct {
|
||||
svc service.CmsContentSubmissionsService
|
||||
}
|
||||
|
||||
func NewCmsContentSubmissionsController(svc service.CmsContentSubmissionsService) CmsContentSubmissionsController {
|
||||
return &cmsContentSubmissionsController{svc: svc}
|
||||
}
|
||||
|
||||
func (_i *cmsContentSubmissionsController) Submit(c *fiber.Ctx) error {
|
||||
user := middleware.GetUser(c)
|
||||
clientID := middleware.GetClientID(c)
|
||||
if user == nil || clientID == nil {
|
||||
return fiber.NewError(fiber.StatusUnauthorized, "Unauthorized")
|
||||
}
|
||||
req := new(request.SubmitCmsContentSubmissionRequest)
|
||||
if err := utilVal.ParseAndValidate(c, req); err != nil {
|
||||
return err
|
||||
}
|
||||
row, err := _i.svc.Submit(clientID, user, req)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"CMS submission saved"},
|
||||
Data: row,
|
||||
})
|
||||
}
|
||||
|
||||
func (_i *cmsContentSubmissionsController) List(c *fiber.Ctx) error {
|
||||
user := middleware.GetUser(c)
|
||||
clientID := middleware.GetClientID(c)
|
||||
if user == nil || clientID == nil {
|
||||
return fiber.NewError(fiber.StatusUnauthorized, "Unauthorized")
|
||||
}
|
||||
p, err := paginator.Paginate(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
status := c.Query("status")
|
||||
mineOnly := c.Query("mine") == "1" || c.Query("mine") == "true"
|
||||
rows, paging, err := _i.svc.List(clientID, user, status, mineOnly, p)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"CMS submissions loaded"},
|
||||
Data: rows,
|
||||
Meta: paging,
|
||||
})
|
||||
}
|
||||
|
||||
func (_i *cmsContentSubmissionsController) Approve(c *fiber.Ctx) error {
|
||||
user := middleware.GetUser(c)
|
||||
clientID := middleware.GetClientID(c)
|
||||
if user == nil || clientID == nil {
|
||||
return fiber.NewError(fiber.StatusUnauthorized, "Unauthorized")
|
||||
}
|
||||
id, err := uuid.Parse(c.Params("id"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := _i.svc.Approve(clientID, user, id); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"CMS submission approved and applied"},
|
||||
})
|
||||
}
|
||||
|
||||
func (_i *cmsContentSubmissionsController) Reject(c *fiber.Ctx) error {
|
||||
user := middleware.GetUser(c)
|
||||
clientID := middleware.GetClientID(c)
|
||||
if user == nil || clientID == nil {
|
||||
return fiber.NewError(fiber.StatusUnauthorized, "Unauthorized")
|
||||
}
|
||||
id, err := uuid.Parse(c.Params("id"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req := new(request.RejectCmsContentSubmissionRequest)
|
||||
_ = c.BodyParser(req)
|
||||
if err := _i.svc.Reject(clientID, user, id, req.Note); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||
}
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"CMS submission rejected"},
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package controller
|
||||
|
||||
import (
|
||||
"web-qudo-be/app/module/cms_content_submissions/service"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
type Controller struct {
|
||||
CmsContentSubmissions CmsContentSubmissionsController
|
||||
}
|
||||
|
||||
func NewController(
|
||||
svc service.CmsContentSubmissionsService,
|
||||
log zerolog.Logger,
|
||||
) *Controller {
|
||||
_ = log
|
||||
return &Controller{
|
||||
CmsContentSubmissions: NewCmsContentSubmissionsController(svc),
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
package repository
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"web-qudo-be/app/database"
|
||||
"web-qudo-be/app/database/entity"
|
||||
"web-qudo-be/utils/paginator"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
type CmsContentSubmissionsRepository interface {
|
||||
Create(row *entity.CmsContentSubmission) error
|
||||
FindByID(clientID uuid.UUID, id uuid.UUID) (*entity.CmsContentSubmission, error)
|
||||
List(clientID uuid.UUID, status string, submittedByID *uint, p *paginator.Pagination) ([]entity.CmsContentSubmission, *paginator.Pagination, error)
|
||||
Update(row *entity.CmsContentSubmission) error
|
||||
}
|
||||
|
||||
type cmsContentSubmissionsRepository struct {
|
||||
DB *database.Database
|
||||
Log zerolog.Logger
|
||||
}
|
||||
|
||||
func NewCmsContentSubmissionsRepository(db *database.Database, log zerolog.Logger) CmsContentSubmissionsRepository {
|
||||
return &cmsContentSubmissionsRepository{DB: db, Log: log}
|
||||
}
|
||||
|
||||
func (_i *cmsContentSubmissionsRepository) Create(row *entity.CmsContentSubmission) error {
|
||||
return _i.DB.DB.Create(row).Error
|
||||
}
|
||||
|
||||
func (_i *cmsContentSubmissionsRepository) FindByID(clientID uuid.UUID, id uuid.UUID) (*entity.CmsContentSubmission, error) {
|
||||
var row entity.CmsContentSubmission
|
||||
err := _i.DB.DB.Where("client_id = ? AND id = ?", clientID, id).First(&row).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &row, nil
|
||||
}
|
||||
|
||||
func (_i *cmsContentSubmissionsRepository) List(clientID uuid.UUID, status string, submittedByID *uint, p *paginator.Pagination) ([]entity.CmsContentSubmission, *paginator.Pagination, error) {
|
||||
var rows []entity.CmsContentSubmission
|
||||
var count int64
|
||||
|
||||
q := _i.DB.DB.Model(&entity.CmsContentSubmission{}).Where("client_id = ?", clientID)
|
||||
st := strings.TrimSpace(strings.ToLower(status))
|
||||
if st != "" && st != "all" {
|
||||
q = q.Where("status = ?", strings.TrimSpace(status))
|
||||
}
|
||||
if submittedByID != nil {
|
||||
q = q.Where("submitted_by_id = ?", *submittedByID)
|
||||
}
|
||||
if err := q.Count(&count).Error; err != nil {
|
||||
return nil, p, err
|
||||
}
|
||||
p.Count = count
|
||||
p = paginator.Paging(p)
|
||||
order := "created_at DESC"
|
||||
if p.SortBy != "" {
|
||||
dir := "DESC"
|
||||
if p.Sort == "asc" {
|
||||
dir = "ASC"
|
||||
}
|
||||
order = p.SortBy + " " + dir
|
||||
}
|
||||
err := q.Order(order).Offset(p.Offset).Limit(p.Limit).Find(&rows).Error
|
||||
return rows, p, err
|
||||
}
|
||||
|
||||
func (_i *cmsContentSubmissionsRepository) Update(row *entity.CmsContentSubmission) error {
|
||||
return _i.DB.DB.Save(row).Error
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package request
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
type SubmitCmsContentSubmissionRequest struct {
|
||||
Domain string `json:"domain" validate:"required"`
|
||||
Title string `json:"title" validate:"required"`
|
||||
Payload json.RawMessage `json:"payload" validate:"required"`
|
||||
}
|
||||
|
||||
type RejectCmsContentSubmissionRequest struct {
|
||||
Note string `json:"note"`
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package response
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type CmsContentSubmissionListItem struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
Domain string `json:"domain"`
|
||||
Title string `json:"title"`
|
||||
Status string `json:"status"`
|
||||
Payload string `json:"payload"`
|
||||
SubmittedByID uint `json:"submitted_by_id"`
|
||||
SubmitterName string `json:"submitter_name"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
|
@ -0,0 +1,597 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"web-qudo-be/app/database"
|
||||
"web-qudo-be/app/database/entity"
|
||||
"web-qudo-be/app/database/entity/users"
|
||||
aboutUsImageSvc "web-qudo-be/app/module/about_us_content_images/service"
|
||||
aboutUsSvc "web-qudo-be/app/module/about_us_contents/service"
|
||||
"web-qudo-be/app/module/cms_content_submissions/repository"
|
||||
"web-qudo-be/app/module/cms_content_submissions/request"
|
||||
"web-qudo-be/app/module/cms_content_submissions/response"
|
||||
heroImageSvc "web-qudo-be/app/module/hero_content_images/service"
|
||||
heroSvc "web-qudo-be/app/module/hero_contents/service"
|
||||
ourProductImageSvc "web-qudo-be/app/module/our_product_content_images/service"
|
||||
ourProductSvc "web-qudo-be/app/module/our_product_contents/service"
|
||||
ourServiceImageSvc "web-qudo-be/app/module/our_service_content_images/service"
|
||||
ourServiceSvc "web-qudo-be/app/module/our_service_contents/service"
|
||||
partnerSvc "web-qudo-be/app/module/partner_contents/service"
|
||||
popupImageReq "web-qudo-be/app/module/popup_news_content_images/request"
|
||||
popupImageSvc "web-qudo-be/app/module/popup_news_content_images/service"
|
||||
popupNewsReq "web-qudo-be/app/module/popup_news_contents/request"
|
||||
popupSvc "web-qudo-be/app/module/popup_news_contents/service"
|
||||
"web-qudo-be/utils/paginator"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/rs/zerolog"
|
||||
)
|
||||
|
||||
const (
|
||||
cmsSubmissionPending = "pending"
|
||||
cmsSubmissionApproved = "approved"
|
||||
cmsSubmissionRejected = "rejected"
|
||||
userLevelContributor = uint(2)
|
||||
userLevelApprover = uint(3)
|
||||
)
|
||||
|
||||
type CmsContentSubmissionsService interface {
|
||||
Submit(clientID *uuid.UUID, user *users.Users, req *request.SubmitCmsContentSubmissionRequest) (*entity.CmsContentSubmission, error)
|
||||
List(clientID *uuid.UUID, user *users.Users, status string, mineOnly bool, p *paginator.Pagination) ([]response.CmsContentSubmissionListItem, *paginator.Pagination, error)
|
||||
Approve(clientID *uuid.UUID, user *users.Users, id uuid.UUID) error
|
||||
Reject(clientID *uuid.UUID, user *users.Users, id uuid.UUID, note string) error
|
||||
}
|
||||
|
||||
type cmsContentSubmissionsService struct {
|
||||
Repo repository.CmsContentSubmissionsRepository
|
||||
DB *database.Database
|
||||
Hero heroSvc.HeroContentsService
|
||||
HeroImg heroImageSvc.HeroContentImagesService
|
||||
About aboutUsSvc.AboutUsContentService
|
||||
AboutImg aboutUsImageSvc.AboutUsContentImageService
|
||||
OurProduct ourProductSvc.OurProductContentService
|
||||
OurProductImg ourProductImageSvc.OurProductContentImagesService
|
||||
OurService ourServiceSvc.OurServiceContentService
|
||||
OurServiceImg ourServiceImageSvc.OurServiceContentImagesService
|
||||
Partner partnerSvc.PartnerContentService
|
||||
Popup popupSvc.PopupNewsContentsService
|
||||
PopupImg popupImageSvc.PopupNewsContentImagesService
|
||||
Log zerolog.Logger
|
||||
}
|
||||
|
||||
func NewCmsContentSubmissionsService(
|
||||
repo repository.CmsContentSubmissionsRepository,
|
||||
db *database.Database,
|
||||
hero heroSvc.HeroContentsService,
|
||||
heroImg heroImageSvc.HeroContentImagesService,
|
||||
about aboutUsSvc.AboutUsContentService,
|
||||
aboutImg aboutUsImageSvc.AboutUsContentImageService,
|
||||
ourProduct ourProductSvc.OurProductContentService,
|
||||
ourProductImg ourProductImageSvc.OurProductContentImagesService,
|
||||
ourService ourServiceSvc.OurServiceContentService,
|
||||
ourServiceImg ourServiceImageSvc.OurServiceContentImagesService,
|
||||
partner partnerSvc.PartnerContentService,
|
||||
popup popupSvc.PopupNewsContentsService,
|
||||
popupImg popupImageSvc.PopupNewsContentImagesService,
|
||||
log zerolog.Logger,
|
||||
) CmsContentSubmissionsService {
|
||||
return &cmsContentSubmissionsService{
|
||||
Repo: repo,
|
||||
DB: db,
|
||||
Hero: hero,
|
||||
HeroImg: heroImg,
|
||||
About: about,
|
||||
AboutImg: aboutImg,
|
||||
OurProduct: ourProduct,
|
||||
OurProductImg: ourProductImg,
|
||||
OurService: ourService,
|
||||
OurServiceImg: ourServiceImg,
|
||||
Partner: partner,
|
||||
Popup: popup,
|
||||
PopupImg: popupImg,
|
||||
Log: log,
|
||||
}
|
||||
}
|
||||
|
||||
func (_i *cmsContentSubmissionsService) Submit(clientID *uuid.UUID, user *users.Users, req *request.SubmitCmsContentSubmissionRequest) (*entity.CmsContentSubmission, error) {
|
||||
if clientID == nil || user == nil {
|
||||
return nil, errors.New("unauthorized")
|
||||
}
|
||||
if user.UserLevelId != userLevelContributor {
|
||||
return nil, errors.New("only contributor (user level 2) can submit CMS drafts")
|
||||
}
|
||||
domain := strings.TrimSpace(strings.ToLower(req.Domain))
|
||||
if domain == "" {
|
||||
return nil, errors.New("domain is required")
|
||||
}
|
||||
title := strings.TrimSpace(req.Title)
|
||||
if title == "" {
|
||||
return nil, errors.New("title is required")
|
||||
}
|
||||
if len(req.Payload) == 0 || string(req.Payload) == "null" {
|
||||
return nil, errors.New("payload is required")
|
||||
}
|
||||
row := &entity.CmsContentSubmission{
|
||||
ID: uuid.New(),
|
||||
ClientID: *clientID,
|
||||
Domain: domain,
|
||||
Title: title,
|
||||
Status: cmsSubmissionPending,
|
||||
Payload: string(req.Payload),
|
||||
SubmittedByID: user.ID,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
if err := _i.Repo.Create(row); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return row, nil
|
||||
}
|
||||
|
||||
func (_i *cmsContentSubmissionsService) List(clientID *uuid.UUID, user *users.Users, status string, mineOnly bool, p *paginator.Pagination) ([]response.CmsContentSubmissionListItem, *paginator.Pagination, error) {
|
||||
if clientID == nil || user == nil {
|
||||
return nil, p, errors.New("unauthorized")
|
||||
}
|
||||
st := strings.TrimSpace(strings.ToLower(status))
|
||||
var submittedBy *uint
|
||||
if mineOnly {
|
||||
submittedBy = &user.ID
|
||||
} else if user.UserLevelId == userLevelContributor {
|
||||
submittedBy = &user.ID
|
||||
}
|
||||
statusArg := status
|
||||
if st == "" {
|
||||
statusArg = "all"
|
||||
}
|
||||
rows, paging, err := _i.Repo.List(*clientID, statusArg, submittedBy, p)
|
||||
if err != nil {
|
||||
return nil, paging, err
|
||||
}
|
||||
out := make([]response.CmsContentSubmissionListItem, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
name := ""
|
||||
var u users.Users
|
||||
if err := _i.DB.DB.Select("fullname").Where("id = ?", row.SubmittedByID).First(&u).Error; err == nil {
|
||||
name = u.Fullname
|
||||
}
|
||||
out = append(out, response.CmsContentSubmissionListItem{
|
||||
ID: row.ID,
|
||||
Domain: row.Domain,
|
||||
Title: row.Title,
|
||||
Status: row.Status,
|
||||
Payload: row.Payload,
|
||||
SubmittedByID: row.SubmittedByID,
|
||||
SubmitterName: name,
|
||||
CreatedAt: row.CreatedAt,
|
||||
UpdatedAt: row.UpdatedAt,
|
||||
})
|
||||
}
|
||||
return out, paging, nil
|
||||
}
|
||||
|
||||
func (_i *cmsContentSubmissionsService) Approve(clientID *uuid.UUID, user *users.Users, id uuid.UUID) error {
|
||||
if clientID == nil || user == nil {
|
||||
return errors.New("unauthorized")
|
||||
}
|
||||
if user.UserLevelId != userLevelApprover {
|
||||
return errors.New("only approver (user level 3) can approve CMS submissions")
|
||||
}
|
||||
row, err := _i.Repo.FindByID(*clientID, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if row.Status != cmsSubmissionPending {
|
||||
return errors.New("submission is not pending")
|
||||
}
|
||||
if err := _i.applyDomainPayload(row.Domain, row.Payload); err != nil {
|
||||
return err
|
||||
}
|
||||
now := time.Now()
|
||||
row.Status = cmsSubmissionApproved
|
||||
row.ReviewedByID = &user.ID
|
||||
row.ReviewNote = ""
|
||||
row.UpdatedAt = now
|
||||
return _i.Repo.Update(row)
|
||||
}
|
||||
|
||||
func (_i *cmsContentSubmissionsService) Reject(clientID *uuid.UUID, user *users.Users, id uuid.UUID, note string) error {
|
||||
if clientID == nil || user == nil {
|
||||
return errors.New("unauthorized")
|
||||
}
|
||||
if user.UserLevelId != userLevelApprover {
|
||||
return errors.New("only approver (user level 3) can reject CMS submissions")
|
||||
}
|
||||
row, err := _i.Repo.FindByID(*clientID, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if row.Status != cmsSubmissionPending {
|
||||
return errors.New("submission is not pending")
|
||||
}
|
||||
now := time.Now()
|
||||
row.Status = cmsSubmissionRejected
|
||||
row.ReviewedByID = &user.ID
|
||||
row.ReviewNote = strings.TrimSpace(note)
|
||||
row.UpdatedAt = now
|
||||
return _i.Repo.Update(row)
|
||||
}
|
||||
|
||||
func (_i *cmsContentSubmissionsService) applyDomainPayload(domain string, payloadJSON string) error {
|
||||
switch strings.ToLower(strings.TrimSpace(domain)) {
|
||||
case "hero":
|
||||
return _i.mergeHero([]byte(payloadJSON))
|
||||
case "about":
|
||||
return _i.mergeAbout([]byte(payloadJSON))
|
||||
case "product":
|
||||
return _i.mergeProduct([]byte(payloadJSON))
|
||||
case "service":
|
||||
return _i.mergeService([]byte(payloadJSON))
|
||||
case "partner":
|
||||
return _i.mergePartner([]byte(payloadJSON))
|
||||
case "popup":
|
||||
return _i.mergePopup([]byte(payloadJSON))
|
||||
default:
|
||||
return errors.New("unknown domain")
|
||||
}
|
||||
}
|
||||
|
||||
type heroPayload struct {
|
||||
Action string `json:"action"`
|
||||
HeroID string `json:"hero_id"`
|
||||
HeroImageID string `json:"hero_image_id"`
|
||||
PrimaryTitle string `json:"primary_title"`
|
||||
SecondaryTitle string `json:"secondary_title"`
|
||||
Description string `json:"description"`
|
||||
PrimaryCta string `json:"primary_cta"`
|
||||
SecondaryCtaText string `json:"secondary_cta_text"`
|
||||
ImageURL string `json:"image_url"`
|
||||
}
|
||||
|
||||
func (_i *cmsContentSubmissionsService) mergeHero(raw []byte) error {
|
||||
var p heroPayload
|
||||
if err := json.Unmarshal(raw, &p); err != nil {
|
||||
return err
|
||||
}
|
||||
if strings.EqualFold(p.Action, "delete") {
|
||||
return errors.New("hero delete is not supported")
|
||||
}
|
||||
ent := &entity.HeroContents{
|
||||
PrimaryTitle: p.PrimaryTitle,
|
||||
SecondaryTitle: p.SecondaryTitle,
|
||||
Description: p.Description,
|
||||
PrimaryCta: p.PrimaryCta,
|
||||
SecondaryCtaText: p.SecondaryCtaText,
|
||||
}
|
||||
var heroUUID uuid.UUID
|
||||
if strings.TrimSpace(p.HeroID) == "" {
|
||||
saved, err := _i.Hero.Save(ent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
heroUUID = saved.ID
|
||||
} else {
|
||||
id, err := uuid.Parse(p.HeroID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
heroUUID = id
|
||||
if err := _i.Hero.Update(heroUUID, ent); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
imgURL := strings.TrimSpace(p.ImageURL)
|
||||
if imgURL == "" {
|
||||
return nil
|
||||
}
|
||||
if strings.TrimSpace(p.HeroImageID) != "" {
|
||||
imgID, err := uuid.Parse(p.HeroImageID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return _i.HeroImg.Update(imgID, &entity.HeroContentImages{
|
||||
ID: imgID,
|
||||
HeroContentID: heroUUID,
|
||||
ImageURL: imgURL,
|
||||
})
|
||||
}
|
||||
_, err := _i.HeroImg.Save(&entity.HeroContentImages{
|
||||
HeroContentID: heroUUID,
|
||||
ImageURL: imgURL,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
type aboutPayload struct {
|
||||
Action string `json:"action"`
|
||||
AboutID *int `json:"about_id"`
|
||||
AboutMediaImageID *int `json:"about_media_image_id"`
|
||||
PrimaryTitle string `json:"primary_title"`
|
||||
SecondaryTitle string `json:"secondary_title"`
|
||||
Description string `json:"description"`
|
||||
PrimaryCta string `json:"primary_cta"`
|
||||
SecondaryCtaText string `json:"secondary_cta_text"`
|
||||
MediaURL string `json:"media_url"`
|
||||
}
|
||||
|
||||
func (_i *cmsContentSubmissionsService) mergeAbout(raw []byte) error {
|
||||
var p aboutPayload
|
||||
if err := json.Unmarshal(raw, &p); err != nil {
|
||||
return err
|
||||
}
|
||||
if strings.EqualFold(p.Action, "delete") {
|
||||
return errors.New("about delete is not supported")
|
||||
}
|
||||
ent := &entity.AboutUsContent{
|
||||
PrimaryTitle: p.PrimaryTitle,
|
||||
SecondaryTitle: p.SecondaryTitle,
|
||||
Description: p.Description,
|
||||
PrimaryCta: p.PrimaryCta,
|
||||
SecondaryCtaText: p.SecondaryCtaText,
|
||||
}
|
||||
var aboutID uint
|
||||
if p.AboutID == nil || *p.AboutID == 0 {
|
||||
saved, err := _i.About.Save(ent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
aboutID = saved.ID
|
||||
} else {
|
||||
aboutID = uint(*p.AboutID)
|
||||
if err := _i.About.Update(aboutID, ent); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
mediaURL := strings.TrimSpace(p.MediaURL)
|
||||
if mediaURL == "" {
|
||||
return nil
|
||||
}
|
||||
if p.AboutMediaImageID != nil && *p.AboutMediaImageID > 0 {
|
||||
_ = _i.AboutImg.Delete(uint(*p.AboutMediaImageID))
|
||||
}
|
||||
_, err := _i.AboutImg.SaveRemoteURL(aboutID, mediaURL, "")
|
||||
return err
|
||||
}
|
||||
|
||||
type productPayload struct {
|
||||
Action string `json:"action"`
|
||||
ProductID string `json:"product_id"`
|
||||
ProductImageID string `json:"product_image_id"`
|
||||
PrimaryTitle string `json:"primary_title"`
|
||||
SecondaryTitle string `json:"secondary_title"`
|
||||
Description string `json:"description"`
|
||||
LinkURL string `json:"link_url"`
|
||||
ImageURL string `json:"image_url"`
|
||||
}
|
||||
|
||||
func (_i *cmsContentSubmissionsService) mergeProduct(raw []byte) error {
|
||||
var p productPayload
|
||||
if err := json.Unmarshal(raw, &p); err != nil {
|
||||
return err
|
||||
}
|
||||
if strings.EqualFold(p.Action, "delete") {
|
||||
if strings.TrimSpace(p.ProductID) == "" {
|
||||
return errors.New("product_id required for delete")
|
||||
}
|
||||
id, err := uuid.Parse(p.ProductID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return _i.OurProduct.Delete(id)
|
||||
}
|
||||
ent := &entity.OurProductContent{
|
||||
PrimaryTitle: p.PrimaryTitle,
|
||||
SecondaryTitle: p.SecondaryTitle,
|
||||
Description: p.Description,
|
||||
LinkURL: p.LinkURL,
|
||||
}
|
||||
var pid uuid.UUID
|
||||
if strings.TrimSpace(p.ProductID) == "" {
|
||||
saved, err := _i.OurProduct.Save(ent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pid = saved.ID
|
||||
} else {
|
||||
id, err := uuid.Parse(p.ProductID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pid = id
|
||||
if err := _i.OurProduct.Update(pid, ent); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
imgURL := strings.TrimSpace(p.ImageURL)
|
||||
if imgURL == "" {
|
||||
return nil
|
||||
}
|
||||
if strings.TrimSpace(p.ProductImageID) != "" {
|
||||
imgID, err := uuid.Parse(p.ProductImageID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return _i.OurProductImg.Update(imgID, &entity.OurProductContentImage{
|
||||
ID: imgID,
|
||||
OurProductContentID: pid,
|
||||
ImageURL: imgURL,
|
||||
})
|
||||
}
|
||||
_, err := _i.OurProductImg.Save(&entity.OurProductContentImage{
|
||||
OurProductContentID: pid,
|
||||
ImageURL: imgURL,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
type servicePayload struct {
|
||||
Action string `json:"action"`
|
||||
ServiceID *int `json:"service_id"`
|
||||
ServiceImageID string `json:"service_image_id"`
|
||||
PrimaryTitle string `json:"primary_title"`
|
||||
SecondaryTitle string `json:"secondary_title"`
|
||||
Description string `json:"description"`
|
||||
LinkURL string `json:"link_url"`
|
||||
ImageURL string `json:"image_url"`
|
||||
}
|
||||
|
||||
func (_i *cmsContentSubmissionsService) mergeService(raw []byte) error {
|
||||
var p servicePayload
|
||||
if err := json.Unmarshal(raw, &p); err != nil {
|
||||
return err
|
||||
}
|
||||
if strings.EqualFold(p.Action, "delete") {
|
||||
if p.ServiceID == nil || *p.ServiceID == 0 {
|
||||
return errors.New("service_id required for delete")
|
||||
}
|
||||
return _i.OurService.Delete(uint(*p.ServiceID))
|
||||
}
|
||||
ent := &entity.OurServiceContent{
|
||||
PrimaryTitle: p.PrimaryTitle,
|
||||
SecondaryTitle: p.SecondaryTitle,
|
||||
Description: p.Description,
|
||||
LinkURL: p.LinkURL,
|
||||
}
|
||||
var sid uint
|
||||
if p.ServiceID == nil || *p.ServiceID == 0 {
|
||||
saved, err := _i.OurService.Save(ent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sid = saved.ID
|
||||
} else {
|
||||
sid = uint(*p.ServiceID)
|
||||
if err := _i.OurService.Update(sid, ent); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
imgURL := strings.TrimSpace(p.ImageURL)
|
||||
if imgURL == "" {
|
||||
return nil
|
||||
}
|
||||
return _i.mergeServiceImage(sid, strings.TrimSpace(p.ServiceImageID), imgURL)
|
||||
}
|
||||
|
||||
func (_i *cmsContentSubmissionsService) mergeServiceImage(sid uint, serviceImageID string, imgURL string) error {
|
||||
if serviceImageID != "" {
|
||||
n, err := strconv.ParseUint(serviceImageID, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
imgID := uint(n)
|
||||
return _i.OurServiceImg.Update(imgID, &entity.OurServiceContentImage{
|
||||
ID: imgID,
|
||||
OurServiceContentID: sid,
|
||||
ImageURL: imgURL,
|
||||
})
|
||||
}
|
||||
_, err := _i.OurServiceImg.Save(&entity.OurServiceContentImage{
|
||||
OurServiceContentID: sid,
|
||||
ImageURL: imgURL,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
type partnerPayload struct {
|
||||
Action string `json:"action"`
|
||||
PartnerID string `json:"partner_id"`
|
||||
PrimaryTitle string `json:"primary_title"`
|
||||
ImagePath string `json:"image_path"`
|
||||
ImageURL string `json:"image_url"`
|
||||
}
|
||||
|
||||
func (_i *cmsContentSubmissionsService) mergePartner(raw []byte) error {
|
||||
var p partnerPayload
|
||||
if err := json.Unmarshal(raw, &p); err != nil {
|
||||
return err
|
||||
}
|
||||
if strings.EqualFold(p.Action, "delete") {
|
||||
if strings.TrimSpace(p.PartnerID) == "" {
|
||||
return errors.New("partner_id required for delete")
|
||||
}
|
||||
id, err := uuid.Parse(p.PartnerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return _i.Partner.Delete(id)
|
||||
}
|
||||
ent := &entity.PartnerContent{
|
||||
PrimaryTitle: strings.TrimSpace(p.PrimaryTitle),
|
||||
ImagePath: p.ImagePath,
|
||||
ImageURL: strings.TrimSpace(p.ImageURL),
|
||||
}
|
||||
if strings.TrimSpace(p.PartnerID) == "" {
|
||||
_, err := _i.Partner.Save(ent)
|
||||
return err
|
||||
}
|
||||
id, err := uuid.Parse(p.PartnerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return _i.Partner.Update(id, ent)
|
||||
}
|
||||
|
||||
type popupPayload struct {
|
||||
Action string `json:"action"`
|
||||
PopupID *uint `json:"popup_id"`
|
||||
PrimaryTitle string `json:"primary_title"`
|
||||
SecondaryTitle string `json:"secondary_title"`
|
||||
Description string `json:"description"`
|
||||
PrimaryCta string `json:"primary_cta"`
|
||||
SecondaryCtaText string `json:"secondary_cta_text"`
|
||||
MediaURL string `json:"media_url"`
|
||||
}
|
||||
|
||||
func (_i *cmsContentSubmissionsService) mergePopup(raw []byte) error {
|
||||
var p popupPayload
|
||||
if err := json.Unmarshal(raw, &p); err != nil {
|
||||
return err
|
||||
}
|
||||
if strings.EqualFold(p.Action, "delete") {
|
||||
if p.PopupID == nil || *p.PopupID == 0 {
|
||||
return errors.New("popup_id required for delete")
|
||||
}
|
||||
return _i.Popup.Delete(*p.PopupID)
|
||||
}
|
||||
if p.PopupID == nil || *p.PopupID == 0 {
|
||||
res, err := _i.Popup.Save(popupNewsReq.PopupNewsContentsCreateRequest{
|
||||
PrimaryTitle: p.PrimaryTitle,
|
||||
SecondaryTitle: p.SecondaryTitle,
|
||||
Description: p.Description,
|
||||
PrimaryCTA: p.PrimaryCta,
|
||||
SecondaryCTAText: p.SecondaryCtaText,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return _i.attachPopupImage(res.ID, p.MediaURL)
|
||||
}
|
||||
pid := *p.PopupID
|
||||
if err := _i.Popup.Update(pid, popupNewsReq.PopupNewsContentsUpdateRequest{
|
||||
ID: pid,
|
||||
PrimaryTitle: p.PrimaryTitle,
|
||||
SecondaryTitle: p.SecondaryTitle,
|
||||
Description: p.Description,
|
||||
PrimaryCTA: p.PrimaryCta,
|
||||
SecondaryCTAText: p.SecondaryCtaText,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
return _i.attachPopupImage(pid, p.MediaURL)
|
||||
}
|
||||
|
||||
func (_i *cmsContentSubmissionsService) attachPopupImage(popupID uint, mediaURL string) error {
|
||||
mediaURL = strings.TrimSpace(mediaURL)
|
||||
if mediaURL == "" {
|
||||
return nil
|
||||
}
|
||||
return _i.PopupImg.Save(popupImageReq.PopupNewsContentImagesCreateRequest{
|
||||
PopupNewsContentID: popupID,
|
||||
MediaPath: "",
|
||||
MediaURL: mediaURL,
|
||||
})
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ import (
|
|||
|
||||
"web-qudo-be/app/database/entity"
|
||||
"web-qudo-be/app/module/hero_content_images/repository"
|
||||
medialib "web-qudo-be/app/module/media_library/service"
|
||||
minioStorage "web-qudo-be/config/config"
|
||||
"web-qudo-be/utils/storage"
|
||||
)
|
||||
|
|
@ -15,6 +16,7 @@ import (
|
|||
type heroContentImagesService struct {
|
||||
Repo repository.HeroContentImagesRepository
|
||||
MinioStorage *minioStorage.MinioStorage
|
||||
MediaLib medialib.MediaLibraryService
|
||||
Log zerolog.Logger
|
||||
}
|
||||
|
||||
|
|
@ -30,11 +32,13 @@ type HeroContentImagesService interface {
|
|||
func NewHeroContentImagesService(
|
||||
repo repository.HeroContentImagesRepository,
|
||||
minio *minioStorage.MinioStorage,
|
||||
mediaLib medialib.MediaLibraryService,
|
||||
log zerolog.Logger,
|
||||
) HeroContentImagesService {
|
||||
return &heroContentImagesService{
|
||||
Repo: repo,
|
||||
MinioStorage: minio,
|
||||
MediaLib: mediaLib,
|
||||
Log: log,
|
||||
}
|
||||
}
|
||||
|
|
@ -71,7 +75,14 @@ func (s *heroContentImagesService) SaveWithFile(heroContentID uuid.UUID, file *m
|
|||
ImagePath: key,
|
||||
ImageURL: url,
|
||||
}
|
||||
return s.Save(data)
|
||||
out, err := s.Save(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if s.MediaLib != nil {
|
||||
_ = s.MediaLib.RegisterCMSAsset(url, key, "hero_content", file)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s *heroContentImagesService) Update(id uuid.UUID, data *entity.HeroContentImages) error {
|
||||
|
|
@ -89,10 +100,16 @@ func (s *heroContentImagesService) UpdateWithFile(id uuid.UUID, file *multipart.
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.Repo.Update(id, &entity.HeroContentImages{
|
||||
if err := s.Repo.Update(id, &entity.HeroContentImages{
|
||||
ImagePath: key,
|
||||
ImageURL: url,
|
||||
})
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if s.MediaLib != nil {
|
||||
_ = s.MediaLib.RegisterCMSAsset(url, key, "hero_content", file)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *heroContentImagesService) Delete(id uuid.UUID) error {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,104 @@
|
|||
package controller
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
|
||||
"web-qudo-be/app/middleware"
|
||||
"web-qudo-be/app/module/media_library/request"
|
||||
"web-qudo-be/app/module/media_library/service"
|
||||
"web-qudo-be/utils/paginator"
|
||||
utilRes "web-qudo-be/utils/response"
|
||||
utilVal "web-qudo-be/utils/validator"
|
||||
)
|
||||
|
||||
type MediaLibraryController struct {
|
||||
svc service.MediaLibraryService
|
||||
}
|
||||
|
||||
func NewMediaLibraryController(svc service.MediaLibraryService) *MediaLibraryController {
|
||||
return &MediaLibraryController{svc: svc}
|
||||
}
|
||||
|
||||
func (_i *MediaLibraryController) All(c *fiber.Ctx) error {
|
||||
clientID := middleware.GetClientID(c)
|
||||
user := middleware.GetUser(c)
|
||||
uid := 0
|
||||
if user != nil {
|
||||
uid = int(user.ID)
|
||||
}
|
||||
_ = uid
|
||||
|
||||
paginate, err := paginator.Paginate(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q := strings.TrimSpace(c.Query("q"))
|
||||
var st *string
|
||||
if v := strings.TrimSpace(c.Query("source_type")); v != "" {
|
||||
st = &v
|
||||
}
|
||||
data, paging, err := _i.svc.All(clientID, q, st, paginate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"Media library list"},
|
||||
Data: data,
|
||||
Meta: paging,
|
||||
})
|
||||
}
|
||||
|
||||
func (_i *MediaLibraryController) Register(c *fiber.Ctx) error {
|
||||
clientID := middleware.GetClientID(c)
|
||||
user := middleware.GetUser(c)
|
||||
uid := 0
|
||||
if user != nil {
|
||||
uid = int(user.ID)
|
||||
}
|
||||
req := new(request.MediaLibraryRegisterRequest)
|
||||
if err := utilVal.ParseAndValidate(c, req); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := _i.svc.RegisterFromRequest(clientID, uid, req); err != nil {
|
||||
return err
|
||||
}
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"Registered to media library"},
|
||||
})
|
||||
}
|
||||
|
||||
func (_i *MediaLibraryController) Upload(c *fiber.Ctx) error {
|
||||
clientID := middleware.GetClientID(c)
|
||||
user := middleware.GetUser(c)
|
||||
uid := 0
|
||||
if user != nil {
|
||||
uid = int(user.ID)
|
||||
}
|
||||
if err := _i.svc.Upload(clientID, uid, c); err != nil {
|
||||
return err
|
||||
}
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"File uploaded and added to media library"},
|
||||
})
|
||||
}
|
||||
|
||||
func (_i *MediaLibraryController) Delete(c *fiber.Ctx) error {
|
||||
clientID := middleware.GetClientID(c)
|
||||
id, err := strconv.ParseUint(c.Params("id"), 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := _i.svc.Delete(clientID, uint(id)); err != nil {
|
||||
return err
|
||||
}
|
||||
return utilRes.Resp(c, utilRes.Response{
|
||||
Success: true,
|
||||
Messages: utilRes.Messages{"Media library entry removed"},
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
package media_library
|
||||
|
||||
import (
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"go.uber.org/fx"
|
||||
|
||||
"web-qudo-be/app/module/media_library/controller"
|
||||
"web-qudo-be/app/module/media_library/repository"
|
||||
"web-qudo-be/app/module/media_library/service"
|
||||
)
|
||||
|
||||
type MediaLibraryRouter struct {
|
||||
App *fiber.App
|
||||
Ctrl *controller.MediaLibraryController
|
||||
}
|
||||
|
||||
var NewMediaLibraryModule = fx.Options(
|
||||
fx.Provide(repository.NewMediaLibraryRepository),
|
||||
fx.Provide(service.NewMediaLibraryService),
|
||||
fx.Provide(controller.NewMediaLibraryController),
|
||||
fx.Provide(NewMediaLibraryRouter),
|
||||
)
|
||||
|
||||
func NewMediaLibraryRouter(app *fiber.App, ctrl *controller.MediaLibraryController) *MediaLibraryRouter {
|
||||
return &MediaLibraryRouter{App: app, Ctrl: ctrl}
|
||||
}
|
||||
|
||||
func (r *MediaLibraryRouter) RegisterMediaLibraryRoutes() {
|
||||
c := r.Ctrl
|
||||
r.App.Route("/media-library", func(router fiber.Router) {
|
||||
router.Get("/", c.All)
|
||||
router.Post("/register", c.Register)
|
||||
router.Post("/upload", c.Upload)
|
||||
router.Delete("/:id", c.Delete)
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
package repository
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"web-qudo-be/app/database"
|
||||
"web-qudo-be/app/database/entity"
|
||||
"web-qudo-be/utils/paginator"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/rs/zerolog"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type MediaLibraryRepository interface {
|
||||
FindByPublicURLAny(publicURL string) (*entity.MediaLibraryItem, error)
|
||||
Create(item *entity.MediaLibraryItem) error
|
||||
Update(id uint, fields map[string]interface{}) error
|
||||
GetAll(clientID *uuid.UUID, q string, sourceType *string, p *paginator.Pagination) ([]*entity.MediaLibraryItem, *paginator.Pagination, error)
|
||||
SoftDelete(clientID *uuid.UUID, id uint) error
|
||||
}
|
||||
|
||||
type mediaLibraryRepository struct {
|
||||
DB *database.Database
|
||||
Log zerolog.Logger
|
||||
}
|
||||
|
||||
func NewMediaLibraryRepository(db *database.Database, log zerolog.Logger) MediaLibraryRepository {
|
||||
return &mediaLibraryRepository{DB: db, Log: log}
|
||||
}
|
||||
|
||||
func (_i *mediaLibraryRepository) FindByPublicURLAny(publicURL string) (*entity.MediaLibraryItem, error) {
|
||||
var row entity.MediaLibraryItem
|
||||
err := _i.DB.DB.Where("public_url = ?", publicURL).First(&row).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &row, nil
|
||||
}
|
||||
|
||||
func (_i *mediaLibraryRepository) Create(item *entity.MediaLibraryItem) error {
|
||||
return _i.DB.DB.Create(item).Error
|
||||
}
|
||||
|
||||
func (_i *mediaLibraryRepository) Update(id uint, fields map[string]interface{}) error {
|
||||
return _i.DB.DB.Model(&entity.MediaLibraryItem{}).Where("id = ?", id).Updates(fields).Error
|
||||
}
|
||||
|
||||
func (_i *mediaLibraryRepository) GetAll(clientID *uuid.UUID, q string, sourceType *string, p *paginator.Pagination) ([]*entity.MediaLibraryItem, *paginator.Pagination, error) {
|
||||
var rows []*entity.MediaLibraryItem
|
||||
var count int64
|
||||
|
||||
query := _i.DB.DB.Model(&entity.MediaLibraryItem{}).Where("is_active = ?", true)
|
||||
if clientID != nil {
|
||||
query = query.Where("client_id = ?", clientID)
|
||||
}
|
||||
if sourceType != nil && strings.TrimSpace(*sourceType) != "" {
|
||||
query = query.Where("source_type = ?", strings.TrimSpace(*sourceType))
|
||||
}
|
||||
if strings.TrimSpace(q) != "" {
|
||||
like := "%" + strings.ToLower(strings.TrimSpace(q)) + "%"
|
||||
query = query.Where(
|
||||
"LOWER(COALESCE(original_filename,'')) LIKE ? OR LOWER(public_url) LIKE ? OR LOWER(COALESCE(source_label,'')) LIKE ?",
|
||||
like, like, like,
|
||||
)
|
||||
}
|
||||
if err := query.Count(&count).Error; err != nil {
|
||||
return nil, p, err
|
||||
}
|
||||
p.Count = count
|
||||
p = paginator.Paging(p)
|
||||
err := query.Order("created_at DESC").Offset(p.Offset).Limit(p.Limit).Find(&rows).Error
|
||||
return rows, p, err
|
||||
}
|
||||
|
||||
func (_i *mediaLibraryRepository) SoftDelete(clientID *uuid.UUID, id uint) error {
|
||||
q := _i.DB.DB.Model(&entity.MediaLibraryItem{}).Where("id = ?", id)
|
||||
if clientID != nil {
|
||||
q = q.Where("client_id = ?", clientID)
|
||||
}
|
||||
res := q.Update("is_active", false)
|
||||
if res.Error != nil {
|
||||
return res.Error
|
||||
}
|
||||
if res.RowsAffected == 0 {
|
||||
return gorm.ErrRecordNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package request
|
||||
|
||||
import (
|
||||
"web-qudo-be/utils/paginator"
|
||||
)
|
||||
|
||||
type MediaLibraryRegisterRequest struct {
|
||||
PublicURL string `json:"public_url" validate:"required"`
|
||||
ObjectKey *string `json:"object_key"`
|
||||
OriginalFilename *string `json:"original_filename"`
|
||||
FileCategory *string `json:"file_category"`
|
||||
SizeBytes *int64 `json:"size_bytes"`
|
||||
SourceType string `json:"source_type" validate:"required"`
|
||||
SourceLabel *string `json:"source_label"`
|
||||
ArticleFileID *uint `json:"article_file_id"`
|
||||
}
|
||||
|
||||
type MediaLibraryQueryRequest struct {
|
||||
Q string `json:"q"`
|
||||
SourceType *string `json:"source_type"`
|
||||
Pagination *paginator.Pagination `json:"pagination"`
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package response
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type MediaLibraryItemResponse struct {
|
||||
ID uint `json:"id"`
|
||||
PublicURL string `json:"public_url"`
|
||||
ObjectKey *string `json:"object_key"`
|
||||
OriginalFilename *string `json:"original_filename"`
|
||||
FileCategory string `json:"file_category"`
|
||||
SizeBytes *int64 `json:"size_bytes"`
|
||||
SourceType string `json:"source_type"`
|
||||
SourceLabel *string `json:"source_label"`
|
||||
ArticleFileID *uint `json:"article_file_id"`
|
||||
CreatedByID int `json:"created_by_id"`
|
||||
ClientID *uuid.UUID `json:"client_id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
|
@ -0,0 +1,244 @@
|
|||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"mime/multipart"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/google/uuid"
|
||||
"github.com/rs/zerolog"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"web-qudo-be/app/database/entity"
|
||||
"web-qudo-be/app/module/media_library/repository"
|
||||
"web-qudo-be/app/module/media_library/request"
|
||||
"web-qudo-be/app/module/media_library/response"
|
||||
appcfg "web-qudo-be/config/config"
|
||||
"web-qudo-be/utils/paginator"
|
||||
"web-qudo-be/utils/storage"
|
||||
)
|
||||
|
||||
// RegisterInput is used from article/CMS upload hooks (same physical file, extra catalog row).
|
||||
type RegisterInput struct {
|
||||
ClientID *uuid.UUID
|
||||
UserID int
|
||||
PublicURL string
|
||||
ObjectKey *string
|
||||
OriginalFilename *string
|
||||
FileCategory string
|
||||
SizeBytes *int64
|
||||
SourceType string
|
||||
SourceLabel *string
|
||||
ArticleFileID *uint
|
||||
}
|
||||
|
||||
type MediaLibraryService interface {
|
||||
UpsertRegister(in RegisterInput) error
|
||||
RegisterFromRequest(clientID *uuid.UUID, userID int, req *request.MediaLibraryRegisterRequest) error
|
||||
RegisterCMSAsset(publicURL, objectKey, sourceLabel string, file *multipart.FileHeader) error
|
||||
All(clientID *uuid.UUID, q string, sourceType *string, p *paginator.Pagination) ([]*response.MediaLibraryItemResponse, *paginator.Pagination, error)
|
||||
Upload(clientID *uuid.UUID, userID int, c *fiber.Ctx) error
|
||||
Delete(clientID *uuid.UUID, id uint) error
|
||||
}
|
||||
|
||||
type mediaLibraryService struct {
|
||||
Repo repository.MediaLibraryRepository
|
||||
Cfg *appcfg.Config
|
||||
MinioStorage *appcfg.MinioStorage
|
||||
Log zerolog.Logger
|
||||
}
|
||||
|
||||
func NewMediaLibraryService(
|
||||
repo repository.MediaLibraryRepository,
|
||||
cfg *appcfg.Config,
|
||||
minio *appcfg.MinioStorage,
|
||||
log zerolog.Logger,
|
||||
) MediaLibraryService {
|
||||
return &mediaLibraryService{Repo: repo, Cfg: cfg, MinioStorage: minio, Log: log}
|
||||
}
|
||||
|
||||
func ArticleFilePublicURL(cfg *appcfg.Config, fileName string) string {
|
||||
base := strings.TrimSuffix(cfg.App.Domain, "/")
|
||||
return base + "/article-files/viewer/" + strings.TrimPrefix(fileName, "/")
|
||||
}
|
||||
|
||||
func CategoryFromFilename(name string) string {
|
||||
ext := strings.ToLower(filepath.Ext(name))
|
||||
switch ext {
|
||||
case ".jpg", ".jpeg", ".png", ".gif", ".webp", ".svg":
|
||||
return "image"
|
||||
case ".mp4", ".webm", ".mov":
|
||||
return "video"
|
||||
case ".mp3", ".wav", ".ogg", ".m4a":
|
||||
return "audio"
|
||||
case ".pdf", ".doc", ".docx", ".txt", ".csv":
|
||||
return "document"
|
||||
default:
|
||||
return "other"
|
||||
}
|
||||
}
|
||||
|
||||
func (s *mediaLibraryService) UpsertRegister(in RegisterInput) error {
|
||||
url := strings.TrimSpace(in.PublicURL)
|
||||
if url == "" {
|
||||
return nil
|
||||
}
|
||||
existing, err := s.Repo.FindByPublicURLAny(url)
|
||||
if err == nil {
|
||||
if !existing.IsActive {
|
||||
cat := strings.TrimSpace(in.FileCategory)
|
||||
if cat == "" {
|
||||
cat = "other"
|
||||
}
|
||||
return s.Repo.Update(existing.ID, map[string]interface{}{
|
||||
"is_active": true,
|
||||
"object_key": in.ObjectKey,
|
||||
"original_filename": in.OriginalFilename,
|
||||
"file_category": cat,
|
||||
"size_bytes": in.SizeBytes,
|
||||
"source_type": in.SourceType,
|
||||
"source_label": in.SourceLabel,
|
||||
"article_file_id": in.ArticleFileID,
|
||||
"updated_at": time.Now(),
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return err
|
||||
}
|
||||
cat := strings.TrimSpace(in.FileCategory)
|
||||
if cat == "" {
|
||||
cat = "other"
|
||||
}
|
||||
item := &entity.MediaLibraryItem{
|
||||
PublicURL: url,
|
||||
ObjectKey: in.ObjectKey,
|
||||
OriginalFilename: in.OriginalFilename,
|
||||
FileCategory: cat,
|
||||
SizeBytes: in.SizeBytes,
|
||||
SourceType: in.SourceType,
|
||||
SourceLabel: in.SourceLabel,
|
||||
ArticleFileID: in.ArticleFileID,
|
||||
CreatedByID: in.UserID,
|
||||
ClientID: in.ClientID,
|
||||
IsActive: true,
|
||||
}
|
||||
return s.Repo.Create(item)
|
||||
}
|
||||
|
||||
func (s *mediaLibraryService) RegisterCMSAsset(publicURL, objectKey, sourceLabel string, file *multipart.FileHeader) error {
|
||||
if strings.TrimSpace(publicURL) == "" {
|
||||
return nil
|
||||
}
|
||||
var keyPtr *string
|
||||
if strings.TrimSpace(objectKey) != "" {
|
||||
k := objectKey
|
||||
keyPtr = &k
|
||||
}
|
||||
lbl := sourceLabel
|
||||
var namePtr *string
|
||||
var sz *int64
|
||||
cat := "other"
|
||||
if file != nil {
|
||||
b := filepath.Base(file.Filename)
|
||||
namePtr = &b
|
||||
ss := file.Size
|
||||
sz = &ss
|
||||
cat = CategoryFromFilename(b)
|
||||
}
|
||||
return s.UpsertRegister(RegisterInput{
|
||||
PublicURL: publicURL,
|
||||
ObjectKey: keyPtr,
|
||||
OriginalFilename: namePtr,
|
||||
FileCategory: cat,
|
||||
SizeBytes: sz,
|
||||
SourceType: "cms",
|
||||
SourceLabel: &lbl,
|
||||
})
|
||||
}
|
||||
|
||||
func (s *mediaLibraryService) RegisterFromRequest(clientID *uuid.UUID, userID int, req *request.MediaLibraryRegisterRequest) error {
|
||||
cat := CategoryFromFilename(req.PublicURL)
|
||||
if req.OriginalFilename != nil {
|
||||
cat = CategoryFromFilename(*req.OriginalFilename)
|
||||
}
|
||||
if req.FileCategory != nil && strings.TrimSpace(*req.FileCategory) != "" {
|
||||
cat = strings.TrimSpace(*req.FileCategory)
|
||||
}
|
||||
return s.UpsertRegister(RegisterInput{
|
||||
ClientID: clientID,
|
||||
UserID: userID,
|
||||
PublicURL: strings.TrimSpace(req.PublicURL),
|
||||
ObjectKey: req.ObjectKey,
|
||||
OriginalFilename: req.OriginalFilename,
|
||||
FileCategory: cat,
|
||||
SizeBytes: req.SizeBytes,
|
||||
SourceType: req.SourceType,
|
||||
SourceLabel: req.SourceLabel,
|
||||
ArticleFileID: req.ArticleFileID,
|
||||
})
|
||||
}
|
||||
|
||||
func toResponse(e *entity.MediaLibraryItem) *response.MediaLibraryItemResponse {
|
||||
return &response.MediaLibraryItemResponse{
|
||||
ID: e.ID,
|
||||
PublicURL: e.PublicURL,
|
||||
ObjectKey: e.ObjectKey,
|
||||
OriginalFilename: e.OriginalFilename,
|
||||
FileCategory: e.FileCategory,
|
||||
SizeBytes: e.SizeBytes,
|
||||
SourceType: e.SourceType,
|
||||
SourceLabel: e.SourceLabel,
|
||||
ArticleFileID: e.ArticleFileID,
|
||||
CreatedByID: e.CreatedByID,
|
||||
ClientID: e.ClientID,
|
||||
CreatedAt: e.CreatedAt,
|
||||
UpdatedAt: e.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *mediaLibraryService) All(clientID *uuid.UUID, q string, sourceType *string, p *paginator.Pagination) ([]*response.MediaLibraryItemResponse, *paginator.Pagination, error) {
|
||||
rows, paging, err := s.Repo.GetAll(clientID, q, sourceType, p)
|
||||
if err != nil {
|
||||
return nil, paging, err
|
||||
}
|
||||
out := make([]*response.MediaLibraryItemResponse, 0, len(rows))
|
||||
for _, r := range rows {
|
||||
out = append(out, toResponse(r))
|
||||
}
|
||||
return out, paging, nil
|
||||
}
|
||||
|
||||
func (s *mediaLibraryService) Upload(clientID *uuid.UUID, userID int, c *fiber.Ctx) error {
|
||||
file, err := c.FormFile("file")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key, previewURL, err := storage.UploadMediaLibraryObject(s.MinioStorage, file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
name := filepath.Base(file.Filename)
|
||||
sz := file.Size
|
||||
return s.UpsertRegister(RegisterInput{
|
||||
ClientID: clientID,
|
||||
UserID: userID,
|
||||
PublicURL: previewURL,
|
||||
ObjectKey: &key,
|
||||
OriginalFilename: &name,
|
||||
FileCategory: CategoryFromFilename(name),
|
||||
SizeBytes: &sz,
|
||||
SourceType: "upload",
|
||||
SourceLabel: strPtr("media_library_direct"),
|
||||
})
|
||||
}
|
||||
|
||||
func strPtr(s string) *string { return &s }
|
||||
|
||||
func (s *mediaLibraryService) Delete(clientID *uuid.UUID, id uint) error {
|
||||
return s.Repo.SoftDelete(clientID, id)
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ import (
|
|||
"github.com/rs/zerolog"
|
||||
|
||||
"web-qudo-be/app/database/entity"
|
||||
medialib "web-qudo-be/app/module/media_library/service"
|
||||
"web-qudo-be/app/module/our_product_content_images/repository"
|
||||
minioStorage "web-qudo-be/config/config"
|
||||
"web-qudo-be/utils/storage"
|
||||
|
|
@ -15,6 +16,7 @@ import (
|
|||
type ourProductContentImagesService struct {
|
||||
Repo repository.OurProductContentImagesRepository
|
||||
MinioStorage *minioStorage.MinioStorage
|
||||
MediaLib medialib.MediaLibraryService
|
||||
Log zerolog.Logger
|
||||
}
|
||||
|
||||
|
|
@ -30,11 +32,13 @@ type OurProductContentImagesService interface {
|
|||
func NewOurProductContentImagesService(
|
||||
repo repository.OurProductContentImagesRepository,
|
||||
minio *minioStorage.MinioStorage,
|
||||
mediaLib medialib.MediaLibraryService,
|
||||
log zerolog.Logger,
|
||||
) OurProductContentImagesService {
|
||||
return &ourProductContentImagesService{
|
||||
Repo: repo,
|
||||
MinioStorage: minio,
|
||||
MediaLib: mediaLib,
|
||||
Log: log,
|
||||
}
|
||||
}
|
||||
|
|
@ -71,7 +75,14 @@ func (s *ourProductContentImagesService) SaveWithFile(ourProductContentID uuid.U
|
|||
ImagePath: key,
|
||||
ImageURL: url,
|
||||
}
|
||||
return s.Save(data)
|
||||
out, err := s.Save(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if s.MediaLib != nil {
|
||||
_ = s.MediaLib.RegisterCMSAsset(url, key, "our_product", file)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s *ourProductContentImagesService) Update(id uuid.UUID, data *entity.OurProductContentImage) error {
|
||||
|
|
@ -89,10 +100,16 @@ func (s *ourProductContentImagesService) UpdateWithFile(id uuid.UUID, file *mult
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.Repo.Update(id, &entity.OurProductContentImage{
|
||||
if err := s.Repo.Update(id, &entity.OurProductContentImage{
|
||||
ImagePath: key,
|
||||
ImageURL: url,
|
||||
})
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if s.MediaLib != nil {
|
||||
_ = s.MediaLib.RegisterCMSAsset(url, key, "our_product", file)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ourProductContentImagesService) Delete(id uuid.UUID) error {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"github.com/rs/zerolog"
|
||||
|
||||
"web-qudo-be/app/database/entity"
|
||||
medialib "web-qudo-be/app/module/media_library/service"
|
||||
"web-qudo-be/app/module/our_service_content_images/repository"
|
||||
minioStorage "web-qudo-be/config/config"
|
||||
"web-qudo-be/utils/storage"
|
||||
|
|
@ -14,6 +15,7 @@ import (
|
|||
type ourServiceContentImagesService struct {
|
||||
Repo repository.OurServiceContentImagesRepository
|
||||
MinioStorage *minioStorage.MinioStorage
|
||||
MediaLib medialib.MediaLibraryService
|
||||
Log zerolog.Logger
|
||||
}
|
||||
|
||||
|
|
@ -29,11 +31,13 @@ type OurServiceContentImagesService interface {
|
|||
func NewOurServiceContentImagesService(
|
||||
repo repository.OurServiceContentImagesRepository,
|
||||
minio *minioStorage.MinioStorage,
|
||||
mediaLib medialib.MediaLibraryService,
|
||||
log zerolog.Logger,
|
||||
) OurServiceContentImagesService {
|
||||
return &ourServiceContentImagesService{
|
||||
Repo: repo,
|
||||
MinioStorage: minio,
|
||||
MediaLib: mediaLib,
|
||||
Log: log,
|
||||
}
|
||||
}
|
||||
|
|
@ -68,7 +72,14 @@ func (s *ourServiceContentImagesService) SaveWithFile(ourServiceContentID uint,
|
|||
ImagePath: key,
|
||||
ImageURL: url,
|
||||
}
|
||||
return s.Save(data)
|
||||
out, err := s.Save(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if s.MediaLib != nil {
|
||||
_ = s.MediaLib.RegisterCMSAsset(url, key, "our_service", file)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (s *ourServiceContentImagesService) Update(id uint, data *entity.OurServiceContentImage) error {
|
||||
|
|
@ -86,10 +97,16 @@ func (s *ourServiceContentImagesService) UpdateWithFile(id uint, file *multipart
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.Repo.Update(id, &entity.OurServiceContentImage{
|
||||
if err := s.Repo.Update(id, &entity.OurServiceContentImage{
|
||||
ImagePath: key,
|
||||
ImageURL: url,
|
||||
})
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if s.MediaLib != nil {
|
||||
_ = s.MediaLib.RegisterCMSAsset(url, key, "our_service", file)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ourServiceContentImagesService) Delete(id uint) error {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
"github.com/rs/zerolog"
|
||||
|
||||
"web-qudo-be/app/database/entity"
|
||||
medialib "web-qudo-be/app/module/media_library/service"
|
||||
"web-qudo-be/app/module/partner_contents/repository"
|
||||
minioStorage "web-qudo-be/config/config"
|
||||
"web-qudo-be/utils/storage"
|
||||
|
|
@ -15,6 +16,7 @@ import (
|
|||
type partnerContentService struct {
|
||||
Repo repository.PartnerContentRepository
|
||||
MinioStorage *minioStorage.MinioStorage
|
||||
MediaLib medialib.MediaLibraryService
|
||||
Log zerolog.Logger
|
||||
}
|
||||
|
||||
|
|
@ -29,11 +31,13 @@ type PartnerContentService interface {
|
|||
func NewPartnerContentService(
|
||||
repo repository.PartnerContentRepository,
|
||||
minio *minioStorage.MinioStorage,
|
||||
mediaLib medialib.MediaLibraryService,
|
||||
log zerolog.Logger,
|
||||
) PartnerContentService {
|
||||
return &partnerContentService{
|
||||
Repo: repo,
|
||||
MinioStorage: minio,
|
||||
MediaLib: mediaLib,
|
||||
Log: log,
|
||||
}
|
||||
}
|
||||
|
|
@ -75,7 +79,13 @@ func (s *partnerContentService) UploadLogo(id uuid.UUID, file *multipart.FileHea
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.Repo.UpdateImageFields(id, key, url)
|
||||
if err := s.Repo.UpdateImageFields(id, key, url); err != nil {
|
||||
return err
|
||||
}
|
||||
if s.MediaLib != nil {
|
||||
_ = s.MediaLib.RegisterCMSAsset(url, key, "partner_logo", file)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *partnerContentService) Delete(id uuid.UUID) error {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"github.com/rs/zerolog"
|
||||
|
||||
"web-qudo-be/app/database/entity"
|
||||
medialib "web-qudo-be/app/module/media_library/service"
|
||||
"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"
|
||||
|
|
@ -15,6 +16,7 @@ import (
|
|||
type popupNewsContentImagesService struct {
|
||||
Repo repository.PopupNewsContentImagesRepository
|
||||
MinioStorage *minioStorage.MinioStorage
|
||||
MediaLib medialib.MediaLibraryService
|
||||
Log zerolog.Logger
|
||||
}
|
||||
|
||||
|
|
@ -68,6 +70,9 @@ func (_i *popupNewsContentImagesService) SaveWithFile(popupNewsContentID uint, f
|
|||
if err := _i.Repo.Create(row); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _i.MediaLib != nil {
|
||||
_ = _i.MediaLib.RegisterCMSAsset(url, key, "popup_news", file)
|
||||
}
|
||||
return row, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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_content_submissions"
|
||||
"web-qudo-be/app/module/cms_media"
|
||||
"web-qudo-be/app/module/custom_static_pages"
|
||||
"web-qudo-be/app/module/districts"
|
||||
|
|
@ -28,6 +29,7 @@ import (
|
|||
hero_content "web-qudo-be/app/module/hero_contents"
|
||||
"web-qudo-be/app/module/magazine_files"
|
||||
"web-qudo-be/app/module/magazines"
|
||||
"web-qudo-be/app/module/media_library"
|
||||
"web-qudo-be/app/module/master_menus"
|
||||
"web-qudo-be/app/module/master_modules"
|
||||
"web-qudo-be/app/module/our_product_content_images"
|
||||
|
|
@ -73,6 +75,7 @@ type Router struct {
|
|||
CitiesRouter *cities.CitiesRouter
|
||||
ClientApprovalSettingsRouter *client_approval_settings.ClientApprovalSettingsRouter
|
||||
ClientsRouter *clients.ClientsRouter
|
||||
CmsContentSubmissionsRouter *cms_content_submissions.CmsContentSubmissionsRouter
|
||||
CmsMediaRouter *cms_media.CmsMediaRouter
|
||||
HeroContentsRouter *hero_content.HeroContentsRouter
|
||||
HeroContentImagesRouter *hero_content_image.HeroContentImagesRouter
|
||||
|
|
@ -81,6 +84,7 @@ type Router struct {
|
|||
FeedbacksRouter *feedbacks.FeedbacksRouter
|
||||
MagazineFilesRouter *magazine_files.MagazineFilesRouter
|
||||
MagazinesRouter *magazines.MagazinesRouter
|
||||
MediaLibraryRouter *media_library.MediaLibraryRouter
|
||||
MasterMenusRouter *master_menus.MasterMenusRouter
|
||||
MasterModulesRouter *master_modules.MasterModulesRouter
|
||||
OurProductContentsRouter *our_product_contents.OurProductContentsRouter
|
||||
|
|
@ -121,6 +125,7 @@ func NewRouter(
|
|||
citiesRouter *cities.CitiesRouter,
|
||||
clientApprovalSettingsRouter *client_approval_settings.ClientApprovalSettingsRouter,
|
||||
clientsRouter *clients.ClientsRouter,
|
||||
cmsContentSubmissionsRouter *cms_content_submissions.CmsContentSubmissionsRouter,
|
||||
cmsMediaRouter *cms_media.CmsMediaRouter,
|
||||
heroContentsRouter *hero_content.HeroContentsRouter,
|
||||
heroContentImagesRouter *hero_content_image.HeroContentImagesRouter,
|
||||
|
|
@ -129,6 +134,7 @@ func NewRouter(
|
|||
feedbacksRouter *feedbacks.FeedbacksRouter,
|
||||
magazineFilesRouter *magazine_files.MagazineFilesRouter,
|
||||
magazinesRouter *magazines.MagazinesRouter,
|
||||
mediaLibraryRouter *media_library.MediaLibraryRouter,
|
||||
masterMenuRouter *master_menus.MasterMenusRouter,
|
||||
masterModuleRouter *master_modules.MasterModulesRouter,
|
||||
ourProductContentsRouter *our_product_contents.OurProductContentsRouter,
|
||||
|
|
@ -168,6 +174,7 @@ func NewRouter(
|
|||
CitiesRouter: citiesRouter,
|
||||
ClientApprovalSettingsRouter: clientApprovalSettingsRouter,
|
||||
ClientsRouter: clientsRouter,
|
||||
CmsContentSubmissionsRouter: cmsContentSubmissionsRouter,
|
||||
CmsMediaRouter: cmsMediaRouter,
|
||||
HeroContentsRouter: heroContentsRouter,
|
||||
HeroContentImagesRouter: heroContentImagesRouter,
|
||||
|
|
@ -176,6 +183,7 @@ func NewRouter(
|
|||
FeedbacksRouter: feedbacksRouter,
|
||||
MagazineFilesRouter: magazineFilesRouter,
|
||||
MagazinesRouter: magazinesRouter,
|
||||
MediaLibraryRouter: mediaLibraryRouter,
|
||||
MasterMenusRouter: masterMenuRouter,
|
||||
MasterModulesRouter: masterModuleRouter,
|
||||
OurProductContentsRouter: ourProductContentsRouter,
|
||||
|
|
@ -225,6 +233,7 @@ func (r *Router) Register() {
|
|||
r.CitiesRouter.RegisterCitiesRoutes()
|
||||
r.ClientApprovalSettingsRouter.RegisterClientApprovalSettingsRoutes()
|
||||
r.ClientsRouter.RegisterClientsRoutes()
|
||||
r.CmsContentSubmissionsRouter.RegisterCmsContentSubmissionsRoutes()
|
||||
r.CmsMediaRouter.RegisterCmsMediaRoutes()
|
||||
r.HeroContentsRouter.RegisterHeroContentsRoutes()
|
||||
r.HeroContentImagesRouter.RegisterHeroContentImagesRoutes()
|
||||
|
|
@ -233,6 +242,7 @@ func (r *Router) Register() {
|
|||
r.FeedbacksRouter.RegisterFeedbacksRoutes()
|
||||
r.MagazinesRouter.RegisterMagazinesRoutes()
|
||||
r.MagazineFilesRouter.RegisterMagazineFilesRoutes()
|
||||
r.MediaLibraryRouter.RegisterMediaLibraryRoutes()
|
||||
r.MasterMenusRouter.RegisterMasterMenusRoutes()
|
||||
r.MasterModulesRouter.RegisterMasterModulesRoutes()
|
||||
r.OurProductContentsRouter.RegisterOurProductContentsRoutes()
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ 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 = true
|
||||
production = false
|
||||
body-limit = 1048576000 # "100 * 1024 * 1024"
|
||||
|
||||
[db.postgres]
|
||||
|
|
|
|||
4
main.go
4
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_content_submissions"
|
||||
"web-qudo-be/app/module/cms_media"
|
||||
"web-qudo-be/app/module/custom_static_pages"
|
||||
"web-qudo-be/app/module/districts"
|
||||
|
|
@ -31,6 +32,7 @@ import (
|
|||
hero_content "web-qudo-be/app/module/hero_contents"
|
||||
"web-qudo-be/app/module/magazine_files"
|
||||
"web-qudo-be/app/module/magazines"
|
||||
"web-qudo-be/app/module/media_library"
|
||||
"web-qudo-be/app/module/master_menus"
|
||||
"web-qudo-be/app/module/master_modules"
|
||||
"web-qudo-be/app/module/our_product_content_images"
|
||||
|
|
@ -105,6 +107,7 @@ func main() {
|
|||
cities.NewCitiesModule,
|
||||
client_approval_settings.NewClientApprovalSettingsModule,
|
||||
clients.NewClientsModule,
|
||||
cms_content_submissions.NewCmsContentSubmissionsModule,
|
||||
cms_media.NewCmsMediaModule,
|
||||
custom_static_pages.NewCustomStaticPagesModule,
|
||||
districts.NewDistrictsModule,
|
||||
|
|
@ -113,6 +116,7 @@ func main() {
|
|||
hero_content_image.NewHeroContentImagesModule,
|
||||
magazines.NewMagazinesModule,
|
||||
magazine_files.NewMagazineFilesModule,
|
||||
media_library.NewMediaLibraryModule,
|
||||
master_menus.NewMasterMenusModule,
|
||||
master_modules.NewMasterModulesModule,
|
||||
our_product_contents.NewOurProductContentsModule,
|
||||
|
|
|
|||
|
|
@ -31,13 +31,25 @@ var mediaExts = map[string]bool{
|
|||
".mp4": true, ".webm": true,
|
||||
}
|
||||
|
||||
// mediaLibraryExts = images + video + audio + common documents (admin Media Library upload).
|
||||
var mediaLibraryExts = map[string]bool{
|
||||
".jpg": true, ".jpeg": true, ".png": true, ".gif": true, ".webp": true, ".svg": true,
|
||||
".mp4": true, ".webm": true, ".mov": true,
|
||||
".mp3": true, ".wav": true, ".ogg": true, ".m4a": true,
|
||||
".pdf": true, ".doc": true, ".docx": true, ".txt": true, ".csv": true,
|
||||
}
|
||||
|
||||
// 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")
|
||||
}
|
||||
ext := strings.ToLower(filepath.Ext(file.Filename))
|
||||
if allowVideo {
|
||||
if folder == "media-library" {
|
||||
if !mediaLibraryExts[ext] {
|
||||
return "", "", fmt.Errorf("unsupported file type for media library")
|
||||
}
|
||||
} else if allowVideo {
|
||||
if !mediaExts[ext] {
|
||||
return "", "", fmt.Errorf("unsupported file type (allowed: images, mp4, webm)")
|
||||
}
|
||||
|
|
@ -74,3 +86,15 @@ func UploadCMSObject(ms *appcfg.MinioStorage, folder string, file *multipart.Fil
|
|||
|
||||
return objectKey, CMSPreviewURL(ms.Cfg, objectKey), nil
|
||||
}
|
||||
|
||||
// UploadMediaLibraryObject stores under cms/media-library/... with a broader MIME allowlist.
|
||||
func UploadMediaLibraryObject(ms *appcfg.MinioStorage, file *multipart.FileHeader) (objectKey string, previewURL string, err error) {
|
||||
if file == nil {
|
||||
return "", "", fmt.Errorf("file is required")
|
||||
}
|
||||
ext := strings.ToLower(filepath.Ext(file.Filename))
|
||||
if !mediaLibraryExts[ext] {
|
||||
return "", "", fmt.Errorf("unsupported file type for media library")
|
||||
}
|
||||
return UploadCMSObject(ms, "media-library", file, false)
|
||||
}
|
||||
|
|
|
|||
BIN
web-qudo-be.exe
BIN
web-qudo-be.exe
Binary file not shown.
Loading…
Reference in New Issue