feat: update banner image upload, gallery files, product spec, product, promotion

This commit is contained in:
hanif salafi 2025-11-17 22:30:00 +07:00
parent 85bf2dd456
commit d6aa8b6c2c
23 changed files with 2235 additions and 453 deletions

View File

@ -46,6 +46,7 @@ func (_i *BannersRouter) RegisterBannersRoutes() {
// define routes // define routes
_i.App.Route("/banners", func(router fiber.Router) { _i.App.Route("/banners", func(router fiber.Router) {
router.Get("/", bannersController.All) router.Get("/", bannersController.All)
router.Get("/viewer/:filename", bannersController.Viewer)
router.Get("/:id", bannersController.Show) router.Get("/:id", bannersController.Show)
router.Post("/", bannersController.Save) router.Post("/", bannersController.Save)
router.Put("/:id", bannersController.Update) router.Put("/:id", bannersController.Update)

View File

@ -22,6 +22,7 @@ type BannersController interface {
Save(c *fiber.Ctx) error Save(c *fiber.Ctx) error
Update(c *fiber.Ctx) error Update(c *fiber.Ctx) error
Delete(c *fiber.Ctx) error Delete(c *fiber.Ctx) error
Viewer(c *fiber.Ctx) error
} }
func NewBannersController(bannersService service.BannersService) BannersController { func NewBannersController(bannersService service.BannersService) BannersController {
@ -102,23 +103,61 @@ func (_i *bannersController) Show(c *fiber.Ctx) error {
// Save Banner // Save Banner
// @Summary Create Banner // @Summary Create Banner
// @Description API for creating Banner // @Description API for creating Banner with file upload
// @Tags Banners // @Tags Banners
// @Security Bearer // @Security Bearer
// @Param X-Client-Key header string true "Insert the X-Client-Key" // @Param X-Client-Key header string true "Insert the X-Client-Key"
// @Param payload body request.BannersCreateRequest true "Required payload" // @Param file formData file false "Upload file"
// @Param title formData string true "Banner title"
// @Param description formData string false "Banner description"
// @Param position formData string false "Banner position"
// @Param status formData string false "Banner status"
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
// @Failure 400 {object} response.BadRequestError // @Failure 400 {object} response.BadRequestError
// @Failure 401 {object} response.UnauthorizedError // @Failure 401 {object} response.UnauthorizedError
// @Failure 500 {object} response.InternalServerError // @Failure 500 {object} response.InternalServerError
// @Router /banners [post] // @Router /banners [post]
func (_i *bannersController) Save(c *fiber.Ctx) error { func (_i *bannersController) Save(c *fiber.Ctx) error {
req := new(request.BannersCreateRequest) // Parse multipart form
if err := utilVal.ParseAndValidate(c, req); err != nil { form, err := c.MultipartForm()
return err if err != nil {
return utilRes.Resp(c, utilRes.Response{
Success: false,
Messages: utilRes.Messages{"Failed to parse form data"},
})
} }
dataResult, err := _i.bannersService.Create(*req) // Extract form values
req := request.BannersCreateRequest{
Title: c.FormValue("title"),
}
if description := c.FormValue("description"); description != "" {
req.Description = &description
}
if position := c.FormValue("position"); position != "" {
req.Position = &position
}
if status := c.FormValue("status"); status != "" {
req.Status = &status
}
// Validate required fields
if req.Title == "" {
return utilRes.Resp(c, utilRes.Response{
Success: false,
Messages: utilRes.Messages{"Title is required"},
})
}
// Check if file is uploaded
if len(form.File["file"]) > 0 {
// File will be handled in service
}
dataResult, err := _i.bannersService.Create(c, req)
if err != nil { if err != nil {
return err return err
} }
@ -194,3 +233,19 @@ func (_i *bannersController) Delete(c *fiber.Ctx) error {
Messages: utilRes.Messages{"Banner successfully deleted"}, Messages: utilRes.Messages{"Banner successfully deleted"},
}) })
} }
// Viewer Banner
// @Summary Viewer Banner
// @Description API for viewing Banner file
// @Tags Banners
// @Security Bearer
// @Param X-Client-Key header string true "Insert the X-Client-Key"
// @Param filename path string true "Banner File Path"
// @Success 200 {file} file
// @Failure 400 {object} response.BadRequestError
// @Failure 401 {object} response.UnauthorizedError
// @Failure 500 {object} response.InternalServerError
// @Router /banners/viewer/{filename} [get]
func (_i *bannersController) Viewer(c *fiber.Ctx) error {
return _i.bannersService.Viewer(c)
}

View File

@ -21,6 +21,7 @@ type BannersRepository interface {
Create(banner *entity.Banners) (bannerReturn *entity.Banners, err error) Create(banner *entity.Banners) (bannerReturn *entity.Banners, err error)
Update(id uint, banner *entity.Banners) (err error) Update(id uint, banner *entity.Banners) (err error)
Delete(id uint) (err error) Delete(id uint) (err error)
FindByThumbnailPath(thumbnailPath string) (banner *entity.Banners, err error)
} }
func NewBannersRepository(db *database.Database, log zerolog.Logger) BannersRepository { func NewBannersRepository(db *database.Database, log zerolog.Logger) BannersRepository {
@ -90,3 +91,9 @@ func (_i *bannersRepository) Delete(id uint) (err error) {
err = _i.DB.DB.Model(&entity.Banners{}).Where("id = ?", id).Update("is_active", false).Error err = _i.DB.DB.Model(&entity.Banners{}).Where("id = ?", id).Update("is_active", false).Error
return return
} }
func (_i *bannersRepository) FindByThumbnailPath(thumbnailPath string) (banner *entity.Banners, err error) {
banner = &entity.Banners{}
err = _i.DB.DB.Where("thumbnail_path LIKE ? AND is_active = ?", "%"+thumbnailPath, true).First(banner).Error
return
}

View File

@ -1,36 +1,52 @@
package service package service
import ( import (
"context"
"errors" "errors"
"fmt"
"io"
"jaecoo-be/app/module/banners/mapper" "jaecoo-be/app/module/banners/mapper"
"jaecoo-be/app/module/banners/repository" "jaecoo-be/app/module/banners/repository"
"jaecoo-be/app/module/banners/request" "jaecoo-be/app/module/banners/request"
"jaecoo-be/app/module/banners/response" "jaecoo-be/app/module/banners/response"
"jaecoo-be/config/config" "jaecoo-be/config/config"
minioStorage "jaecoo-be/config/config"
"jaecoo-be/utils/paginator" "jaecoo-be/utils/paginator"
"math/rand"
"mime"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/gofiber/fiber/v2"
"github.com/minio/minio-go/v7"
"github.com/rs/zerolog" "github.com/rs/zerolog"
) )
type bannersService struct { type bannersService struct {
Repo repository.BannersRepository Repo repository.BannersRepository
Log zerolog.Logger Log zerolog.Logger
Cfg *config.Config Cfg *config.Config
MinioStorage *minioStorage.MinioStorage
} }
type BannersService interface { type BannersService interface {
GetAll(req request.BannersQueryRequest) (banners []*response.BannersResponse, paging paginator.Pagination, err error) GetAll(req request.BannersQueryRequest) (banners []*response.BannersResponse, paging paginator.Pagination, err error)
GetOne(id uint) (banner *response.BannersResponse, err error) GetOne(id uint) (banner *response.BannersResponse, err error)
Create(req request.BannersCreateRequest) (banner *response.BannersResponse, err error) Create(c *fiber.Ctx, req request.BannersCreateRequest) (banner *response.BannersResponse, err error)
Update(id uint, req request.BannersUpdateRequest) (banner *response.BannersResponse, err error) Update(id uint, req request.BannersUpdateRequest) (banner *response.BannersResponse, err error)
Delete(id uint) (err error) Delete(id uint) (err error)
UploadFileToMinio(c *fiber.Ctx, fileKey string) (filePath *string, err error)
Viewer(c *fiber.Ctx) (err error)
} }
func NewBannersService(repo repository.BannersRepository, log zerolog.Logger, cfg *config.Config) BannersService { func NewBannersService(repo repository.BannersRepository, log zerolog.Logger, cfg *config.Config, minioStorage *minioStorage.MinioStorage) BannersService {
return &bannersService{ return &bannersService{
Repo: repo, Repo: repo,
Log: log, Log: log,
Cfg: cfg, Cfg: cfg,
MinioStorage: minioStorage,
} }
} }
@ -66,7 +82,12 @@ func (_i *bannersService) GetOne(id uint) (banner *response.BannersResponse, err
return return
} }
func (_i *bannersService) Create(req request.BannersCreateRequest) (banner *response.BannersResponse, err error) { func (_i *bannersService) Create(c *fiber.Ctx, req request.BannersCreateRequest) (banner *response.BannersResponse, err error) {
// Handle file upload if exists
if filePath, uploadErr := _i.UploadFileToMinio(c, "file"); uploadErr == nil && filePath != nil {
req.ThumbnailPath = filePath
}
bannerEntity := req.ToEntity() bannerEntity := req.ToEntity()
isActive := true isActive := true
bannerEntity.IsActive = &isActive bannerEntity.IsActive = &isActive
@ -82,6 +103,71 @@ func (_i *bannersService) Create(req request.BannersCreateRequest) (banner *resp
return return
} }
func (_i *bannersService) UploadFileToMinio(c *fiber.Ctx, fileKey string) (filePath *string, err error) {
form, err := c.MultipartForm()
if err != nil {
return nil, err
}
files := form.File[fileKey]
if len(files) == 0 {
return nil, nil // No file uploaded, return nil without error
}
fileHeader := files[0]
// Create minio connection
minioClient, err := _i.MinioStorage.ConnectMinio()
if err != nil {
return nil, err
}
bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName
// Open file
src, err := fileHeader.Open()
if err != nil {
return nil, err
}
defer src.Close()
// Process filename
filename := filepath.Base(fileHeader.Filename)
filename = strings.ReplaceAll(filename, " ", "")
filenameWithoutExt := filepath.Clean(filename[:len(filename)-len(filepath.Ext(filename))])
extension := filepath.Ext(fileHeader.Filename)[1:]
// Generate unique filename
now := time.Now()
rand.New(rand.NewSource(now.UnixNano()))
randUniqueId := rand.Intn(1000000)
newFilenameWithoutExt := filenameWithoutExt + "_" + strconv.Itoa(randUniqueId)
newFilename := newFilenameWithoutExt + "." + extension
// Create object name with path structure
objectName := fmt.Sprintf("banners/upload/%d/%d/%s", now.Year(), now.Month(), newFilename)
_i.Log.Info().Str("timestamp", time.Now().
Format(time.RFC3339)).Str("Service:Resource", "Banners:UploadFileToMinio").
Interface("Uploading file", objectName).Msg("")
// Upload file to MinIO
_, err = minioClient.PutObject(context.Background(), bucketName, objectName, src, fileHeader.Size, minio.PutObjectOptions{})
if err != nil {
_i.Log.Error().Str("timestamp", time.Now().
Format(time.RFC3339)).Str("Service:Resource", "Banners:UploadFileToMinio").
Interface("Error uploading file", err).Msg("")
return nil, err
}
_i.Log.Info().Str("timestamp", time.Now().
Format(time.RFC3339)).Str("Service:Resource", "Banners:UploadFileToMinio").
Interface("Successfully uploaded", objectName).Msg("")
return &objectName, nil
}
func (_i *bannersService) Update(id uint, req request.BannersUpdateRequest) (banner *response.BannersResponse, err error) { func (_i *bannersService) Update(id uint, req request.BannersUpdateRequest) (banner *response.BannersResponse, err error) {
bannerEntity := req.ToEntity() bannerEntity := req.ToEntity()
@ -105,3 +191,79 @@ func (_i *bannersService) Delete(id uint) (err error) {
err = _i.Repo.Delete(id) err = _i.Repo.Delete(id)
return return
} }
func (_i *bannersService) Viewer(c *fiber.Ctx) (err error) {
filename := c.Params("filename")
// Find banner by thumbnail path
result, err := _i.Repo.FindByThumbnailPath(filename)
if err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
"error": true,
"msg": "Banner file not found",
})
}
if result.ThumbnailPath == nil || *result.ThumbnailPath == "" {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
"error": true,
"msg": "Banner thumbnail path not found",
})
}
ctx := context.Background()
bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName
objectName := *result.ThumbnailPath
_i.Log.Info().Str("timestamp", time.Now().
Format(time.RFC3339)).Str("Service:Resource", "Banners:Viewer").
Interface("data", objectName).Msg("")
// Create minio connection
minioClient, err := _i.MinioStorage.ConnectMinio()
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": true,
"msg": err.Error(),
})
}
fileContent, err := minioClient.GetObject(ctx, bucketName, objectName, minio.GetObjectOptions{})
if err != nil {
_i.Log.Error().Str("timestamp", time.Now().
Format(time.RFC3339)).Str("Service:Resource", "Banners:Viewer").
Interface("Error getting file", err).Msg("")
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": true,
"msg": "Failed to retrieve file",
})
}
defer fileContent.Close()
// Determine Content-Type based on file extension
contentType := mime.TypeByExtension("." + getFileExtension(objectName))
if contentType == "" {
contentType = "application/octet-stream" // fallback if no MIME type matches
}
c.Set("Content-Type", contentType)
if _, err := io.Copy(c.Response().BodyWriter(), fileContent); err != nil {
return err
}
return
}
func getFileExtension(filename string) string {
// split file name
parts := strings.Split(filename, ".")
// jika tidak ada ekstensi, kembalikan string kosong
if len(parts) == 1 || (len(parts) == 2 && parts[0] == "") {
return ""
}
// ambil ekstensi terakhir
return parts[len(parts)-1]
}

View File

@ -22,6 +22,7 @@ type GalleryFilesController interface {
Save(c *fiber.Ctx) error Save(c *fiber.Ctx) error
Update(c *fiber.Ctx) error Update(c *fiber.Ctx) error
Delete(c *fiber.Ctx) error Delete(c *fiber.Ctx) error
Viewer(c *fiber.Ctx) error
} }
func NewGalleryFilesController(galleryFilesService service.GalleryFilesService) GalleryFilesController { func NewGalleryFilesController(galleryFilesService service.GalleryFilesService) GalleryFilesController {
@ -101,23 +102,52 @@ func (_i *galleryFilesController) Show(c *fiber.Ctx) error {
// Save GalleryFile // Save GalleryFile
// @Summary Create GalleryFile // @Summary Create GalleryFile
// @Description API for creating GalleryFile // @Description API for creating GalleryFile with file upload
// @Tags GalleryFiles // @Tags GalleryFiles
// @Security Bearer // @Security Bearer
// @Param X-Client-Key header string true "Insert the X-Client-Key" // @Param X-Client-Key header string true "Insert the X-Client-Key"
// @Param payload body request.GalleryFilesCreateRequest true "Required payload" // @Param file formData file false "Upload file"
// @Param gallery_id formData int true "Gallery ID"
// @Param title formData string false "Gallery file title"
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
// @Failure 400 {object} response.BadRequestError // @Failure 400 {object} response.BadRequestError
// @Failure 401 {object} response.UnauthorizedError // @Failure 401 {object} response.UnauthorizedError
// @Failure 500 {object} response.InternalServerError // @Failure 500 {object} response.InternalServerError
// @Router /gallery-files [post] // @Router /gallery-files [post]
func (_i *galleryFilesController) Save(c *fiber.Ctx) error { func (_i *galleryFilesController) Save(c *fiber.Ctx) error {
req := new(request.GalleryFilesCreateRequest) // Parse multipart form
if err := utilVal.ParseAndValidate(c, req); err != nil { form, err := c.MultipartForm()
return err if err != nil {
return utilRes.Resp(c, utilRes.Response{
Success: false,
Messages: utilRes.Messages{"Failed to parse form data"},
})
} }
dataResult, err := _i.galleryFilesService.Create(*req) // Extract form values
galleryIDStr := c.FormValue("gallery_id")
galleryID, err := strconv.ParseUint(galleryIDStr, 10, 0)
if err != nil {
return utilRes.Resp(c, utilRes.Response{
Success: false,
Messages: utilRes.Messages{"Invalid gallery_id"},
})
}
req := request.GalleryFilesCreateRequest{
GalleryID: uint(galleryID),
}
if title := c.FormValue("title"); title != "" {
req.Title = &title
}
// Check if file is uploaded
if len(form.File["file"]) > 0 {
// File will be handled in service
}
dataResult, err := _i.galleryFilesService.Create(c, req)
if err != nil { if err != nil {
return err return err
} }
@ -193,3 +223,19 @@ func (_i *galleryFilesController) Delete(c *fiber.Ctx) error {
Messages: utilRes.Messages{"GalleryFile successfully deleted"}, Messages: utilRes.Messages{"GalleryFile successfully deleted"},
}) })
} }
// Viewer GalleryFile
// @Summary Viewer GalleryFile
// @Description API for viewing GalleryFile file
// @Tags GalleryFiles
// @Security Bearer
// @Param X-Client-Key header string true "Insert the X-Client-Key"
// @Param filename path string true "Gallery File Path"
// @Success 200 {file} file
// @Failure 400 {object} response.BadRequestError
// @Failure 401 {object} response.UnauthorizedError
// @Failure 500 {object} response.InternalServerError
// @Router /gallery-files/viewer/{filename} [get]
func (_i *galleryFilesController) Viewer(c *fiber.Ctx) error {
return _i.galleryFilesService.Viewer(c)
}

View File

@ -46,6 +46,7 @@ func (_i *GalleryFilesRouter) RegisterGalleryFilesRoutes() {
// define routes // define routes
_i.App.Route("/gallery-files", func(router fiber.Router) { _i.App.Route("/gallery-files", func(router fiber.Router) {
router.Get("/", galleryFilesController.All) router.Get("/", galleryFilesController.All)
router.Get("/viewer/:filename", galleryFilesController.Viewer)
router.Get("/:id", galleryFilesController.Show) router.Get("/:id", galleryFilesController.Show)
router.Post("/", galleryFilesController.Save) router.Post("/", galleryFilesController.Save)
router.Put("/:id", galleryFilesController.Update) router.Put("/:id", galleryFilesController.Update)

View File

@ -21,6 +21,7 @@ type GalleryFilesRepository interface {
Create(file *entity.GalleryFiles) (fileReturn *entity.GalleryFiles, err error) Create(file *entity.GalleryFiles) (fileReturn *entity.GalleryFiles, err error)
Update(id uint, file *entity.GalleryFiles) (err error) Update(id uint, file *entity.GalleryFiles) (err error)
Delete(id uint) (err error) Delete(id uint) (err error)
FindByImagePath(imagePath string) (file *entity.GalleryFiles, err error)
} }
func NewGalleryFilesRepository(db *database.Database, log zerolog.Logger) GalleryFilesRepository { func NewGalleryFilesRepository(db *database.Database, log zerolog.Logger) GalleryFilesRepository {
@ -87,3 +88,9 @@ func (_i *galleryFilesRepository) Delete(id uint) (err error) {
return return
} }
func (_i *galleryFilesRepository) FindByImagePath(imagePath string) (file *entity.GalleryFiles, err error) {
file = &entity.GalleryFiles{}
err = _i.DB.DB.Where("image_path LIKE ? AND is_active = ?", "%"+imagePath, true).First(file).Error
return
}

View File

@ -1,36 +1,52 @@
package service package service
import ( import (
"context"
"errors" "errors"
"fmt"
"io"
"jaecoo-be/app/module/gallery_files/mapper" "jaecoo-be/app/module/gallery_files/mapper"
"jaecoo-be/app/module/gallery_files/repository" "jaecoo-be/app/module/gallery_files/repository"
"jaecoo-be/app/module/gallery_files/request" "jaecoo-be/app/module/gallery_files/request"
"jaecoo-be/app/module/gallery_files/response" "jaecoo-be/app/module/gallery_files/response"
"jaecoo-be/config/config" "jaecoo-be/config/config"
minioStorage "jaecoo-be/config/config"
"jaecoo-be/utils/paginator" "jaecoo-be/utils/paginator"
"math/rand"
"mime"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/gofiber/fiber/v2"
"github.com/minio/minio-go/v7"
"github.com/rs/zerolog" "github.com/rs/zerolog"
) )
type galleryFilesService struct { type galleryFilesService struct {
Repo repository.GalleryFilesRepository Repo repository.GalleryFilesRepository
Log zerolog.Logger Log zerolog.Logger
Cfg *config.Config Cfg *config.Config
MinioStorage *minioStorage.MinioStorage
} }
type GalleryFilesService interface { type GalleryFilesService interface {
GetAll(req request.GalleryFilesQueryRequest) (files []*response.GalleryFilesResponse, paging paginator.Pagination, err error) GetAll(req request.GalleryFilesQueryRequest) (files []*response.GalleryFilesResponse, paging paginator.Pagination, err error)
GetOne(id uint) (file *response.GalleryFilesResponse, err error) GetOne(id uint) (file *response.GalleryFilesResponse, err error)
Create(req request.GalleryFilesCreateRequest) (file *response.GalleryFilesResponse, err error) Create(c *fiber.Ctx, req request.GalleryFilesCreateRequest) (file *response.GalleryFilesResponse, err error)
Update(id uint, req request.GalleryFilesUpdateRequest) (file *response.GalleryFilesResponse, err error) Update(id uint, req request.GalleryFilesUpdateRequest) (file *response.GalleryFilesResponse, err error)
Delete(id uint) (err error) Delete(id uint) (err error)
UploadFileToMinio(c *fiber.Ctx, fileKey string) (filePath *string, err error)
Viewer(c *fiber.Ctx) (err error)
} }
func NewGalleryFilesService(repo repository.GalleryFilesRepository, log zerolog.Logger, cfg *config.Config) GalleryFilesService { func NewGalleryFilesService(repo repository.GalleryFilesRepository, log zerolog.Logger, cfg *config.Config, minioStorage *minioStorage.MinioStorage) GalleryFilesService {
return &galleryFilesService{ return &galleryFilesService{
Repo: repo, Repo: repo,
Log: log, Log: log,
Cfg: cfg, Cfg: cfg,
MinioStorage: minioStorage,
} }
} }
@ -66,7 +82,12 @@ func (_i *galleryFilesService) GetOne(id uint) (file *response.GalleryFilesRespo
return return
} }
func (_i *galleryFilesService) Create(req request.GalleryFilesCreateRequest) (file *response.GalleryFilesResponse, err error) { func (_i *galleryFilesService) Create(c *fiber.Ctx, req request.GalleryFilesCreateRequest) (file *response.GalleryFilesResponse, err error) {
// Handle file upload if exists
if filePath, uploadErr := _i.UploadFileToMinio(c, "file"); uploadErr == nil && filePath != nil {
req.ImagePath = filePath
}
fileEntity := req.ToEntity() fileEntity := req.ToEntity()
isActive := true isActive := true
fileEntity.IsActive = &isActive fileEntity.IsActive = &isActive
@ -82,6 +103,71 @@ func (_i *galleryFilesService) Create(req request.GalleryFilesCreateRequest) (fi
return return
} }
func (_i *galleryFilesService) UploadFileToMinio(c *fiber.Ctx, fileKey string) (filePath *string, err error) {
form, err := c.MultipartForm()
if err != nil {
return nil, err
}
files := form.File[fileKey]
if len(files) == 0 {
return nil, nil // No file uploaded, return nil without error
}
fileHeader := files[0]
// Create minio connection
minioClient, err := _i.MinioStorage.ConnectMinio()
if err != nil {
return nil, err
}
bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName
// Open file
src, err := fileHeader.Open()
if err != nil {
return nil, err
}
defer src.Close()
// Process filename
filename := filepath.Base(fileHeader.Filename)
filename = strings.ReplaceAll(filename, " ", "")
filenameWithoutExt := filepath.Clean(filename[:len(filename)-len(filepath.Ext(filename))])
extension := filepath.Ext(fileHeader.Filename)[1:]
// Generate unique filename
now := time.Now()
rand.New(rand.NewSource(now.UnixNano()))
randUniqueId := rand.Intn(1000000)
newFilenameWithoutExt := filenameWithoutExt + "_" + strconv.Itoa(randUniqueId)
newFilename := newFilenameWithoutExt + "." + extension
// Create object name with path structure
objectName := fmt.Sprintf("gallery-files/upload/%d/%d/%s", now.Year(), now.Month(), newFilename)
_i.Log.Info().Str("timestamp", time.Now().
Format(time.RFC3339)).Str("Service:Resource", "GalleryFiles:UploadFileToMinio").
Interface("Uploading file", objectName).Msg("")
// Upload file to MinIO
_, err = minioClient.PutObject(context.Background(), bucketName, objectName, src, fileHeader.Size, minio.PutObjectOptions{})
if err != nil {
_i.Log.Error().Str("timestamp", time.Now().
Format(time.RFC3339)).Str("Service:Resource", "GalleryFiles:UploadFileToMinio").
Interface("Error uploading file", err).Msg("")
return nil, err
}
_i.Log.Info().Str("timestamp", time.Now().
Format(time.RFC3339)).Str("Service:Resource", "GalleryFiles:UploadFileToMinio").
Interface("Successfully uploaded", objectName).Msg("")
return &objectName, nil
}
func (_i *galleryFilesService) Update(id uint, req request.GalleryFilesUpdateRequest) (file *response.GalleryFilesResponse, err error) { func (_i *galleryFilesService) Update(id uint, req request.GalleryFilesUpdateRequest) (file *response.GalleryFilesResponse, err error) {
fileEntity := req.ToEntity() fileEntity := req.ToEntity()
@ -105,3 +191,79 @@ func (_i *galleryFilesService) Delete(id uint) (err error) {
err = _i.Repo.Delete(id) err = _i.Repo.Delete(id)
return return
} }
func (_i *galleryFilesService) Viewer(c *fiber.Ctx) (err error) {
filename := c.Params("filename")
// Find gallery file by image path
result, err := _i.Repo.FindByImagePath(filename)
if err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
"error": true,
"msg": "Gallery file not found",
})
}
if result.ImagePath == nil || *result.ImagePath == "" {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
"error": true,
"msg": "Gallery image path not found",
})
}
ctx := context.Background()
bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName
objectName := *result.ImagePath
_i.Log.Info().Str("timestamp", time.Now().
Format(time.RFC3339)).Str("Service:Resource", "GalleryFiles:Viewer").
Interface("data", objectName).Msg("")
// Create minio connection
minioClient, err := _i.MinioStorage.ConnectMinio()
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": true,
"msg": err.Error(),
})
}
fileContent, err := minioClient.GetObject(ctx, bucketName, objectName, minio.GetObjectOptions{})
if err != nil {
_i.Log.Error().Str("timestamp", time.Now().
Format(time.RFC3339)).Str("Service:Resource", "GalleryFiles:Viewer").
Interface("Error getting file", err).Msg("")
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": true,
"msg": "Failed to retrieve file",
})
}
defer fileContent.Close()
// Determine Content-Type based on file extension
contentType := mime.TypeByExtension("." + getFileExtension(objectName))
if contentType == "" {
contentType = "application/octet-stream" // fallback if no MIME type matches
}
c.Set("Content-Type", contentType)
if _, err := io.Copy(c.Response().BodyWriter(), fileContent); err != nil {
return err
}
return
}
func getFileExtension(filename string) string {
// split file name
parts := strings.Split(filename, ".")
// jika tidak ada ekstensi, kembalikan string kosong
if len(parts) == 1 || (len(parts) == 2 && parts[0] == "") {
return ""
}
// ambil ekstensi terakhir
return parts[len(parts)-1]
}

View File

@ -22,6 +22,7 @@ type ProductSpecificationsController interface {
Save(c *fiber.Ctx) error Save(c *fiber.Ctx) error
Update(c *fiber.Ctx) error Update(c *fiber.Ctx) error
Delete(c *fiber.Ctx) error Delete(c *fiber.Ctx) error
Viewer(c *fiber.Ctx) error
} }
func NewProductSpecificationsController(productSpecificationsService service.ProductSpecificationsService) ProductSpecificationsController { func NewProductSpecificationsController(productSpecificationsService service.ProductSpecificationsService) ProductSpecificationsController {
@ -101,23 +102,57 @@ func (_i *productSpecificationsController) Show(c *fiber.Ctx) error {
// Save ProductSpecification // Save ProductSpecification
// @Summary Create ProductSpecification // @Summary Create ProductSpecification
// @Description API for creating ProductSpecification // @Description API for creating ProductSpecification with file upload
// @Tags ProductSpecifications // @Tags ProductSpecifications
// @Security Bearer // @Security Bearer
// @Param X-Client-Key header string true "Insert the X-Client-Key" // @Param X-Client-Key header string true "Insert the X-Client-Key"
// @Param payload body request.ProductSpecificationsCreateRequest true "Required payload" // @Param file formData file false "Upload file"
// @Param product_id formData int true "Product ID"
// @Param title formData string true "Product specification title"
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
// @Failure 400 {object} response.BadRequestError // @Failure 400 {object} response.BadRequestError
// @Failure 401 {object} response.UnauthorizedError // @Failure 401 {object} response.UnauthorizedError
// @Failure 500 {object} response.InternalServerError // @Failure 500 {object} response.InternalServerError
// @Router /product-specifications [post] // @Router /product-specifications [post]
func (_i *productSpecificationsController) Save(c *fiber.Ctx) error { func (_i *productSpecificationsController) Save(c *fiber.Ctx) error {
req := new(request.ProductSpecificationsCreateRequest) // Parse multipart form
if err := utilVal.ParseAndValidate(c, req); err != nil { form, err := c.MultipartForm()
return err if err != nil {
return utilRes.Resp(c, utilRes.Response{
Success: false,
Messages: utilRes.Messages{"Failed to parse form data"},
})
} }
dataResult, err := _i.productSpecificationsService.Create(*req) // Extract form values
productIDStr := c.FormValue("product_id")
productID, err := strconv.ParseUint(productIDStr, 10, 0)
if err != nil {
return utilRes.Resp(c, utilRes.Response{
Success: false,
Messages: utilRes.Messages{"Invalid product_id"},
})
}
req := request.ProductSpecificationsCreateRequest{
ProductID: uint(productID),
Title: c.FormValue("title"),
}
// Validate required fields
if req.Title == "" {
return utilRes.Resp(c, utilRes.Response{
Success: false,
Messages: utilRes.Messages{"Title is required"},
})
}
// Check if file is uploaded
if len(form.File["file"]) > 0 {
// File will be handled in service
}
dataResult, err := _i.productSpecificationsService.Create(c, req)
if err != nil { if err != nil {
return err return err
} }
@ -194,3 +229,18 @@ func (_i *productSpecificationsController) Delete(c *fiber.Ctx) error {
}) })
} }
// Viewer ProductSpecification
// @Summary Viewer ProductSpecification
// @Description API for viewing ProductSpecification file
// @Tags ProductSpecifications
// @Security Bearer
// @Param X-Client-Key header string true "Insert the X-Client-Key"
// @Param filename path string true "Product Specification File Path"
// @Success 200 {file} file
// @Failure 400 {object} response.BadRequestError
// @Failure 401 {object} response.UnauthorizedError
// @Failure 500 {object} response.InternalServerError
// @Router /product-specifications/viewer/{filename} [get]
func (_i *productSpecificationsController) Viewer(c *fiber.Ctx) error {
return _i.productSpecificationsService.Viewer(c)
}

View File

@ -46,6 +46,7 @@ func (_i *ProductSpecificationsRouter) RegisterProductSpecificationsRoutes() {
// define routes // define routes
_i.App.Route("/product-specifications", func(router fiber.Router) { _i.App.Route("/product-specifications", func(router fiber.Router) {
router.Get("/", productSpecificationsController.All) router.Get("/", productSpecificationsController.All)
router.Get("/viewer/:filename", productSpecificationsController.Viewer)
router.Get("/:id", productSpecificationsController.Show) router.Get("/:id", productSpecificationsController.Show)
router.Post("/", productSpecificationsController.Save) router.Post("/", productSpecificationsController.Save)
router.Put("/:id", productSpecificationsController.Update) router.Put("/:id", productSpecificationsController.Update)

View File

@ -21,6 +21,7 @@ type ProductSpecificationsRepository interface {
Create(spec *entity.ProductSpecifications) (specReturn *entity.ProductSpecifications, err error) Create(spec *entity.ProductSpecifications) (specReturn *entity.ProductSpecifications, err error)
Update(id uint, spec *entity.ProductSpecifications) (err error) Update(id uint, spec *entity.ProductSpecifications) (err error)
Delete(id uint) (err error) Delete(id uint) (err error)
FindByThumbnailPath(thumbnailPath string) (spec *entity.ProductSpecifications, err error)
} }
func NewProductSpecificationsRepository(db *database.Database, log zerolog.Logger) ProductSpecificationsRepository { func NewProductSpecificationsRepository(db *database.Database, log zerolog.Logger) ProductSpecificationsRepository {
@ -87,3 +88,9 @@ func (_i *productSpecificationsRepository) Delete(id uint) (err error) {
return return
} }
func (_i *productSpecificationsRepository) FindByThumbnailPath(thumbnailPath string) (spec *entity.ProductSpecifications, err error) {
spec = &entity.ProductSpecifications{}
err = _i.DB.DB.Where("thumbnail_path LIKE ? AND is_active = ?", "%"+thumbnailPath, true).First(spec).Error
return
}

View File

@ -1,36 +1,52 @@
package service package service
import ( import (
"context"
"errors" "errors"
"fmt"
"io"
"jaecoo-be/app/module/product_specifications/mapper" "jaecoo-be/app/module/product_specifications/mapper"
"jaecoo-be/app/module/product_specifications/repository" "jaecoo-be/app/module/product_specifications/repository"
"jaecoo-be/app/module/product_specifications/request" "jaecoo-be/app/module/product_specifications/request"
"jaecoo-be/app/module/product_specifications/response" "jaecoo-be/app/module/product_specifications/response"
"jaecoo-be/config/config" "jaecoo-be/config/config"
minioStorage "jaecoo-be/config/config"
"jaecoo-be/utils/paginator" "jaecoo-be/utils/paginator"
"math/rand"
"mime"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/gofiber/fiber/v2"
"github.com/minio/minio-go/v7"
"github.com/rs/zerolog" "github.com/rs/zerolog"
) )
type productSpecificationsService struct { type productSpecificationsService struct {
Repo repository.ProductSpecificationsRepository Repo repository.ProductSpecificationsRepository
Log zerolog.Logger Log zerolog.Logger
Cfg *config.Config Cfg *config.Config
MinioStorage *minioStorage.MinioStorage
} }
type ProductSpecificationsService interface { type ProductSpecificationsService interface {
GetAll(req request.ProductSpecificationsQueryRequest) (specs []*response.ProductSpecificationsResponse, paging paginator.Pagination, err error) GetAll(req request.ProductSpecificationsQueryRequest) (specs []*response.ProductSpecificationsResponse, paging paginator.Pagination, err error)
GetOne(id uint) (spec *response.ProductSpecificationsResponse, err error) GetOne(id uint) (spec *response.ProductSpecificationsResponse, err error)
Create(req request.ProductSpecificationsCreateRequest) (spec *response.ProductSpecificationsResponse, err error) Create(c *fiber.Ctx, req request.ProductSpecificationsCreateRequest) (spec *response.ProductSpecificationsResponse, err error)
Update(id uint, req request.ProductSpecificationsUpdateRequest) (spec *response.ProductSpecificationsResponse, err error) Update(id uint, req request.ProductSpecificationsUpdateRequest) (spec *response.ProductSpecificationsResponse, err error)
Delete(id uint) (err error) Delete(id uint) (err error)
UploadFileToMinio(c *fiber.Ctx, fileKey string) (filePath *string, err error)
Viewer(c *fiber.Ctx) (err error)
} }
func NewProductSpecificationsService(repo repository.ProductSpecificationsRepository, log zerolog.Logger, cfg *config.Config) ProductSpecificationsService { func NewProductSpecificationsService(repo repository.ProductSpecificationsRepository, log zerolog.Logger, cfg *config.Config, minioStorage *minioStorage.MinioStorage) ProductSpecificationsService {
return &productSpecificationsService{ return &productSpecificationsService{
Repo: repo, Repo: repo,
Log: log, Log: log,
Cfg: cfg, Cfg: cfg,
MinioStorage: minioStorage,
} }
} }
@ -66,7 +82,12 @@ func (_i *productSpecificationsService) GetOne(id uint) (spec *response.ProductS
return return
} }
func (_i *productSpecificationsService) Create(req request.ProductSpecificationsCreateRequest) (spec *response.ProductSpecificationsResponse, err error) { func (_i *productSpecificationsService) Create(c *fiber.Ctx, req request.ProductSpecificationsCreateRequest) (spec *response.ProductSpecificationsResponse, err error) {
// Handle file upload if exists
if filePath, uploadErr := _i.UploadFileToMinio(c, "file"); uploadErr == nil && filePath != nil {
req.ThumbnailPath = filePath
}
specEntity := req.ToEntity() specEntity := req.ToEntity()
isActive := true isActive := true
specEntity.IsActive = &isActive specEntity.IsActive = &isActive
@ -82,6 +103,71 @@ func (_i *productSpecificationsService) Create(req request.ProductSpecifications
return return
} }
func (_i *productSpecificationsService) UploadFileToMinio(c *fiber.Ctx, fileKey string) (filePath *string, err error) {
form, err := c.MultipartForm()
if err != nil {
return nil, err
}
files := form.File[fileKey]
if len(files) == 0 {
return nil, nil // No file uploaded, return nil without error
}
fileHeader := files[0]
// Create minio connection
minioClient, err := _i.MinioStorage.ConnectMinio()
if err != nil {
return nil, err
}
bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName
// Open file
src, err := fileHeader.Open()
if err != nil {
return nil, err
}
defer src.Close()
// Process filename
filename := filepath.Base(fileHeader.Filename)
filename = strings.ReplaceAll(filename, " ", "")
filenameWithoutExt := filepath.Clean(filename[:len(filename)-len(filepath.Ext(filename))])
extension := filepath.Ext(fileHeader.Filename)[1:]
// Generate unique filename
now := time.Now()
rand.New(rand.NewSource(now.UnixNano()))
randUniqueId := rand.Intn(1000000)
newFilenameWithoutExt := filenameWithoutExt + "_" + strconv.Itoa(randUniqueId)
newFilename := newFilenameWithoutExt + "." + extension
// Create object name with path structure
objectName := fmt.Sprintf("product-specifications/upload/%d/%d/%s", now.Year(), now.Month(), newFilename)
_i.Log.Info().Str("timestamp", time.Now().
Format(time.RFC3339)).Str("Service:Resource", "ProductSpecifications:UploadFileToMinio").
Interface("Uploading file", objectName).Msg("")
// Upload file to MinIO
_, err = minioClient.PutObject(context.Background(), bucketName, objectName, src, fileHeader.Size, minio.PutObjectOptions{})
if err != nil {
_i.Log.Error().Str("timestamp", time.Now().
Format(time.RFC3339)).Str("Service:Resource", "ProductSpecifications:UploadFileToMinio").
Interface("Error uploading file", err).Msg("")
return nil, err
}
_i.Log.Info().Str("timestamp", time.Now().
Format(time.RFC3339)).Str("Service:Resource", "ProductSpecifications:UploadFileToMinio").
Interface("Successfully uploaded", objectName).Msg("")
return &objectName, nil
}
func (_i *productSpecificationsService) Update(id uint, req request.ProductSpecificationsUpdateRequest) (spec *response.ProductSpecificationsResponse, err error) { func (_i *productSpecificationsService) Update(id uint, req request.ProductSpecificationsUpdateRequest) (spec *response.ProductSpecificationsResponse, err error) {
specEntity := req.ToEntity() specEntity := req.ToEntity()
@ -105,3 +191,79 @@ func (_i *productSpecificationsService) Delete(id uint) (err error) {
err = _i.Repo.Delete(id) err = _i.Repo.Delete(id)
return return
} }
func (_i *productSpecificationsService) Viewer(c *fiber.Ctx) (err error) {
filename := c.Params("filename")
// Find product specification by thumbnail path
result, err := _i.Repo.FindByThumbnailPath(filename)
if err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
"error": true,
"msg": "Product specification file not found",
})
}
if result.ThumbnailPath == nil || *result.ThumbnailPath == "" {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
"error": true,
"msg": "Product specification thumbnail path not found",
})
}
ctx := context.Background()
bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName
objectName := *result.ThumbnailPath
_i.Log.Info().Str("timestamp", time.Now().
Format(time.RFC3339)).Str("Service:Resource", "ProductSpecifications:Viewer").
Interface("data", objectName).Msg("")
// Create minio connection
minioClient, err := _i.MinioStorage.ConnectMinio()
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": true,
"msg": err.Error(),
})
}
fileContent, err := minioClient.GetObject(ctx, bucketName, objectName, minio.GetObjectOptions{})
if err != nil {
_i.Log.Error().Str("timestamp", time.Now().
Format(time.RFC3339)).Str("Service:Resource", "ProductSpecifications:Viewer").
Interface("Error getting file", err).Msg("")
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": true,
"msg": "Failed to retrieve file",
})
}
defer fileContent.Close()
// Determine Content-Type based on file extension
contentType := mime.TypeByExtension("." + getFileExtension(objectName))
if contentType == "" {
contentType = "application/octet-stream" // fallback if no MIME type matches
}
c.Set("Content-Type", contentType)
if _, err := io.Copy(c.Response().BodyWriter(), fileContent); err != nil {
return err
}
return
}
func getFileExtension(filename string) string {
// split file name
parts := strings.Split(filename, ".")
// jika tidak ada ekstensi, kembalikan string kosong
if len(parts) == 1 || (len(parts) == 2 && parts[0] == "") {
return ""
}
// ambil ekstensi terakhir
return parts[len(parts)-1]
}

View File

@ -1,6 +1,7 @@
package controller package controller
import ( import (
"encoding/json"
"jaecoo-be/app/module/products/request" "jaecoo-be/app/module/products/request"
"jaecoo-be/app/module/products/service" "jaecoo-be/app/module/products/service"
"jaecoo-be/utils/paginator" "jaecoo-be/utils/paginator"
@ -22,6 +23,7 @@ type ProductsController interface {
Save(c *fiber.Ctx) error Save(c *fiber.Ctx) error
Update(c *fiber.Ctx) error Update(c *fiber.Ctx) error
Delete(c *fiber.Ctx) error Delete(c *fiber.Ctx) error
Viewer(c *fiber.Ctx) error
} }
func NewProductsController(productsService service.ProductsService) ProductsController { func NewProductsController(productsService service.ProductsService) ProductsController {
@ -101,23 +103,67 @@ func (_i *productsController) Show(c *fiber.Ctx) error {
// Save Product // Save Product
// @Summary Create Product // @Summary Create Product
// @Description API for creating Product // @Description API for creating Product with file upload
// @Tags Products // @Tags Products
// @Security Bearer // @Security Bearer
// @Param X-Client-Key header string true "Insert the X-Client-Key" // @Param X-Client-Key header string true "Insert the X-Client-Key"
// @Param payload body request.ProductsCreateRequest true "Required payload" // @Param file formData file false "Upload file"
// @Param title formData string true "Product title"
// @Param variant formData string false "Product variant"
// @Param price formData number false "Product price"
// @Param colors formData string false "Product colors (JSON array)"
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
// @Failure 400 {object} response.BadRequestError // @Failure 400 {object} response.BadRequestError
// @Failure 401 {object} response.UnauthorizedError // @Failure 401 {object} response.UnauthorizedError
// @Failure 500 {object} response.InternalServerError // @Failure 500 {object} response.InternalServerError
// @Router /products [post] // @Router /products [post]
func (_i *productsController) Save(c *fiber.Ctx) error { func (_i *productsController) Save(c *fiber.Ctx) error {
req := new(request.ProductsCreateRequest) // Parse multipart form
if err := utilVal.ParseAndValidate(c, req); err != nil { form, err := c.MultipartForm()
return err if err != nil {
return utilRes.Resp(c, utilRes.Response{
Success: false,
Messages: utilRes.Messages{"Failed to parse form data"},
})
} }
dataResult, err := _i.productsService.Create(*req) // Extract form values
req := request.ProductsCreateRequest{
Title: c.FormValue("title"),
}
if variant := c.FormValue("variant"); variant != "" {
req.Variant = &variant
}
if priceStr := c.FormValue("price"); priceStr != "" {
if price, err := strconv.ParseFloat(priceStr, 64); err == nil {
req.Price = &price
}
}
// Handle colors (JSON array string)
if colorsStr := c.FormValue("colors"); colorsStr != "" {
var colors []string
if err := json.Unmarshal([]byte(colorsStr), &colors); err == nil {
req.Colors = colors
}
}
// Validate required fields
if req.Title == "" {
return utilRes.Resp(c, utilRes.Response{
Success: false,
Messages: utilRes.Messages{"Title is required"},
})
}
// Check if file is uploaded
if len(form.File["file"]) > 0 {
// File will be handled in service
}
dataResult, err := _i.productsService.Create(c, req)
if err != nil { if err != nil {
return err return err
} }
@ -194,3 +240,19 @@ func (_i *productsController) Delete(c *fiber.Ctx) error {
}) })
} }
// Viewer Product
// @Summary Viewer Product
// @Description API for viewing Product file
// @Tags Products
// @Security Bearer
// @Param X-Client-Key header string true "Insert the X-Client-Key"
// @Param filename path string true "Product File Path"
// @Success 200 {file} file
// @Failure 400 {object} response.BadRequestError
// @Failure 401 {object} response.UnauthorizedError
// @Failure 500 {object} response.InternalServerError
// @Router /products/viewer/{filename} [get]
func (_i *productsController) Viewer(c *fiber.Ctx) error {
return _i.productsService.Viewer(c)
}

View File

@ -46,6 +46,7 @@ func (_i *ProductsRouter) RegisterProductsRoutes() {
// define routes // define routes
_i.App.Route("/products", func(router fiber.Router) { _i.App.Route("/products", func(router fiber.Router) {
router.Get("/", productsController.All) router.Get("/", productsController.All)
router.Get("/viewer/:filename", productsController.Viewer)
router.Get("/:id", productsController.Show) router.Get("/:id", productsController.Show)
router.Post("/", productsController.Save) router.Post("/", productsController.Save)
router.Put("/:id", productsController.Update) router.Put("/:id", productsController.Update)

View File

@ -21,6 +21,7 @@ type ProductsRepository interface {
Create(product *entity.Products) (productReturn *entity.Products, err error) Create(product *entity.Products) (productReturn *entity.Products, err error)
Update(id uint, product *entity.Products) (err error) Update(id uint, product *entity.Products) (err error)
Delete(id uint) (err error) Delete(id uint) (err error)
FindByThumbnailPath(thumbnailPath string) (product *entity.Products, err error)
} }
func NewProductsRepository(db *database.Database, log zerolog.Logger) ProductsRepository { func NewProductsRepository(db *database.Database, log zerolog.Logger) ProductsRepository {
@ -87,3 +88,9 @@ func (_i *productsRepository) Delete(id uint) (err error) {
return return
} }
func (_i *productsRepository) FindByThumbnailPath(thumbnailPath string) (product *entity.Products, err error) {
product = &entity.Products{}
err = _i.DB.DB.Where("thumbnail_path LIKE ? AND is_active = ?", "%"+thumbnailPath, true).First(product).Error
return
}

View File

@ -1,36 +1,52 @@
package service package service
import ( import (
"context"
"errors" "errors"
"fmt"
"io"
"jaecoo-be/app/module/products/mapper" "jaecoo-be/app/module/products/mapper"
"jaecoo-be/app/module/products/repository" "jaecoo-be/app/module/products/repository"
"jaecoo-be/app/module/products/request" "jaecoo-be/app/module/products/request"
"jaecoo-be/app/module/products/response" "jaecoo-be/app/module/products/response"
"jaecoo-be/config/config" "jaecoo-be/config/config"
minioStorage "jaecoo-be/config/config"
"jaecoo-be/utils/paginator" "jaecoo-be/utils/paginator"
"math/rand"
"mime"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/gofiber/fiber/v2"
"github.com/minio/minio-go/v7"
"github.com/rs/zerolog" "github.com/rs/zerolog"
) )
type productsService struct { type productsService struct {
Repo repository.ProductsRepository Repo repository.ProductsRepository
Log zerolog.Logger Log zerolog.Logger
Cfg *config.Config Cfg *config.Config
MinioStorage *minioStorage.MinioStorage
} }
type ProductsService interface { type ProductsService interface {
GetAll(req request.ProductsQueryRequest) (products []*response.ProductsResponse, paging paginator.Pagination, err error) GetAll(req request.ProductsQueryRequest) (products []*response.ProductsResponse, paging paginator.Pagination, err error)
GetOne(id uint) (product *response.ProductsResponse, err error) GetOne(id uint) (product *response.ProductsResponse, err error)
Create(req request.ProductsCreateRequest) (product *response.ProductsResponse, err error) Create(c *fiber.Ctx, req request.ProductsCreateRequest) (product *response.ProductsResponse, err error)
Update(id uint, req request.ProductsUpdateRequest) (product *response.ProductsResponse, err error) Update(id uint, req request.ProductsUpdateRequest) (product *response.ProductsResponse, err error)
Delete(id uint) (err error) Delete(id uint) (err error)
UploadFileToMinio(c *fiber.Ctx, fileKey string) (filePath *string, err error)
Viewer(c *fiber.Ctx) (err error)
} }
func NewProductsService(repo repository.ProductsRepository, log zerolog.Logger, cfg *config.Config) ProductsService { func NewProductsService(repo repository.ProductsRepository, log zerolog.Logger, cfg *config.Config, minioStorage *minioStorage.MinioStorage) ProductsService {
return &productsService{ return &productsService{
Repo: repo, Repo: repo,
Log: log, Log: log,
Cfg: cfg, Cfg: cfg,
MinioStorage: minioStorage,
} }
} }
@ -66,7 +82,12 @@ func (_i *productsService) GetOne(id uint) (product *response.ProductsResponse,
return return
} }
func (_i *productsService) Create(req request.ProductsCreateRequest) (product *response.ProductsResponse, err error) { func (_i *productsService) Create(c *fiber.Ctx, req request.ProductsCreateRequest) (product *response.ProductsResponse, err error) {
// Handle file upload if exists
if filePath, uploadErr := _i.UploadFileToMinio(c, "file"); uploadErr == nil && filePath != nil {
req.ThumbnailPath = filePath
}
productEntity := req.ToEntity() productEntity := req.ToEntity()
isActive := true isActive := true
productEntity.IsActive = &isActive productEntity.IsActive = &isActive
@ -82,6 +103,71 @@ func (_i *productsService) Create(req request.ProductsCreateRequest) (product *r
return return
} }
func (_i *productsService) UploadFileToMinio(c *fiber.Ctx, fileKey string) (filePath *string, err error) {
form, err := c.MultipartForm()
if err != nil {
return nil, err
}
files := form.File[fileKey]
if len(files) == 0 {
return nil, nil // No file uploaded, return nil without error
}
fileHeader := files[0]
// Create minio connection
minioClient, err := _i.MinioStorage.ConnectMinio()
if err != nil {
return nil, err
}
bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName
// Open file
src, err := fileHeader.Open()
if err != nil {
return nil, err
}
defer src.Close()
// Process filename
filename := filepath.Base(fileHeader.Filename)
filename = strings.ReplaceAll(filename, " ", "")
filenameWithoutExt := filepath.Clean(filename[:len(filename)-len(filepath.Ext(filename))])
extension := filepath.Ext(fileHeader.Filename)[1:]
// Generate unique filename
now := time.Now()
rand.New(rand.NewSource(now.UnixNano()))
randUniqueId := rand.Intn(1000000)
newFilenameWithoutExt := filenameWithoutExt + "_" + strconv.Itoa(randUniqueId)
newFilename := newFilenameWithoutExt + "." + extension
// Create object name with path structure
objectName := fmt.Sprintf("products/upload/%d/%d/%s", now.Year(), now.Month(), newFilename)
_i.Log.Info().Str("timestamp", time.Now().
Format(time.RFC3339)).Str("Service:Resource", "Products:UploadFileToMinio").
Interface("Uploading file", objectName).Msg("")
// Upload file to MinIO
_, err = minioClient.PutObject(context.Background(), bucketName, objectName, src, fileHeader.Size, minio.PutObjectOptions{})
if err != nil {
_i.Log.Error().Str("timestamp", time.Now().
Format(time.RFC3339)).Str("Service:Resource", "Products:UploadFileToMinio").
Interface("Error uploading file", err).Msg("")
return nil, err
}
_i.Log.Info().Str("timestamp", time.Now().
Format(time.RFC3339)).Str("Service:Resource", "Products:UploadFileToMinio").
Interface("Successfully uploaded", objectName).Msg("")
return &objectName, nil
}
func (_i *productsService) Update(id uint, req request.ProductsUpdateRequest) (product *response.ProductsResponse, err error) { func (_i *productsService) Update(id uint, req request.ProductsUpdateRequest) (product *response.ProductsResponse, err error) {
productEntity := req.ToEntity() productEntity := req.ToEntity()
@ -105,3 +191,79 @@ func (_i *productsService) Delete(id uint) (err error) {
err = _i.Repo.Delete(id) err = _i.Repo.Delete(id)
return return
} }
func (_i *productsService) Viewer(c *fiber.Ctx) (err error) {
filename := c.Params("filename")
// Find product by thumbnail path
result, err := _i.Repo.FindByThumbnailPath(filename)
if err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
"error": true,
"msg": "Product file not found",
})
}
if result.ThumbnailPath == nil || *result.ThumbnailPath == "" {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
"error": true,
"msg": "Product thumbnail path not found",
})
}
ctx := context.Background()
bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName
objectName := *result.ThumbnailPath
_i.Log.Info().Str("timestamp", time.Now().
Format(time.RFC3339)).Str("Service:Resource", "Products:Viewer").
Interface("data", objectName).Msg("")
// Create minio connection
minioClient, err := _i.MinioStorage.ConnectMinio()
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": true,
"msg": err.Error(),
})
}
fileContent, err := minioClient.GetObject(ctx, bucketName, objectName, minio.GetObjectOptions{})
if err != nil {
_i.Log.Error().Str("timestamp", time.Now().
Format(time.RFC3339)).Str("Service:Resource", "Products:Viewer").
Interface("Error getting file", err).Msg("")
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": true,
"msg": "Failed to retrieve file",
})
}
defer fileContent.Close()
// Determine Content-Type based on file extension
contentType := mime.TypeByExtension("." + getFileExtension(objectName))
if contentType == "" {
contentType = "application/octet-stream" // fallback if no MIME type matches
}
c.Set("Content-Type", contentType)
if _, err := io.Copy(c.Response().BodyWriter(), fileContent); err != nil {
return err
}
return
}
func getFileExtension(filename string) string {
// split file name
parts := strings.Split(filename, ".")
// jika tidak ada ekstensi, kembalikan string kosong
if len(parts) == 1 || (len(parts) == 2 && parts[0] == "") {
return ""
}
// ambil ekstensi terakhir
return parts[len(parts)-1]
}

View File

@ -22,6 +22,7 @@ type PromotionsController interface {
Save(c *fiber.Ctx) error Save(c *fiber.Ctx) error
Update(c *fiber.Ctx) error Update(c *fiber.Ctx) error
Delete(c *fiber.Ctx) error Delete(c *fiber.Ctx) error
Viewer(c *fiber.Ctx) error
} }
func NewPromotionsController(promotionsService service.PromotionsService) PromotionsController { func NewPromotionsController(promotionsService service.PromotionsService) PromotionsController {
@ -100,23 +101,51 @@ func (_i *promotionsController) Show(c *fiber.Ctx) error {
// Save Promotion // Save Promotion
// @Summary Create Promotion // @Summary Create Promotion
// @Description API for creating Promotion // @Description API for creating Promotion with file upload
// @Tags Promotions // @Tags Promotions
// @Security Bearer // @Security Bearer
// @Param X-Client-Key header string true "Insert the X-Client-Key" // @Param X-Client-Key header string true "Insert the X-Client-Key"
// @Param payload body request.PromotionsCreateRequest true "Required payload" // @Param file formData file false "Upload file"
// @Param title formData string true "Promotion title"
// @Param description formData string false "Promotion description"
// @Success 200 {object} response.Response // @Success 200 {object} response.Response
// @Failure 400 {object} response.BadRequestError // @Failure 400 {object} response.BadRequestError
// @Failure 401 {object} response.UnauthorizedError // @Failure 401 {object} response.UnauthorizedError
// @Failure 500 {object} response.InternalServerError // @Failure 500 {object} response.InternalServerError
// @Router /promotions [post] // @Router /promotions [post]
func (_i *promotionsController) Save(c *fiber.Ctx) error { func (_i *promotionsController) Save(c *fiber.Ctx) error {
req := new(request.PromotionsCreateRequest) // Parse multipart form
if err := utilVal.ParseAndValidate(c, req); err != nil { form, err := c.MultipartForm()
return err if err != nil {
return utilRes.Resp(c, utilRes.Response{
Success: false,
Messages: utilRes.Messages{"Failed to parse form data"},
})
} }
dataResult, err := _i.promotionsService.Create(*req) // Extract form values
req := request.PromotionsCreateRequest{
Title: c.FormValue("title"),
}
if description := c.FormValue("description"); description != "" {
req.Description = &description
}
// Validate required fields
if req.Title == "" {
return utilRes.Resp(c, utilRes.Response{
Success: false,
Messages: utilRes.Messages{"Title is required"},
})
}
// Check if file is uploaded
if len(form.File["file"]) > 0 {
// File will be handled in service
}
dataResult, err := _i.promotionsService.Create(c, req)
if err != nil { if err != nil {
return err return err
} }
@ -192,3 +221,19 @@ func (_i *promotionsController) Delete(c *fiber.Ctx) error {
Messages: utilRes.Messages{"Promotion successfully deleted"}, Messages: utilRes.Messages{"Promotion successfully deleted"},
}) })
} }
// Viewer Promotion
// @Summary Viewer Promotion
// @Description API for viewing Promotion file
// @Tags Promotions
// @Security Bearer
// @Param X-Client-Key header string true "Insert the X-Client-Key"
// @Param filename path string true "Promotion File Path"
// @Success 200 {file} file
// @Failure 400 {object} response.BadRequestError
// @Failure 401 {object} response.UnauthorizedError
// @Failure 500 {object} response.InternalServerError
// @Router /promotions/viewer/{filename} [get]
func (_i *promotionsController) Viewer(c *fiber.Ctx) error {
return _i.promotionsService.Viewer(c)
}

View File

@ -46,6 +46,7 @@ func (_i *PromotionsRouter) RegisterPromotionsRoutes() {
// define routes // define routes
_i.App.Route("/promotions", func(router fiber.Router) { _i.App.Route("/promotions", func(router fiber.Router) {
router.Get("/", promotionsController.All) router.Get("/", promotionsController.All)
router.Get("/viewer/:filename", promotionsController.Viewer)
router.Get("/:id", promotionsController.Show) router.Get("/:id", promotionsController.Show)
router.Post("/", promotionsController.Save) router.Post("/", promotionsController.Save)
router.Put("/:id", promotionsController.Update) router.Put("/:id", promotionsController.Update)

View File

@ -21,6 +21,7 @@ type PromotionsRepository interface {
Create(promotion *entity.Promotions) (promotionReturn *entity.Promotions, err error) Create(promotion *entity.Promotions) (promotionReturn *entity.Promotions, err error)
Update(id uint, promotion *entity.Promotions) (err error) Update(id uint, promotion *entity.Promotions) (err error)
Delete(id uint) (err error) Delete(id uint) (err error)
FindByThumbnailPath(thumbnailPath string) (promotion *entity.Promotions, err error)
} }
func NewPromotionsRepository(db *database.Database, log zerolog.Logger) PromotionsRepository { func NewPromotionsRepository(db *database.Database, log zerolog.Logger) PromotionsRepository {
@ -83,3 +84,9 @@ func (_i *promotionsRepository) Delete(id uint) (err error) {
return return
} }
func (_i *promotionsRepository) FindByThumbnailPath(thumbnailPath string) (promotion *entity.Promotions, err error) {
promotion = &entity.Promotions{}
err = _i.DB.DB.Where("thumbnail_path LIKE ? AND is_active = ?", "%"+thumbnailPath, true).First(promotion).Error
return
}

View File

@ -1,36 +1,52 @@
package service package service
import ( import (
"context"
"errors" "errors"
"fmt"
"io"
"jaecoo-be/app/module/promotions/mapper" "jaecoo-be/app/module/promotions/mapper"
"jaecoo-be/app/module/promotions/repository" "jaecoo-be/app/module/promotions/repository"
"jaecoo-be/app/module/promotions/request" "jaecoo-be/app/module/promotions/request"
"jaecoo-be/app/module/promotions/response" "jaecoo-be/app/module/promotions/response"
"jaecoo-be/config/config" "jaecoo-be/config/config"
minioStorage "jaecoo-be/config/config"
"jaecoo-be/utils/paginator" "jaecoo-be/utils/paginator"
"math/rand"
"mime"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/gofiber/fiber/v2"
"github.com/minio/minio-go/v7"
"github.com/rs/zerolog" "github.com/rs/zerolog"
) )
type promotionsService struct { type promotionsService struct {
Repo repository.PromotionsRepository Repo repository.PromotionsRepository
Log zerolog.Logger Log zerolog.Logger
Cfg *config.Config Cfg *config.Config
MinioStorage *minioStorage.MinioStorage
} }
type PromotionsService interface { type PromotionsService interface {
GetAll(req request.PromotionsQueryRequest) (promotions []*response.PromotionsResponse, paging paginator.Pagination, err error) GetAll(req request.PromotionsQueryRequest) (promotions []*response.PromotionsResponse, paging paginator.Pagination, err error)
GetOne(id uint) (promotion *response.PromotionsResponse, err error) GetOne(id uint) (promotion *response.PromotionsResponse, err error)
Create(req request.PromotionsCreateRequest) (promotion *response.PromotionsResponse, err error) Create(c *fiber.Ctx, req request.PromotionsCreateRequest) (promotion *response.PromotionsResponse, err error)
Update(id uint, req request.PromotionsUpdateRequest) (promotion *response.PromotionsResponse, err error) Update(id uint, req request.PromotionsUpdateRequest) (promotion *response.PromotionsResponse, err error)
Delete(id uint) (err error) Delete(id uint) (err error)
UploadFileToMinio(c *fiber.Ctx, fileKey string) (filePath *string, err error)
Viewer(c *fiber.Ctx) (err error)
} }
func NewPromotionsService(repo repository.PromotionsRepository, log zerolog.Logger, cfg *config.Config) PromotionsService { func NewPromotionsService(repo repository.PromotionsRepository, log zerolog.Logger, cfg *config.Config, minioStorage *minioStorage.MinioStorage) PromotionsService {
return &promotionsService{ return &promotionsService{
Repo: repo, Repo: repo,
Log: log, Log: log,
Cfg: cfg, Cfg: cfg,
MinioStorage: minioStorage,
} }
} }
@ -66,7 +82,12 @@ func (_i *promotionsService) GetOne(id uint) (promotion *response.PromotionsResp
return return
} }
func (_i *promotionsService) Create(req request.PromotionsCreateRequest) (promotion *response.PromotionsResponse, err error) { func (_i *promotionsService) Create(c *fiber.Ctx, req request.PromotionsCreateRequest) (promotion *response.PromotionsResponse, err error) {
// Handle file upload if exists
if filePath, uploadErr := _i.UploadFileToMinio(c, "file"); uploadErr == nil && filePath != nil {
req.ThumbnailPath = filePath
}
promotionEntity := req.ToEntity() promotionEntity := req.ToEntity()
isActive := true isActive := true
promotionEntity.IsActive = &isActive promotionEntity.IsActive = &isActive
@ -82,6 +103,71 @@ func (_i *promotionsService) Create(req request.PromotionsCreateRequest) (promot
return return
} }
func (_i *promotionsService) UploadFileToMinio(c *fiber.Ctx, fileKey string) (filePath *string, err error) {
form, err := c.MultipartForm()
if err != nil {
return nil, err
}
files := form.File[fileKey]
if len(files) == 0 {
return nil, nil // No file uploaded, return nil without error
}
fileHeader := files[0]
// Create minio connection
minioClient, err := _i.MinioStorage.ConnectMinio()
if err != nil {
return nil, err
}
bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName
// Open file
src, err := fileHeader.Open()
if err != nil {
return nil, err
}
defer src.Close()
// Process filename
filename := filepath.Base(fileHeader.Filename)
filename = strings.ReplaceAll(filename, " ", "")
filenameWithoutExt := filepath.Clean(filename[:len(filename)-len(filepath.Ext(filename))])
extension := filepath.Ext(fileHeader.Filename)[1:]
// Generate unique filename
now := time.Now()
rand.New(rand.NewSource(now.UnixNano()))
randUniqueId := rand.Intn(1000000)
newFilenameWithoutExt := filenameWithoutExt + "_" + strconv.Itoa(randUniqueId)
newFilename := newFilenameWithoutExt + "." + extension
// Create object name with path structure
objectName := fmt.Sprintf("promotions/upload/%d/%d/%s", now.Year(), now.Month(), newFilename)
_i.Log.Info().Str("timestamp", time.Now().
Format(time.RFC3339)).Str("Service:Resource", "Promotions:UploadFileToMinio").
Interface("Uploading file", objectName).Msg("")
// Upload file to MinIO
_, err = minioClient.PutObject(context.Background(), bucketName, objectName, src, fileHeader.Size, minio.PutObjectOptions{})
if err != nil {
_i.Log.Error().Str("timestamp", time.Now().
Format(time.RFC3339)).Str("Service:Resource", "Promotions:UploadFileToMinio").
Interface("Error uploading file", err).Msg("")
return nil, err
}
_i.Log.Info().Str("timestamp", time.Now().
Format(time.RFC3339)).Str("Service:Resource", "Promotions:UploadFileToMinio").
Interface("Successfully uploaded", objectName).Msg("")
return &objectName, nil
}
func (_i *promotionsService) Update(id uint, req request.PromotionsUpdateRequest) (promotion *response.PromotionsResponse, err error) { func (_i *promotionsService) Update(id uint, req request.PromotionsUpdateRequest) (promotion *response.PromotionsResponse, err error) {
promotionEntity := req.ToEntity() promotionEntity := req.ToEntity()
@ -105,3 +191,79 @@ func (_i *promotionsService) Delete(id uint) (err error) {
err = _i.Repo.Delete(id) err = _i.Repo.Delete(id)
return return
} }
func (_i *promotionsService) Viewer(c *fiber.Ctx) (err error) {
filename := c.Params("filename")
// Find promotion by thumbnail path
result, err := _i.Repo.FindByThumbnailPath(filename)
if err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
"error": true,
"msg": "Promotion file not found",
})
}
if result.ThumbnailPath == nil || *result.ThumbnailPath == "" {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
"error": true,
"msg": "Promotion thumbnail path not found",
})
}
ctx := context.Background()
bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName
objectName := *result.ThumbnailPath
_i.Log.Info().Str("timestamp", time.Now().
Format(time.RFC3339)).Str("Service:Resource", "Promotions:Viewer").
Interface("data", objectName).Msg("")
// Create minio connection
minioClient, err := _i.MinioStorage.ConnectMinio()
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": true,
"msg": err.Error(),
})
}
fileContent, err := minioClient.GetObject(ctx, bucketName, objectName, minio.GetObjectOptions{})
if err != nil {
_i.Log.Error().Str("timestamp", time.Now().
Format(time.RFC3339)).Str("Service:Resource", "Promotions:Viewer").
Interface("Error getting file", err).Msg("")
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": true,
"msg": "Failed to retrieve file",
})
}
defer fileContent.Close()
// Determine Content-Type based on file extension
contentType := mime.TypeByExtension("." + getFileExtension(objectName))
if contentType == "" {
contentType = "application/octet-stream" // fallback if no MIME type matches
}
c.Set("Content-Type", contentType)
if _, err := io.Copy(c.Response().BodyWriter(), fileContent); err != nil {
return err
}
return
}
func getFileExtension(filename string) string {
// split file name
parts := strings.Split(filename, ".")
// jika tidak ada ekstensi, kembalikan string kosong
if len(parts) == 1 || (len(parts) == 2 && parts[0] == "") {
return ""
}
// ambil ekstensi terakhir
return parts[len(parts)-1]
}

View File

@ -3331,7 +3331,7 @@ const docTemplate = `{
"Bearer": [] "Bearer": []
} }
], ],
"description": "API for creating Banner", "description": "API for creating Banner with file upload",
"tags": [ "tags": [
"Banners" "Banners"
], ],
@ -3345,13 +3345,35 @@ const docTemplate = `{
"required": true "required": true
}, },
{ {
"description": "Required payload", "type": "file",
"name": "payload", "description": "Upload file",
"in": "body", "name": "file",
"required": true, "in": "formData"
"schema": { },
"$ref": "#/definitions/request.BannersCreateRequest" {
} "type": "string",
"description": "Banner title",
"name": "title",
"in": "formData",
"required": true
},
{
"type": "string",
"description": "Banner description",
"name": "description",
"in": "formData"
},
{
"type": "string",
"description": "Banner position",
"name": "position",
"in": "formData"
},
{
"type": "string",
"description": "Banner status",
"name": "status",
"in": "formData"
} }
], ],
"responses": { "responses": {
@ -3382,6 +3404,62 @@ const docTemplate = `{
} }
} }
}, },
"/banners/viewer/{filename}": {
"get": {
"security": [
{
"Bearer": []
}
],
"description": "API for viewing Banner file",
"tags": [
"Banners"
],
"summary": "Viewer Banner",
"parameters": [
{
"type": "string",
"description": "Insert the X-Client-Key",
"name": "X-Client-Key",
"in": "header",
"required": true
},
{
"type": "string",
"description": "Banner File Path",
"name": "filename",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "file"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.BadRequestError"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/response.UnauthorizedError"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.InternalServerError"
}
}
}
}
},
"/banners/{id}": { "/banners/{id}": {
"get": { "get": {
"security": [ "security": [
@ -5279,7 +5357,7 @@ const docTemplate = `{
"Bearer": [] "Bearer": []
} }
], ],
"description": "API for creating GalleryFile", "description": "API for creating GalleryFile with file upload",
"tags": [ "tags": [
"GalleryFiles" "GalleryFiles"
], ],
@ -5293,13 +5371,23 @@ const docTemplate = `{
"required": true "required": true
}, },
{ {
"description": "Required payload", "type": "file",
"name": "payload", "description": "Upload file",
"in": "body", "name": "file",
"required": true, "in": "formData"
"schema": { },
"$ref": "#/definitions/request.GalleryFilesCreateRequest" {
} "type": "integer",
"description": "Gallery ID",
"name": "gallery_id",
"in": "formData",
"required": true
},
{
"type": "string",
"description": "Gallery file title",
"name": "title",
"in": "formData"
} }
], ],
"responses": { "responses": {
@ -5330,6 +5418,62 @@ const docTemplate = `{
} }
} }
}, },
"/gallery-files/viewer/{filename}": {
"get": {
"security": [
{
"Bearer": []
}
],
"description": "API for viewing GalleryFile file",
"tags": [
"GalleryFiles"
],
"summary": "Viewer GalleryFile",
"parameters": [
{
"type": "string",
"description": "Insert the X-Client-Key",
"name": "X-Client-Key",
"in": "header",
"required": true
},
{
"type": "string",
"description": "Gallery File Path",
"name": "filename",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "file"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.BadRequestError"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/response.UnauthorizedError"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.InternalServerError"
}
}
}
}
},
"/gallery-files/{id}": { "/gallery-files/{id}": {
"get": { "get": {
"security": [ "security": [
@ -5607,7 +5751,7 @@ const docTemplate = `{
"Bearer": [] "Bearer": []
} }
], ],
"description": "API for creating ProductSpecification", "description": "API for creating ProductSpecification with file upload",
"tags": [ "tags": [
"ProductSpecifications" "ProductSpecifications"
], ],
@ -5621,13 +5765,24 @@ const docTemplate = `{
"required": true "required": true
}, },
{ {
"description": "Required payload", "type": "file",
"name": "payload", "description": "Upload file",
"in": "body", "name": "file",
"required": true, "in": "formData"
"schema": { },
"$ref": "#/definitions/request.ProductSpecificationsCreateRequest" {
} "type": "integer",
"description": "Product ID",
"name": "product_id",
"in": "formData",
"required": true
},
{
"type": "string",
"description": "Product specification title",
"name": "title",
"in": "formData",
"required": true
} }
], ],
"responses": { "responses": {
@ -5658,6 +5813,62 @@ const docTemplate = `{
} }
} }
}, },
"/product-specifications/viewer/{filename}": {
"get": {
"security": [
{
"Bearer": []
}
],
"description": "API for viewing ProductSpecification file",
"tags": [
"ProductSpecifications"
],
"summary": "Viewer ProductSpecification",
"parameters": [
{
"type": "string",
"description": "Insert the X-Client-Key",
"name": "X-Client-Key",
"in": "header",
"required": true
},
{
"type": "string",
"description": "Product Specification File Path",
"name": "filename",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "file"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.BadRequestError"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/response.UnauthorizedError"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.InternalServerError"
}
}
}
}
},
"/product-specifications/{id}": { "/product-specifications/{id}": {
"get": { "get": {
"security": [ "security": [
@ -5935,7 +6146,7 @@ const docTemplate = `{
"Bearer": [] "Bearer": []
} }
], ],
"description": "API for creating Product", "description": "API for creating Product with file upload",
"tags": [ "tags": [
"Products" "Products"
], ],
@ -5949,13 +6160,35 @@ const docTemplate = `{
"required": true "required": true
}, },
{ {
"description": "Required payload", "type": "file",
"name": "payload", "description": "Upload file",
"in": "body", "name": "file",
"required": true, "in": "formData"
"schema": { },
"$ref": "#/definitions/request.ProductsCreateRequest" {
} "type": "string",
"description": "Product title",
"name": "title",
"in": "formData",
"required": true
},
{
"type": "string",
"description": "Product variant",
"name": "variant",
"in": "formData"
},
{
"type": "number",
"description": "Product price",
"name": "price",
"in": "formData"
},
{
"type": "string",
"description": "Product colors (JSON array)",
"name": "colors",
"in": "formData"
} }
], ],
"responses": { "responses": {
@ -5986,6 +6219,62 @@ const docTemplate = `{
} }
} }
}, },
"/products/viewer/{filename}": {
"get": {
"security": [
{
"Bearer": []
}
],
"description": "API for viewing Product file",
"tags": [
"Products"
],
"summary": "Viewer Product",
"parameters": [
{
"type": "string",
"description": "Insert the X-Client-Key",
"name": "X-Client-Key",
"in": "header",
"required": true
},
{
"type": "string",
"description": "Product File Path",
"name": "filename",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "file"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.BadRequestError"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/response.UnauthorizedError"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.InternalServerError"
}
}
}
}
},
"/products/{id}": { "/products/{id}": {
"get": { "get": {
"security": [ "security": [
@ -6258,7 +6547,7 @@ const docTemplate = `{
"Bearer": [] "Bearer": []
} }
], ],
"description": "API for creating Promotion", "description": "API for creating Promotion with file upload",
"tags": [ "tags": [
"Promotions" "Promotions"
], ],
@ -6272,13 +6561,23 @@ const docTemplate = `{
"required": true "required": true
}, },
{ {
"description": "Required payload", "type": "file",
"name": "payload", "description": "Upload file",
"in": "body", "name": "file",
"required": true, "in": "formData"
"schema": { },
"$ref": "#/definitions/request.PromotionsCreateRequest" {
} "type": "string",
"description": "Promotion title",
"name": "title",
"in": "formData",
"required": true
},
{
"type": "string",
"description": "Promotion description",
"name": "description",
"in": "formData"
} }
], ],
"responses": { "responses": {
@ -6309,6 +6608,62 @@ const docTemplate = `{
} }
} }
}, },
"/promotions/viewer/{filename}": {
"get": {
"security": [
{
"Bearer": []
}
],
"description": "API for viewing Promotion file",
"tags": [
"Promotions"
],
"summary": "Viewer Promotion",
"parameters": [
{
"type": "string",
"description": "Insert the X-Client-Key",
"name": "X-Client-Key",
"in": "header",
"required": true
},
{
"type": "string",
"description": "Promotion File Path",
"name": "filename",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "file"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.BadRequestError"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/response.UnauthorizedError"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.InternalServerError"
}
}
}
}
},
"/promotions/{id}": { "/promotions/{id}": {
"get": { "get": {
"security": [ "security": [
@ -9820,29 +10175,6 @@ const docTemplate = `{
} }
} }
}, },
"request.BannersCreateRequest": {
"type": "object",
"required": [
"title"
],
"properties": {
"description": {
"type": "string"
},
"position": {
"type": "string"
},
"status": {
"type": "string"
},
"thumbnail_path": {
"type": "string"
},
"title": {
"type": "string"
}
}
},
"request.BannersUpdateRequest": { "request.BannersUpdateRequest": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -10027,23 +10359,6 @@ const docTemplate = `{
} }
} }
}, },
"request.GalleryFilesCreateRequest": {
"type": "object",
"required": [
"gallery_id"
],
"properties": {
"gallery_id": {
"type": "integer"
},
"image_path": {
"type": "string"
},
"title": {
"type": "string"
}
}
},
"request.GalleryFilesUpdateRequest": { "request.GalleryFilesUpdateRequest": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -10061,24 +10376,6 @@ const docTemplate = `{
} }
} }
}, },
"request.ProductSpecificationsCreateRequest": {
"type": "object",
"required": [
"product_id",
"title"
],
"properties": {
"product_id": {
"type": "integer"
},
"thumbnail_path": {
"type": "string"
},
"title": {
"type": "string"
}
}
},
"request.ProductSpecificationsUpdateRequest": { "request.ProductSpecificationsUpdateRequest": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -10096,32 +10393,6 @@ const docTemplate = `{
} }
} }
}, },
"request.ProductsCreateRequest": {
"type": "object",
"required": [
"title"
],
"properties": {
"colors": {
"type": "array",
"items": {
"type": "string"
}
},
"price": {
"type": "number"
},
"thumbnail_path": {
"type": "string"
},
"title": {
"type": "string"
},
"variant": {
"type": "string"
}
}
},
"request.ProductsUpdateRequest": { "request.ProductsUpdateRequest": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -10148,23 +10419,6 @@ const docTemplate = `{
} }
} }
}, },
"request.PromotionsCreateRequest": {
"type": "object",
"required": [
"title"
],
"properties": {
"description": {
"type": "string"
},
"thumbnail_path": {
"type": "string"
},
"title": {
"type": "string"
}
}
},
"request.PromotionsUpdateRequest": { "request.PromotionsUpdateRequest": {
"type": "object", "type": "object",
"properties": { "properties": {

View File

@ -3320,7 +3320,7 @@
"Bearer": [] "Bearer": []
} }
], ],
"description": "API for creating Banner", "description": "API for creating Banner with file upload",
"tags": [ "tags": [
"Banners" "Banners"
], ],
@ -3334,13 +3334,35 @@
"required": true "required": true
}, },
{ {
"description": "Required payload", "type": "file",
"name": "payload", "description": "Upload file",
"in": "body", "name": "file",
"required": true, "in": "formData"
"schema": { },
"$ref": "#/definitions/request.BannersCreateRequest" {
} "type": "string",
"description": "Banner title",
"name": "title",
"in": "formData",
"required": true
},
{
"type": "string",
"description": "Banner description",
"name": "description",
"in": "formData"
},
{
"type": "string",
"description": "Banner position",
"name": "position",
"in": "formData"
},
{
"type": "string",
"description": "Banner status",
"name": "status",
"in": "formData"
} }
], ],
"responses": { "responses": {
@ -3371,6 +3393,62 @@
} }
} }
}, },
"/banners/viewer/{filename}": {
"get": {
"security": [
{
"Bearer": []
}
],
"description": "API for viewing Banner file",
"tags": [
"Banners"
],
"summary": "Viewer Banner",
"parameters": [
{
"type": "string",
"description": "Insert the X-Client-Key",
"name": "X-Client-Key",
"in": "header",
"required": true
},
{
"type": "string",
"description": "Banner File Path",
"name": "filename",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "file"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.BadRequestError"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/response.UnauthorizedError"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.InternalServerError"
}
}
}
}
},
"/banners/{id}": { "/banners/{id}": {
"get": { "get": {
"security": [ "security": [
@ -5268,7 +5346,7 @@
"Bearer": [] "Bearer": []
} }
], ],
"description": "API for creating GalleryFile", "description": "API for creating GalleryFile with file upload",
"tags": [ "tags": [
"GalleryFiles" "GalleryFiles"
], ],
@ -5282,13 +5360,23 @@
"required": true "required": true
}, },
{ {
"description": "Required payload", "type": "file",
"name": "payload", "description": "Upload file",
"in": "body", "name": "file",
"required": true, "in": "formData"
"schema": { },
"$ref": "#/definitions/request.GalleryFilesCreateRequest" {
} "type": "integer",
"description": "Gallery ID",
"name": "gallery_id",
"in": "formData",
"required": true
},
{
"type": "string",
"description": "Gallery file title",
"name": "title",
"in": "formData"
} }
], ],
"responses": { "responses": {
@ -5319,6 +5407,62 @@
} }
} }
}, },
"/gallery-files/viewer/{filename}": {
"get": {
"security": [
{
"Bearer": []
}
],
"description": "API for viewing GalleryFile file",
"tags": [
"GalleryFiles"
],
"summary": "Viewer GalleryFile",
"parameters": [
{
"type": "string",
"description": "Insert the X-Client-Key",
"name": "X-Client-Key",
"in": "header",
"required": true
},
{
"type": "string",
"description": "Gallery File Path",
"name": "filename",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "file"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.BadRequestError"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/response.UnauthorizedError"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.InternalServerError"
}
}
}
}
},
"/gallery-files/{id}": { "/gallery-files/{id}": {
"get": { "get": {
"security": [ "security": [
@ -5596,7 +5740,7 @@
"Bearer": [] "Bearer": []
} }
], ],
"description": "API for creating ProductSpecification", "description": "API for creating ProductSpecification with file upload",
"tags": [ "tags": [
"ProductSpecifications" "ProductSpecifications"
], ],
@ -5610,13 +5754,24 @@
"required": true "required": true
}, },
{ {
"description": "Required payload", "type": "file",
"name": "payload", "description": "Upload file",
"in": "body", "name": "file",
"required": true, "in": "formData"
"schema": { },
"$ref": "#/definitions/request.ProductSpecificationsCreateRequest" {
} "type": "integer",
"description": "Product ID",
"name": "product_id",
"in": "formData",
"required": true
},
{
"type": "string",
"description": "Product specification title",
"name": "title",
"in": "formData",
"required": true
} }
], ],
"responses": { "responses": {
@ -5647,6 +5802,62 @@
} }
} }
}, },
"/product-specifications/viewer/{filename}": {
"get": {
"security": [
{
"Bearer": []
}
],
"description": "API for viewing ProductSpecification file",
"tags": [
"ProductSpecifications"
],
"summary": "Viewer ProductSpecification",
"parameters": [
{
"type": "string",
"description": "Insert the X-Client-Key",
"name": "X-Client-Key",
"in": "header",
"required": true
},
{
"type": "string",
"description": "Product Specification File Path",
"name": "filename",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "file"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.BadRequestError"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/response.UnauthorizedError"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.InternalServerError"
}
}
}
}
},
"/product-specifications/{id}": { "/product-specifications/{id}": {
"get": { "get": {
"security": [ "security": [
@ -5924,7 +6135,7 @@
"Bearer": [] "Bearer": []
} }
], ],
"description": "API for creating Product", "description": "API for creating Product with file upload",
"tags": [ "tags": [
"Products" "Products"
], ],
@ -5938,13 +6149,35 @@
"required": true "required": true
}, },
{ {
"description": "Required payload", "type": "file",
"name": "payload", "description": "Upload file",
"in": "body", "name": "file",
"required": true, "in": "formData"
"schema": { },
"$ref": "#/definitions/request.ProductsCreateRequest" {
} "type": "string",
"description": "Product title",
"name": "title",
"in": "formData",
"required": true
},
{
"type": "string",
"description": "Product variant",
"name": "variant",
"in": "formData"
},
{
"type": "number",
"description": "Product price",
"name": "price",
"in": "formData"
},
{
"type": "string",
"description": "Product colors (JSON array)",
"name": "colors",
"in": "formData"
} }
], ],
"responses": { "responses": {
@ -5975,6 +6208,62 @@
} }
} }
}, },
"/products/viewer/{filename}": {
"get": {
"security": [
{
"Bearer": []
}
],
"description": "API for viewing Product file",
"tags": [
"Products"
],
"summary": "Viewer Product",
"parameters": [
{
"type": "string",
"description": "Insert the X-Client-Key",
"name": "X-Client-Key",
"in": "header",
"required": true
},
{
"type": "string",
"description": "Product File Path",
"name": "filename",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "file"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.BadRequestError"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/response.UnauthorizedError"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.InternalServerError"
}
}
}
}
},
"/products/{id}": { "/products/{id}": {
"get": { "get": {
"security": [ "security": [
@ -6247,7 +6536,7 @@
"Bearer": [] "Bearer": []
} }
], ],
"description": "API for creating Promotion", "description": "API for creating Promotion with file upload",
"tags": [ "tags": [
"Promotions" "Promotions"
], ],
@ -6261,13 +6550,23 @@
"required": true "required": true
}, },
{ {
"description": "Required payload", "type": "file",
"name": "payload", "description": "Upload file",
"in": "body", "name": "file",
"required": true, "in": "formData"
"schema": { },
"$ref": "#/definitions/request.PromotionsCreateRequest" {
} "type": "string",
"description": "Promotion title",
"name": "title",
"in": "formData",
"required": true
},
{
"type": "string",
"description": "Promotion description",
"name": "description",
"in": "formData"
} }
], ],
"responses": { "responses": {
@ -6298,6 +6597,62 @@
} }
} }
}, },
"/promotions/viewer/{filename}": {
"get": {
"security": [
{
"Bearer": []
}
],
"description": "API for viewing Promotion file",
"tags": [
"Promotions"
],
"summary": "Viewer Promotion",
"parameters": [
{
"type": "string",
"description": "Insert the X-Client-Key",
"name": "X-Client-Key",
"in": "header",
"required": true
},
{
"type": "string",
"description": "Promotion File Path",
"name": "filename",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "file"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/response.BadRequestError"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/response.UnauthorizedError"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/response.InternalServerError"
}
}
}
}
},
"/promotions/{id}": { "/promotions/{id}": {
"get": { "get": {
"security": [ "security": [
@ -9809,29 +10164,6 @@
} }
} }
}, },
"request.BannersCreateRequest": {
"type": "object",
"required": [
"title"
],
"properties": {
"description": {
"type": "string"
},
"position": {
"type": "string"
},
"status": {
"type": "string"
},
"thumbnail_path": {
"type": "string"
},
"title": {
"type": "string"
}
}
},
"request.BannersUpdateRequest": { "request.BannersUpdateRequest": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -10016,23 +10348,6 @@
} }
} }
}, },
"request.GalleryFilesCreateRequest": {
"type": "object",
"required": [
"gallery_id"
],
"properties": {
"gallery_id": {
"type": "integer"
},
"image_path": {
"type": "string"
},
"title": {
"type": "string"
}
}
},
"request.GalleryFilesUpdateRequest": { "request.GalleryFilesUpdateRequest": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -10050,24 +10365,6 @@
} }
} }
}, },
"request.ProductSpecificationsCreateRequest": {
"type": "object",
"required": [
"product_id",
"title"
],
"properties": {
"product_id": {
"type": "integer"
},
"thumbnail_path": {
"type": "string"
},
"title": {
"type": "string"
}
}
},
"request.ProductSpecificationsUpdateRequest": { "request.ProductSpecificationsUpdateRequest": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -10085,32 +10382,6 @@
} }
} }
}, },
"request.ProductsCreateRequest": {
"type": "object",
"required": [
"title"
],
"properties": {
"colors": {
"type": "array",
"items": {
"type": "string"
}
},
"price": {
"type": "number"
},
"thumbnail_path": {
"type": "string"
},
"title": {
"type": "string"
},
"variant": {
"type": "string"
}
}
},
"request.ProductsUpdateRequest": { "request.ProductsUpdateRequest": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -10137,23 +10408,6 @@
} }
} }
}, },
"request.PromotionsCreateRequest": {
"type": "object",
"required": [
"title"
],
"properties": {
"description": {
"type": "string"
},
"thumbnail_path": {
"type": "string"
},
"title": {
"type": "string"
}
}
},
"request.PromotionsUpdateRequest": { "request.PromotionsUpdateRequest": {
"type": "object", "type": "object",
"properties": { "properties": {

View File

@ -281,21 +281,6 @@ definitions:
- title - title
- typeId - typeId
type: object type: object
request.BannersCreateRequest:
properties:
description:
type: string
position:
type: string
status:
type: string
thumbnail_path:
type: string
title:
type: string
required:
- title
type: object
request.BannersUpdateRequest: request.BannersUpdateRequest:
properties: properties:
description: description:
@ -420,17 +405,6 @@ definitions:
title: title:
type: string type: string
type: object type: object
request.GalleryFilesCreateRequest:
properties:
gallery_id:
type: integer
image_path:
type: string
title:
type: string
required:
- gallery_id
type: object
request.GalleryFilesUpdateRequest: request.GalleryFilesUpdateRequest:
properties: properties:
gallery_id: gallery_id:
@ -442,18 +416,6 @@ definitions:
title: title:
type: string type: string
type: object type: object
request.ProductSpecificationsCreateRequest:
properties:
product_id:
type: integer
thumbnail_path:
type: string
title:
type: string
required:
- product_id
- title
type: object
request.ProductSpecificationsUpdateRequest: request.ProductSpecificationsUpdateRequest:
properties: properties:
is_active: is_active:
@ -465,23 +427,6 @@ definitions:
title: title:
type: string type: string
type: object type: object
request.ProductsCreateRequest:
properties:
colors:
items:
type: string
type: array
price:
type: number
thumbnail_path:
type: string
title:
type: string
variant:
type: string
required:
- title
type: object
request.ProductsUpdateRequest: request.ProductsUpdateRequest:
properties: properties:
colors: colors:
@ -499,17 +444,6 @@ definitions:
variant: variant:
type: string type: string
type: object type: object
request.PromotionsCreateRequest:
properties:
description:
type: string
thumbnail_path:
type: string
title:
type: string
required:
- title
type: object
request.PromotionsUpdateRequest: request.PromotionsUpdateRequest:
properties: properties:
description: description:
@ -3028,19 +2962,34 @@ paths:
tags: tags:
- Banners - Banners
post: post:
description: API for creating Banner description: API for creating Banner with file upload
parameters: parameters:
- description: Insert the X-Client-Key - description: Insert the X-Client-Key
in: header in: header
name: X-Client-Key name: X-Client-Key
required: true required: true
type: string type: string
- description: Required payload - description: Upload file
in: body in: formData
name: payload name: file
type: file
- description: Banner title
in: formData
name: title
required: true required: true
schema: type: string
$ref: '#/definitions/request.BannersCreateRequest' - description: Banner description
in: formData
name: description
type: string
- description: Banner position
in: formData
name: position
type: string
- description: Banner status
in: formData
name: status
type: string
responses: responses:
"200": "200":
description: OK description: OK
@ -3175,6 +3124,42 @@ paths:
summary: Update Banner summary: Update Banner
tags: tags:
- Banners - Banners
/banners/viewer/{filename}:
get:
description: API for viewing Banner file
parameters:
- description: Insert the X-Client-Key
in: header
name: X-Client-Key
required: true
type: string
- description: Banner File Path
in: path
name: filename
required: true
type: string
responses:
"200":
description: OK
schema:
type: file
"400":
description: Bad Request
schema:
$ref: '#/definitions/response.BadRequestError'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/response.UnauthorizedError'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.InternalServerError'
security:
- Bearer: []
summary: Viewer Banner
tags:
- Banners
/cities: /cities:
get: get:
description: API for getting all Cities description: API for getting all Cities
@ -4267,19 +4252,26 @@ paths:
tags: tags:
- GalleryFiles - GalleryFiles
post: post:
description: API for creating GalleryFile description: API for creating GalleryFile with file upload
parameters: parameters:
- description: Insert the X-Client-Key - description: Insert the X-Client-Key
in: header in: header
name: X-Client-Key name: X-Client-Key
required: true required: true
type: string type: string
- description: Required payload - description: Upload file
in: body in: formData
name: payload name: file
type: file
- description: Gallery ID
in: formData
name: gallery_id
required: true required: true
schema: type: integer
$ref: '#/definitions/request.GalleryFilesCreateRequest' - description: Gallery file title
in: formData
name: title
type: string
responses: responses:
"200": "200":
description: OK description: OK
@ -4414,6 +4406,42 @@ paths:
summary: Update GalleryFile summary: Update GalleryFile
tags: tags:
- GalleryFiles - GalleryFiles
/gallery-files/viewer/{filename}:
get:
description: API for viewing GalleryFile file
parameters:
- description: Insert the X-Client-Key
in: header
name: X-Client-Key
required: true
type: string
- description: Gallery File Path
in: path
name: filename
required: true
type: string
responses:
"200":
description: OK
schema:
type: file
"400":
description: Bad Request
schema:
$ref: '#/definitions/response.BadRequestError'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/response.UnauthorizedError'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.InternalServerError'
security:
- Bearer: []
summary: Viewer GalleryFile
tags:
- GalleryFiles
/product-specifications: /product-specifications:
get: get:
description: API for getting all ProductSpecifications description: API for getting all ProductSpecifications
@ -4476,19 +4504,27 @@ paths:
tags: tags:
- ProductSpecifications - ProductSpecifications
post: post:
description: API for creating ProductSpecification description: API for creating ProductSpecification with file upload
parameters: parameters:
- description: Insert the X-Client-Key - description: Insert the X-Client-Key
in: header in: header
name: X-Client-Key name: X-Client-Key
required: true required: true
type: string type: string
- description: Required payload - description: Upload file
in: body in: formData
name: payload name: file
type: file
- description: Product ID
in: formData
name: product_id
required: true required: true
schema: type: integer
$ref: '#/definitions/request.ProductSpecificationsCreateRequest' - description: Product specification title
in: formData
name: title
required: true
type: string
responses: responses:
"200": "200":
description: OK description: OK
@ -4623,6 +4659,42 @@ paths:
summary: Update ProductSpecification summary: Update ProductSpecification
tags: tags:
- ProductSpecifications - ProductSpecifications
/product-specifications/viewer/{filename}:
get:
description: API for viewing ProductSpecification file
parameters:
- description: Insert the X-Client-Key
in: header
name: X-Client-Key
required: true
type: string
- description: Product Specification File Path
in: path
name: filename
required: true
type: string
responses:
"200":
description: OK
schema:
type: file
"400":
description: Bad Request
schema:
$ref: '#/definitions/response.BadRequestError'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/response.UnauthorizedError'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.InternalServerError'
security:
- Bearer: []
summary: Viewer ProductSpecification
tags:
- ProductSpecifications
/products: /products:
get: get:
description: API for getting all Products description: API for getting all Products
@ -4685,19 +4757,34 @@ paths:
tags: tags:
- Products - Products
post: post:
description: API for creating Product description: API for creating Product with file upload
parameters: parameters:
- description: Insert the X-Client-Key - description: Insert the X-Client-Key
in: header in: header
name: X-Client-Key name: X-Client-Key
required: true required: true
type: string type: string
- description: Required payload - description: Upload file
in: body in: formData
name: payload name: file
type: file
- description: Product title
in: formData
name: title
required: true required: true
schema: type: string
$ref: '#/definitions/request.ProductsCreateRequest' - description: Product variant
in: formData
name: variant
type: string
- description: Product price
in: formData
name: price
type: number
- description: Product colors (JSON array)
in: formData
name: colors
type: string
responses: responses:
"200": "200":
description: OK description: OK
@ -4832,6 +4919,42 @@ paths:
summary: Update Product summary: Update Product
tags: tags:
- Products - Products
/products/viewer/{filename}:
get:
description: API for viewing Product file
parameters:
- description: Insert the X-Client-Key
in: header
name: X-Client-Key
required: true
type: string
- description: Product File Path
in: path
name: filename
required: true
type: string
responses:
"200":
description: OK
schema:
type: file
"400":
description: Bad Request
schema:
$ref: '#/definitions/response.BadRequestError'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/response.UnauthorizedError'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.InternalServerError'
security:
- Bearer: []
summary: Viewer Product
tags:
- Products
/promotions: /promotions:
get: get:
description: API for getting all Promotions description: API for getting all Promotions
@ -4891,19 +5014,26 @@ paths:
tags: tags:
- Promotions - Promotions
post: post:
description: API for creating Promotion description: API for creating Promotion with file upload
parameters: parameters:
- description: Insert the X-Client-Key - description: Insert the X-Client-Key
in: header in: header
name: X-Client-Key name: X-Client-Key
required: true required: true
type: string type: string
- description: Required payload - description: Upload file
in: body in: formData
name: payload name: file
type: file
- description: Promotion title
in: formData
name: title
required: true required: true
schema: type: string
$ref: '#/definitions/request.PromotionsCreateRequest' - description: Promotion description
in: formData
name: description
type: string
responses: responses:
"200": "200":
description: OK description: OK
@ -5038,6 +5168,42 @@ paths:
summary: Update Promotion summary: Update Promotion
tags: tags:
- Promotions - Promotions
/promotions/viewer/{filename}:
get:
description: API for viewing Promotion file
parameters:
- description: Insert the X-Client-Key
in: header
name: X-Client-Key
required: true
type: string
- description: Promotion File Path
in: path
name: filename
required: true
type: string
responses:
"200":
description: OK
schema:
type: file
"400":
description: Bad Request
schema:
$ref: '#/definitions/response.BadRequestError'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/response.UnauthorizedError'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/response.InternalServerError'
security:
- Bearer: []
summary: Viewer Promotion
tags:
- Promotions
/provinces: /provinces:
get: get:
description: API for getting all Provinces description: API for getting all Provinces