feat: update all cms modules

This commit is contained in:
hanif salafi 2026-04-10 14:23:24 +07:00
parent 0987814407
commit 706b3a4585
28 changed files with 284 additions and 104 deletions

View File

@ -11,8 +11,7 @@ type AboutUsContentImage struct {
CreatedAt time.Time `json:"created_at" gorm:"default:now()"`
UpdatedAt time.Time `json:"updated_at" gorm:"default:now()"`
// relation (optional tapi bagus)
AboutUsContent AboutUsContent `json:"about_us_content" gorm:"foreignKey:AboutUsContentID"`
AboutUsContent AboutUsContent `json:"-" gorm:"foreignKey:AboutUsContentID"`
}
func (AboutUsContentImage) TableName() string {

View File

@ -12,6 +12,8 @@ type AboutUsContent struct {
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()"`
Images []AboutUsContentImage `json:"images,omitempty" gorm:"foreignKey:AboutUsContentID"`
}
func (AboutUsContent) TableName() string {

View File

@ -7,7 +7,7 @@ import (
)
type HeroContents struct {
ID uuid.UUID `json:"id" gorm:"primaryKey;type:uuid;default:uuid_generate_v4()"`
ID uuid.UUID `json:"id" gorm:"primaryKey;type:uuid"`
PrimaryTitle string `json:"primary_title" gorm:"type:varchar(255)"`
SecondaryTitle string `json:"secondary_title" gorm:"type:varchar(255)"`
Description string `json:"description" gorm:"type:text"`

View File

@ -7,7 +7,7 @@ import (
)
type HeroContentImages struct {
ID uuid.UUID `json:"id" gorm:"primaryKey;type:uuid;default:uuid_generate_v4()"`
ID uuid.UUID `json:"id" gorm:"primaryKey;type:uuid"`
HeroContentID uuid.UUID `json:"hero_content_id" gorm:"type:uuid;not null"`
ImagePath string `json:"image_path" gorm:"type:text"`
ImageURL string `json:"image_url" gorm:"type:text"`

View File

@ -5,7 +5,7 @@ import (
)
type OurProductContentImage struct {
ID uuid.UUID `json:"id" gorm:"primaryKey;type:uuid;default:uuid_generate_v4()"`
ID uuid.UUID `json:"id" gorm:"primaryKey;type:uuid"`
OurProductContentID uuid.UUID `json:"our_product_content_id" gorm:"type:uuid"`
ImagePath string `json:"image_path" gorm:"type:varchar(255)"`
ImageURL string `json:"image_url" gorm:"type:text"`

View File

@ -7,7 +7,7 @@ import (
)
type OurProductContent struct {
ID uuid.UUID `json:"id" gorm:"primaryKey;type:uuid;default:uuid_generate_v4()"`
ID uuid.UUID `json:"id" gorm:"primaryKey;type:uuid"`
PrimaryTitle string `json:"primary_title" gorm:"type:varchar(255)"`
SecondaryTitle string `json:"secondary_title" gorm:"type:varchar(255)"`
Description string `json:"description" gorm:"type:text"`

View File

@ -7,7 +7,7 @@ import (
)
type PartnerContent struct {
ID uuid.UUID `json:"id" gorm:"primaryKey;type:uuid;default:uuid_generate_v4()"`
ID uuid.UUID `json:"id" gorm:"primaryKey;type:uuid"`
PrimaryTitle string `json:"primary_title" gorm:"type:varchar(255)"`
ImagePath string `json:"image_path" gorm:"type:varchar(255)"`
ImageURL string `json:"image_url" gorm:"type:text"`

View File

@ -103,13 +103,13 @@ func Models() []interface{} {
entity.Bookmarks{},
entity.Cities{},
entity.Clients{},
entity.HeroContents{},
entity.HeroContentImages{},
entity.ClientApprovalSettings{},
entity.CsrfTokenRecords{},
entity.CustomStaticPages{},
entity.Districts{},
entity.Feedbacks{},
entity.HeroContents{},
entity.HeroContentImages{},
entity.ForgotPasswords{},
entity.Magazines{},
entity.MagazineFiles{},
@ -121,7 +121,11 @@ func Models() []interface{} {
entity.OneTimePasswords{},
entity.OurProductContent{},
entity.OurProductContentImage{},
entity.OurServiceContent{},
entity.OurServiceContentImage{},
entity.PartnerContent{},
entity.PopupNewsContents{},
entity.PopupNewsContentImages{},
entity.Subscription{},
entity.Schedules{},
entity.UserLevels{},

View File

@ -46,6 +46,7 @@ func (_i *AboutUsContentImageRouter) RegisterAboutUsContentImageRoutes() {
_i.App.Route("/about-us-content-images", func(router fiber.Router) {
router.Get("/", aboutUsContentImageController.All)
router.Post("/url", aboutUsContentImageController.SaveRemote)
router.Get("/:id", aboutUsContentImageController.Show)
// upload image (pakai form-data)

View File

@ -3,12 +3,14 @@ package controller
import (
"strconv"
"web-qudo-be/app/module/about_us_content_images/request"
"web-qudo-be/app/module/about_us_content_images/service"
"github.com/gofiber/fiber/v2"
"github.com/rs/zerolog"
utilRes "web-qudo-be/utils/response"
utilVal "web-qudo-be/utils/validator"
)
type aboutUsContentImageController struct {
@ -20,6 +22,7 @@ type AboutUsContentImageController interface {
All(c *fiber.Ctx) error
Show(c *fiber.Ctx) error
Save(c *fiber.Ctx) error
SaveRemote(c *fiber.Ctx) error
Delete(c *fiber.Ctx) error
}
@ -95,6 +98,24 @@ func (_i *aboutUsContentImageController) Save(c *fiber.Ctx) error {
})
}
// SaveRemote JSON: public URL for image or video (e.g. CDN .mp4)
func (_i *aboutUsContentImageController) SaveRemote(c *fiber.Ctx) error {
req := new(request.AboutUsContentImageRemoteRequest)
if err := utilVal.ParseAndValidate(c, req); err != nil {
return err
}
result, err := _i.service.SaveRemoteURL(req.AboutUsContentID, req.MediaURL, req.MediaType)
if err != nil {
_i.Log.Error().Err(err).Msg("failed save remote about us media")
return err
}
return utilRes.Resp(c, utilRes.Response{
Success: true,
Messages: utilRes.Messages{"About us media URL saved"},
Data: result,
})
}
// DELETE
func (_i *aboutUsContentImageController) Delete(c *fiber.Ctx) error {
id, err := strconv.Atoi(c.Params("id"))

View File

@ -35,9 +35,7 @@ func NewAboutUsContentImageRepository(db *database.Database, log zerolog.Logger)
// GET ALL
func (_i *aboutUsContentImageRepository) GetAll() (images []*entity.AboutUsContentImage, err error) {
err = _i.DB.DB.
Where("is_active = ?", true).
Find(&images).Error
err = _i.DB.DB.Find(&images).Error
return
}
@ -52,7 +50,7 @@ func (_i *aboutUsContentImageRepository) FindOne(id uint) (image *entity.AboutUs
// GET BY ABOUT US CONTENT ID
func (_i *aboutUsContentImageRepository) FindByContentID(contentID uint) (images []*entity.AboutUsContentImage, err error) {
err = _i.DB.DB.
Where("about_us_content_id = ? AND is_active = ?", contentID, true).
Where("about_us_content_id = ?", contentID).
Find(&images).Error
return
}

View File

@ -0,0 +1,7 @@
package request
type AboutUsContentImageRemoteRequest struct {
AboutUsContentID uint `json:"about_us_content_id" validate:"required"`
MediaURL string `json:"media_url" validate:"required"`
MediaType string `json:"media_type"`
}

View File

@ -4,6 +4,7 @@ import (
"fmt"
"mime/multipart"
"path/filepath"
"strings"
"time"
"web-qudo-be/app/database/entity"
"web-qudo-be/app/module/about_us_content_images/repository"
@ -22,6 +23,7 @@ type AboutUsContentImageService interface {
All() (images []*entity.AboutUsContentImage, err error)
Show(id uint) (image *entity.AboutUsContentImage, err error)
Save(aboutUsContentId uint, file *multipart.FileHeader) (image *entity.AboutUsContentImage, err error)
SaveRemoteURL(aboutUsContentID uint, mediaURL, mediaType string) (image *entity.AboutUsContentImage, err error)
Delete(id uint) error
}
@ -50,9 +52,8 @@ func (_i *aboutUsContentImageService) Save(aboutUsContentId uint, file *multipar
Str("filename", file.Filename).
Msg("upload image")
// validasi file
ext := filepath.Ext(file.Filename)
if ext != ".jpg" && ext != ".jpeg" && ext != ".png" {
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")
}
@ -68,10 +69,14 @@ func (_i *aboutUsContentImageService) Save(aboutUsContentId uint, file *multipar
}
// save ke DB
mt := "image/" + strings.TrimPrefix(ext, ".")
if ext == ".mp4" || ext == ".webm" {
mt = "video/" + strings.TrimPrefix(ext, ".")
}
data := &entity.AboutUsContentImage{
AboutUsContentID: aboutUsContentId,
MediaPath: filePath,
MediaType: ext,
MediaType: mt,
MediaURL: "/uploads/" + filename,
}
@ -84,6 +89,27 @@ func (_i *aboutUsContentImageService) Save(aboutUsContentId uint, file *multipar
return result, nil
}
func (_i *aboutUsContentImageService) SaveRemoteURL(aboutUsContentID uint, mediaURL, mediaType string) (image *entity.AboutUsContentImage, err error) {
if strings.TrimSpace(mediaURL) == "" {
return nil, fmt.Errorf("media_url is required")
}
mt := mediaType
if mt == "" {
lower := strings.ToLower(mediaURL)
if strings.HasSuffix(lower, ".mp4") || strings.Contains(lower, "video") {
mt = "video/mp4"
} else {
mt = "image/url"
}
}
data := &entity.AboutUsContentImage{
AboutUsContentID: aboutUsContentID,
MediaURL: mediaURL,
MediaType: mt,
}
return _i.Repo.Create(data)
}
func (_i *aboutUsContentImageService) Delete(id uint) error {
return _i.Repo.Delete(id)
}

View File

@ -3,6 +3,7 @@ package controller
import (
"strconv"
"web-qudo-be/app/module/about_us_contents/request"
"web-qudo-be/app/module/about_us_contents/service"
"github.com/gofiber/fiber/v2"
@ -71,13 +72,13 @@ func (_i *aboutUsContentController) Show(c *fiber.Ctx) error {
// CREATE
func (_i *aboutUsContentController) Save(c *fiber.Ctx) error {
req := new(map[string]interface{})
req := new(request.AboutUsContentCreateRequest)
if err := utilVal.ParseAndValidate(c, req); err != nil {
return err
}
result, err := _i.service.Save(*req)
result, err := _i.service.Save(req.ToEntity())
if err != nil {
_i.Log.Error().Err(err).Msg("failed create about us content")
return err
@ -97,13 +98,13 @@ func (_i *aboutUsContentController) Update(c *fiber.Ctx) error {
return err
}
req := new(map[string]interface{})
req := new(request.AboutUsContentUpdateRequest)
if err := utilVal.ParseAndValidate(c, req); err != nil {
return err
}
err = _i.service.Update(uint(id), *req)
err = _i.service.Update(uint(id), req.ToEntity())
if err != nil {
_i.Log.Error().Err(err).Msg("failed update about us content")
return err

View File

@ -34,7 +34,9 @@ func (r *aboutUsContentRepository) GetAll() ([]*entity.AboutUsContent, error) {
var results []*entity.AboutUsContent
err := r.DB.DB.
Where("is_active = ?", true).
Preload("Images").
Where("is_active IS NULL OR is_active = ?", true).
Order("id ASC").
Find(&results).Error
if err != nil {
@ -69,12 +71,24 @@ func (r *aboutUsContentRepository) Create(data *entity.AboutUsContent) (*entity.
return data, nil
}
// UPDATE
// Update uses a column map so we never SET primary key / created_at to zero values (breaks FK children).
func (r *aboutUsContentRepository) Update(id uint, data *entity.AboutUsContent) error {
updates := map[string]interface{}{
"primary_title": data.PrimaryTitle,
"secondary_title": data.SecondaryTitle,
"description": data.Description,
"primary_cta": data.PrimaryCta,
"secondary_cta_text": data.SecondaryCtaText,
"updated_at": data.UpdatedAt,
}
if data.IsActive != nil {
updates["is_active"] = data.IsActive
}
err := r.DB.DB.
Model(&entity.AboutUsContent{}).
Where("id = ?", id).
Updates(data).Error
Updates(updates).Error
if err != nil {
r.Log.Error().Err(err).Msg("failed update about us content")

View File

@ -1,6 +1,8 @@
package service
import (
"time"
"github.com/rs/zerolog"
"web-qudo-be/app/database/entity"
@ -15,8 +17,8 @@ type aboutUsContentService struct {
type AboutUsContentService interface {
All() ([]*entity.AboutUsContent, error)
Show(id uint) (*entity.AboutUsContent, error)
Save(data map[string]interface{}) (*entity.AboutUsContent, error)
Update(id uint, data map[string]interface{}) error
Save(data *entity.AboutUsContent) (*entity.AboutUsContent, error)
Update(id uint, data *entity.AboutUsContent) error
Delete(id uint) error
}
@ -53,26 +55,8 @@ func (s *aboutUsContentService) Show(id uint) (*entity.AboutUsContent, error) {
}
// CREATE
func (s *aboutUsContentService) Save(data map[string]interface{}) (*entity.AboutUsContent, error) {
entityData := &entity.AboutUsContent{}
if v, ok := data["primary_title"].(string); ok {
entityData.PrimaryTitle = v
}
if v, ok := data["secondary_title"].(string); ok {
entityData.SecondaryTitle = v
}
if v, ok := data["description"].(string); ok {
entityData.Description = v
}
if v, ok := data["primary_cta"].(string); ok {
entityData.PrimaryCta = v
}
if v, ok := data["secondary_cta_text"].(string); ok {
entityData.SecondaryCtaText = v
}
result, err := s.Repo.Create(entityData)
func (s *aboutUsContentService) Save(data *entity.AboutUsContent) (*entity.AboutUsContent, error) {
result, err := s.Repo.Create(data)
if err != nil {
s.Log.Error().Err(err).Msg("failed create about us content")
return nil, err
@ -82,26 +66,8 @@ func (s *aboutUsContentService) Save(data map[string]interface{}) (*entity.About
}
// UPDATE
func (s *aboutUsContentService) Update(id uint, data map[string]interface{}) error {
entityData := &entity.AboutUsContent{}
if v, ok := data["primary_title"].(string); ok {
entityData.PrimaryTitle = v
}
if v, ok := data["secondary_title"].(string); ok {
entityData.SecondaryTitle = v
}
if v, ok := data["description"].(string); ok {
entityData.Description = v
}
if v, ok := data["primary_cta"].(string); ok {
entityData.PrimaryCta = v
}
if v, ok := data["secondary_cta_text"].(string); ok {
entityData.SecondaryCtaText = v
}
err := s.Repo.Update(id, entityData)
func (s *aboutUsContentService) Update(id uint, data *entity.AboutUsContent) error {
err := s.Repo.Update(id, data)
if err != nil {
s.Log.Error().Err(err).Msg("failed update about us content")
return err
@ -119,6 +85,7 @@ func (s *aboutUsContentService) Delete(id uint) error {
isActive := false
result.IsActive = &isActive
result.UpdatedAt = time.Now()
return s.Repo.Update(id, result)
}

View File

@ -1,6 +1,8 @@
package controller
import (
"strings"
"github.com/gofiber/fiber/v2"
"github.com/google/uuid"
"github.com/rs/zerolog"

View File

@ -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/hero_content_images/repository"
minioStorage "web-qudo-be/config/config"
"web-qudo-be/utils/storage"
)
type heroContentImagesService struct {
Repo repository.HeroContentImagesRepository
MinioStorage *minioStorage.MinioStorage
Log zerolog.Logger
}
type HeroContentImagesService interface {
FindByHeroID(heroID uuid.UUID) (*entity.HeroContentImages, error)
Save(data *entity.HeroContentImages) (*entity.HeroContentImages, error)
SaveWithFile(heroContentID uuid.UUID, file *multipart.FileHeader) (*entity.HeroContentImages, error)
Update(id uuid.UUID, data *entity.HeroContentImages) error
UpdateWithFile(id uuid.UUID, file *multipart.FileHeader) error
Delete(id uuid.UUID) error
}
func NewHeroContentImagesService(
repo repository.HeroContentImagesRepository,
minio *minioStorage.MinioStorage,
log zerolog.Logger,
) HeroContentImagesService {
return &heroContentImagesService{
Repo: repo,
MinioStorage: minio,
Log: log,
}
}
@ -52,6 +61,19 @@ func (s *heroContentImagesService) Save(data *entity.HeroContentImages) (*entity
return result, nil
}
func (s *heroContentImagesService) SaveWithFile(heroContentID uuid.UUID, file *multipart.FileHeader) (*entity.HeroContentImages, error) {
key, url, err := storage.UploadCMSObject(s.MinioStorage, "hero", file, false)
if err != nil {
return nil, err
}
data := &entity.HeroContentImages{
HeroContentID: heroContentID,
ImagePath: key,
ImageURL: url,
}
return s.Save(data)
}
func (s *heroContentImagesService) Update(id uuid.UUID, data *entity.HeroContentImages) error {
err := s.Repo.Update(id, data)
if err != nil {
@ -62,6 +84,17 @@ func (s *heroContentImagesService) Update(id uuid.UUID, data *entity.HeroContent
return nil
}
func (s *heroContentImagesService) UpdateWithFile(id uuid.UUID, file *multipart.FileHeader) error {
key, url, err := storage.UploadCMSObject(s.MinioStorage, "hero", file, false)
if err != nil {
return err
}
return s.Repo.Update(id, &entity.HeroContentImages{
ImagePath: key,
ImageURL: url,
})
}
func (s *heroContentImagesService) Delete(id uuid.UUID) error {
err := s.Repo.Delete(id)
if err != nil {

View File

@ -1,11 +1,13 @@
package repository
import (
"errors"
"web-qudo-be/app/database"
"web-qudo-be/app/database/entity"
"github.com/google/uuid"
"github.com/rs/zerolog"
"gorm.io/gorm"
)
type heroContentsRepository struct {
@ -35,6 +37,9 @@ func (r *heroContentsRepository) Get() (*entity.HeroContents, error) {
First(&data).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, nil
}
r.Log.Error().Err(err).Msg("failed get hero content")
return nil, err
}

View File

@ -32,7 +32,7 @@ func NewOurProductContentImagesController(service service.OurProductContentImage
}
func (_i *ourProductContentImagesController) FindByOurProductContentID(c *fiber.Ctx) error {
contentIDStr := c.Params("our_product_content_id")
contentIDStr := c.Params("content_id")
contentID, err := uuid.Parse(contentIDStr)
if err != nil {

View File

@ -1,11 +1,13 @@
package repository
import (
"errors"
"web-qudo-be/app/database"
"web-qudo-be/app/database/entity"
"github.com/google/uuid"
"github.com/rs/zerolog"
"gorm.io/gorm"
)
type ourProductContentRepository struct {
@ -15,6 +17,7 @@ type ourProductContentRepository struct {
type OurProductContentRepository interface {
Get() (*entity.OurProductContent, error)
GetAll() ([]entity.OurProductContent, error)
Create(data *entity.OurProductContent) (*entity.OurProductContent, error)
Update(id uuid.UUID, data *entity.OurProductContent) error
Delete(id uuid.UUID) error
@ -35,6 +38,9 @@ func (r *ourProductContentRepository) Get() (*entity.OurProductContent, error) {
First(&data).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, nil
}
r.Log.Error().Err(err).Msg("failed get our product content")
return nil, err
}
@ -42,6 +48,19 @@ func (r *ourProductContentRepository) Get() (*entity.OurProductContent, error) {
return &data, nil
}
func (r *ourProductContentRepository) GetAll() ([]entity.OurProductContent, error) {
var rows []entity.OurProductContent
err := r.DB.DB.
Preload("Images").
Order("created_at ASC").
Find(&rows).Error
if err != nil {
r.Log.Error().Err(err).Msg("failed list our product contents")
return nil, err
}
return rows, nil
}
func (r *ourProductContentRepository) Create(data *entity.OurProductContent) (*entity.OurProductContent, error) {
data.ID = uuid.New()

View File

@ -16,7 +16,7 @@ type ourProductContentService struct {
}
type OurProductContentService interface {
Show() (*entity.OurProductContent, error)
Show() ([]entity.OurProductContent, error)
Save(data *entity.OurProductContent) (*entity.OurProductContent, error)
Update(id uuid.UUID, data *entity.OurProductContent) error
Delete(id uuid.UUID) error
@ -34,14 +34,13 @@ func NewOurProductContentService(
}
}
func (s *ourProductContentService) Show() (*entity.OurProductContent, error) {
data, err := s.Repo.Get()
func (s *ourProductContentService) Show() ([]entity.OurProductContent, error) {
rows, err := s.Repo.GetAll()
if err != nil {
s.Log.Error().Err(err).Msg("failed get our product content")
s.Log.Error().Err(err).Msg("failed list our product contents")
return nil, err
}
return data, nil
return rows, nil
}
func (s *ourProductContentService) Save(data *entity.OurProductContent) (*entity.OurProductContent, error) {
@ -67,13 +66,5 @@ func (s *ourProductContentService) Update(id uuid.UUID, data *entity.OurProductC
}
func (s *ourProductContentService) Delete(id uuid.UUID) error {
result, err := s.Repo.Get()
if err != nil {
return err
}
isActive := false
result.IsActive = &isActive
return s.Repo.Update(id, result)
return s.Repo.Delete(id)
}

View File

@ -1,10 +1,12 @@
package repository
import (
"errors"
"web-qudo-be/app/database"
"web-qudo-be/app/database/entity"
"github.com/rs/zerolog"
"gorm.io/gorm"
)
type ourServiceContentRepository struct {
@ -14,6 +16,7 @@ type ourServiceContentRepository struct {
type OurServiceContentRepository interface {
Get() (*entity.OurServiceContent, error)
GetAll() ([]entity.OurServiceContent, error)
Create(data *entity.OurServiceContent) (*entity.OurServiceContent, error)
Update(id uint, data *entity.OurServiceContent) error
Delete(id uint) error
@ -34,6 +37,9 @@ func (r *ourServiceContentRepository) Get() (*entity.OurServiceContent, error) {
First(&data).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, nil
}
r.Log.Error().Err(err).Msg("failed get our service content")
return nil, err
}
@ -41,6 +47,19 @@ func (r *ourServiceContentRepository) Get() (*entity.OurServiceContent, error) {
return &data, nil
}
func (r *ourServiceContentRepository) GetAll() ([]entity.OurServiceContent, error) {
var rows []entity.OurServiceContent
err := r.DB.DB.
Preload("Images").
Order("created_at ASC").
Find(&rows).Error
if err != nil {
r.Log.Error().Err(err).Msg("failed list our service contents")
return nil, err
}
return rows, nil
}
func (r *ourServiceContentRepository) Create(data *entity.OurServiceContent) (*entity.OurServiceContent, error) {
err := r.DB.DB.Create(data).Error
if err != nil {

View File

@ -15,7 +15,7 @@ type ourServiceContentService struct {
}
type OurServiceContentService interface {
Show() (*entity.OurServiceContent, error)
Show() ([]entity.OurServiceContent, error)
Save(data *entity.OurServiceContent) (*entity.OurServiceContent, error)
Update(id uint, data *entity.OurServiceContent) error
Delete(id uint) error
@ -33,14 +33,13 @@ func NewOurServiceContentService(
}
}
func (s *ourServiceContentService) Show() (*entity.OurServiceContent, error) {
data, err := s.Repo.Get()
func (s *ourServiceContentService) Show() ([]entity.OurServiceContent, error) {
rows, err := s.Repo.GetAll()
if err != nil {
s.Log.Error().Err(err).Msg("failed get our service content")
s.Log.Error().Err(err).Msg("failed list our service contents")
return nil, err
}
return data, nil
return rows, nil
}
func (s *ourServiceContentService) Save(data *entity.OurServiceContent) (*entity.OurServiceContent, error) {
@ -75,13 +74,5 @@ func (s *ourServiceContentService) Update(id uint, data *entity.OurServiceConten
}
func (s *ourServiceContentService) Delete(id uint) error {
result, err := s.Repo.Get()
if err != nil {
return err
}
isActive := false
result.IsActive = &isActive
return s.Repo.Update(id, result)
return s.Repo.Delete(id)
}

View File

@ -2,6 +2,7 @@ package config
import (
"context"
"fmt"
"log"
"github.com/minio/minio-go/v7"
@ -69,3 +70,13 @@ func (_minio *MinioStorage) ConnectMinio() (*minio.Client, error) {
log.Printf("[MinIO] Successfully connected to MinIO and bucket '%s' is ready", bucketName)
return minioClient, nil
}
// PublicObjectURL builds a path-style URL for public reads (bucket policy must allow GET).
func (m *MinioStorage) PublicObjectURL(objectKey string) string {
o := m.Cfg.ObjectStorage.MinioStorage
scheme := "http"
if o.UseSSL {
scheme = "https"
}
return fmt.Sprintf("%s://%s/%s/%s", scheme, o.Endpoint, o.BucketName, objectKey)
}

View File

@ -12,7 +12,7 @@ production = false
body-limit = 1048576000 # "100 * 1024 * 1024"
[db.postgres]
dsn = "postgresql://medols_user:MedolsDB@2025@38.47.185.79:5432/medols_db" # <driver>://<username>:<password>@<host>:<port>/<database>
dsn = "postgresql://qudo_user:QudoDB@2026@38.47.185.79:5432/qudo_db" # <driver>://<username>:<password>@<host>:<port>/<database>
log-mode = "ERROR"
migrate = true
seed = false
@ -28,7 +28,7 @@ endpoint = "is3.cloudhost.id"
access-key-id = "YRP1RM617986USRU6NN8"
secret-access-key = "vfbwQDYb1m7nfzo4LVEz90BIyOWfBMZ6bfGQbqDO"
use-ssl = true
bucket-name = "mikulnews"
bucket-name = "qudo"
location = "us-east-1"
[middleware.compress]

View File

@ -0,0 +1,69 @@
package storage
import (
"context"
"fmt"
"mime"
"mime/multipart"
"path/filepath"
"strings"
"time"
"github.com/google/uuid"
"github.com/minio/minio-go/v7"
appcfg "web-qudo-be/config/config"
)
var imageExts = map[string]bool{
".jpg": true, ".jpeg": true, ".png": true, ".gif": true, ".webp": true,
}
var mediaExts = map[string]bool{
".jpg": true, ".jpeg": true, ".png": true, ".gif": true, ".webp": true,
".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) {
if file == nil {
return "", "", fmt.Errorf("file is required")
}
ext := strings.ToLower(filepath.Ext(file.Filename))
if allowVideo {
if !mediaExts[ext] {
return "", "", fmt.Errorf("unsupported file type (allowed: images, mp4, webm)")
}
} else if !imageExts[ext] {
return "", "", fmt.Errorf("unsupported image type")
}
client, err := ms.ConnectMinio()
if err != nil {
return "", "", err
}
src, err := file.Open()
if err != nil {
return "", "", err
}
defer src.Close()
bucket := ms.Cfg.ObjectStorage.MinioStorage.BucketName
now := time.Now()
objectKey = fmt.Sprintf("cms/%s/%d/%02d/%s%s", folder, now.Year(), int(now.Month()), uuid.New().String(), ext)
contentType := mime.TypeByExtension(ext)
if contentType == "" {
contentType = "application/octet-stream"
}
_, err = client.PutObject(context.Background(), bucket, objectKey, src, file.Size, minio.PutObjectOptions{
ContentType: contentType,
})
if err != nil {
return "", "", err
}
return objectKey, ms.PublicObjectURL(objectKey), nil
}

Binary file not shown.