feat: update banner image upload, gallery files, product spec, product, promotion
This commit is contained in:
parent
85bf2dd456
commit
d6aa8b6c2c
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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]
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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]
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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]
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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]
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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]
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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": {
|
||||||
|
|
|
||||||
|
|
@ -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": {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue