package seriessetmaster
import (
"bytes"
"encoding/base64"
"fmt"
"image"
_ "image/gif"
_ "image/jpeg"
"image/png"
"io"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"cardverse/internal/pkg/response"
"cardverse/internal/pkg/utils"
"cardverse/internal/pkg/validator"
"github.com/chai2010/webp"
"github.com/gin-gonic/gin"
)
type Handler struct {
service Service
}
func NewHandler(service Service) *Handler {
return &Handler{service: service}
}
func (h *Handler) GetAllSeries(c *gin.Context) {
var query ListSeriesQuery
if err := c.ShouldBindQuery(&query); err != nil {
response.Error(c, http.StatusBadRequest, "query parameter tidak valid", validator.TranslateError(err))
return
}
list, err := h.service.GetAllSeries(query)
if err != nil {
response.Error(c, http.StatusInternalServerError, "gagal mengambil data series", err.Error())
return
}
response.Success(c, http.StatusOK, "berhasil mengambil data series", ToSeriesSetMasterResponseList(list))
}
func (h *Handler) GetAllSets(c *gin.Context) {
var query ListSetQuery
if err := c.ShouldBindQuery(&query); err != nil {
response.Error(c, http.StatusBadRequest, "query parameter tidak valid", validator.TranslateError(err))
return
}
list, err := h.service.GetAllSets(query)
if err != nil {
response.Error(c, http.StatusInternalServerError, "gagal mengambil data set", err.Error())
return
}
response.Success(c, http.StatusOK, "berhasil mengambil data set", ToSeriesSetMasterResponseList(list))
}
func (h *Handler) GetByID(c *gin.Context) {
id, err := parseID(c)
if err != nil {
response.Error(c, http.StatusBadRequest, "id tidak valid", []validator.FieldError{
{Field: "id", Message: "id harus berupa angka"},
})
return
}
m, err := h.service.GetByID(id)
if err != nil {
response.Error(c, http.StatusNotFound, err.Error(), nil)
return
}
response.Success(c, http.StatusOK, "berhasil mengambil data", ToSeriesSetMasterResponse(*m))
}
func (h *Handler) GetSetByCode(c *gin.Context) {
code := c.Param("code")
if code == "" {
response.Error(c, http.StatusBadRequest, "set_code tidak boleh kosong", nil)
return
}
m, err := h.service.GetSetByCode(code)
if err != nil {
response.Error(c, http.StatusNotFound, err.Error(), nil)
return
}
response.Success(c, http.StatusOK, "berhasil mengambil data set", ToSeriesSetMasterResponse(*m))
}
func (h *Handler) CreateSeries(c *gin.Context) {
var req CreateSeriesRequest
contentType := c.Request.Header.Get("Content-Type")
if strings.Contains(contentType, "multipart/form-data") {
req.Name = c.PostForm("name")
if req.Name == "" {
response.Error(c, http.StatusBadRequest, "name wajib diisi", nil)
return
}
fileHeader, err := c.FormFile("image")
if err == nil && fileHeader != nil {
if fileHeader.Size > 5*1024*1024 {
response.Error(c, http.StatusBadRequest, "ukuran file gambar maksimal 5MB", nil)
return
}
srcFile, err := fileHeader.Open()
if err == nil {
defer srcFile.Close()
img, _, err := image.Decode(srcFile)
if err == nil {
slug := utils.UnderscoreString(req.Name)
uploadDir := "./public/images/series"
_ = os.MkdirAll(uploadDir, os.ModePerm)
filename := fmt.Sprintf("%s.webp", slug)
filepathDst := filepath.Join(uploadDir, filename)
out, err := os.Create(filepathDst)
if err == nil {
defer out.Close()
if err := webp.Encode(out, img, &webp.Options{Lossless: false, Quality: 90}); err == nil {
req.Image = fmt.Sprintf("/images/series/%s", filename)
}
}
}
}
}
} else {
if err := c.ShouldBindJSON(&req); err != nil {
response.Error(c, http.StatusBadRequest, "input tidak valid", validator.TranslateError(err))
return
}
}
created, err := h.service.CreateSeries(req)
if err != nil {
response.Error(c, http.StatusBadRequest, err.Error(), nil)
return
}
response.Success(c, http.StatusCreated, "series berhasil dibuat", ToSeriesSetMasterResponse(*created))
}
func (h *Handler) CreateSet(c *gin.Context) {
var req CreateSetRequest
contentType := c.Request.Header.Get("Content-Type")
if strings.Contains(contentType, "multipart/form-data") {
if parentIDStr := c.PostForm("parent_id"); parentIDStr != "" {
if pID, err := strconv.ParseUint(parentIDStr, 10, 64); err == nil {
req.ParentID = uint(pID)
}
}
req.SetCode = c.PostForm("set_code")
req.SetName = c.PostForm("set_name")
if req.ParentID == 0 || req.SetCode == "" || req.SetName == "" {
response.Error(c, http.StatusBadRequest, "parent_id, set_code, dan set_name wajib diisi", nil)
return
}
parentSeries, err := h.service.GetByID(req.ParentID)
if err != nil || parentSeries == nil || parentSeries.ParentID != nil {
response.Error(c, http.StatusBadRequest, "parent_id tidak ditemukan atau bukan merupakan series", nil)
return
}
seriesSlug := utils.Slugify(parentSeries.SeriesName)
uploadDir := fmt.Sprintf("./public/images/%s/%s", seriesSlug, req.SetCode)
// Handle file 'image'
fileHeader, err := c.FormFile("image")
if err == nil && fileHeader != nil {
if fileHeader.Size <= 5*1024*1024 {
srcFile, err := fileHeader.Open()
if err == nil {
defer srcFile.Close()
img, _, err := image.Decode(srcFile)
if err == nil {
_ = os.MkdirAll(uploadDir, os.ModePerm)
imgSlug := utils.UnderscoreString(req.SetName)
filename := fmt.Sprintf("%s.webp", imgSlug)
filepathDst := filepath.Join(uploadDir, filename)
out, err := os.Create(filepathDst)
if err == nil {
defer out.Close()
if err := webp.Encode(out, img, &webp.Options{Lossless: false, Quality: 90}); err == nil {
req.Image = fmt.Sprintf("/images/%s/%s/%s", seriesSlug, req.SetCode, filename)
}
}
}
}
}
}
// Handle file 'logo'
logoHeader, err := c.FormFile("logo")
if err == nil && logoHeader != nil {
if logoHeader.Size <= 5*1024*1024 {
_ = os.MkdirAll(uploadDir, os.ModePerm)
origFilename := logoHeader.Filename
ext := filepath.Ext(origFilename)
baseName := strings.TrimSuffix(origFilename, ext)
if baseName == "" {
baseName = req.SetCode
}
var logoFilename string
if (len(req.SetCode) > 5 || len(baseName) > 5) && (strings.Contains(req.SetCode, "-P") || strings.Contains(baseName, "-P")) {
logoFilename = "PROMO.svg"
} else {
logoFilename = fmt.Sprintf("%s.svg", baseName)
}
filepathDstLogo := filepath.Join(uploadDir, logoFilename)
srcLogo, err := logoHeader.Open()
if err == nil {
defer srcLogo.Close()
logoBytes, err := io.ReadAll(srcLogo)
if err == nil {
checkLen := len(logoBytes)
if checkLen > 512 {
checkLen = 512
}
isSVG := strings.EqualFold(ext, ".svg") || strings.Contains(strings.ToLower(string(logoBytes[:checkLen])), "`, w, h, w, h, w, h, b64)
if err := os.WriteFile(filepathDstLogo, []byte(svgContent), 0644); err == nil {
req.Logo = fmt.Sprintf("/images/%s/%s/%s", seriesSlug, req.SetCode, logoFilename)
}
}
} else {
if err := c.SaveUploadedFile(logoHeader, filepathDstLogo); err == nil {
req.Logo = fmt.Sprintf("/images/%s/%s/%s", seriesSlug, req.SetCode, logoFilename)
}
}
}
}
}
}
}
} else {
if err := c.ShouldBindJSON(&req); err != nil {
response.Error(c, http.StatusBadRequest, "input tidak valid", validator.TranslateError(err))
return
}
}
created, err := h.service.CreateSet(req)
if err != nil {
response.Error(c, http.StatusBadRequest, err.Error(), nil)
return
}
response.Success(c, http.StatusCreated, "set berhasil dibuat", ToSeriesSetMasterResponse(*created))
}
func (h *Handler) UpdateSeries(c *gin.Context) {
id, err := parseID(c)
if err != nil {
response.Error(c, http.StatusBadRequest, "id tidak valid", []validator.FieldError{
{Field: "id", Message: "id harus berupa angka"},
})
return
}
var req UpdateSeriesRequest
contentType := c.Request.Header.Get("Content-Type")
if strings.Contains(contentType, "multipart/form-data") {
req.Name = c.PostForm("name")
fileHeader, err := c.FormFile("image")
if err == nil && fileHeader != nil {
if fileHeader.Size > 5*1024*1024 {
response.Error(c, http.StatusBadRequest, "ukuran file gambar maksimal 5MB", nil)
return
}
srcFile, err := fileHeader.Open()
if err != nil {
response.Error(c, http.StatusInternalServerError, "gagal membaca file gambar series", err.Error())
return
}
defer srcFile.Close()
img, _, err := image.Decode(srcFile)
if err != nil {
response.Error(c, http.StatusBadRequest, "file yang diunggah bukan format gambar yang valid", err.Error())
return
}
seriesName := req.Name
if seriesName == "" {
m, err := h.service.GetByID(id)
if err == nil && m != nil {
seriesName = m.SeriesName
}
}
slug := utils.UnderscoreString(seriesName)
uploadDir := "./public/images/series"
if err := os.MkdirAll(uploadDir, os.ModePerm); err != nil {
response.Error(c, http.StatusInternalServerError, "gagal membuat direktori penyimpanan series", err.Error())
return
}
filename := fmt.Sprintf("%s.webp", slug)
filepathDst := filepath.Join(uploadDir, filename)
out, err := os.Create(filepathDst)
if err != nil {
response.Error(c, http.StatusInternalServerError, "gagal membuat file gambar series", err.Error())
return
}
defer out.Close()
if err := webp.Encode(out, img, &webp.Options{Lossless: false, Quality: 90}); err != nil {
response.Error(c, http.StatusInternalServerError, "gagal mengompresi gambar ke format WebP", err.Error())
return
}
req.Image = fmt.Sprintf("/images/series/%s", filename)
}
} else {
if err := c.ShouldBindJSON(&req); err != nil {
response.Error(c, http.StatusBadRequest, "input tidak valid", validator.TranslateError(err))
return
}
}
updated, err := h.service.UpdateSeries(id, req)
if err != nil {
response.Error(c, http.StatusBadRequest, err.Error(), nil)
return
}
response.Success(c, http.StatusOK, "series berhasil diupdate", ToSeriesSetMasterResponse(*updated))
}
func (h *Handler) UpdateSet(c *gin.Context) {
id, err := parseID(c)
if err != nil {
response.Error(c, http.StatusBadRequest, "id tidak valid", []validator.FieldError{
{Field: "id", Message: "id harus berupa angka"},
})
return
}
mOld, err := h.service.GetByID(id)
if err != nil || mOld == nil {
response.Error(c, http.StatusNotFound, "data set tidak ditemukan", nil)
return
}
var req UpdateSetRequest
contentType := c.Request.Header.Get("Content-Type")
if strings.Contains(contentType, "multipart/form-data") {
req.SetCode = c.PostForm("set_code")
req.SetName = c.PostForm("set_name")
if parentIDStr := c.PostForm("parent_id"); parentIDStr != "" {
if pID, err := strconv.ParseUint(parentIDStr, 10, 64); err == nil {
uintPID := uint(pID)
req.ParentID = &uintPID
}
}
setCode := req.SetCode
if setCode == "" {
setCode = mOld.SetCode
}
setName := req.SetName
if setName == "" {
setName = mOld.SetName
}
seriesName := mOld.SeriesName
if req.ParentID != nil {
parentSeries, err := h.service.GetByID(*req.ParentID)
if err == nil && parentSeries != nil && parentSeries.ParentID == nil {
seriesName = parentSeries.SeriesName
}
}
seriesSlug := utils.Slugify(seriesName)
uploadDir := fmt.Sprintf("./public/images/%s/%s", seriesSlug, setCode)
// 1. Handle file 'image' (WebP format, nama file = utils.UnderscoreString(setName) + ".webp")
fileHeader, err := c.FormFile("image")
if err == nil && fileHeader != nil {
if fileHeader.Size > 5*1024*1024 {
response.Error(c, http.StatusBadRequest, "ukuran file gambar maksimal 5MB", nil)
return
}
srcFile, err := fileHeader.Open()
if err == nil {
defer srcFile.Close()
img, _, err := image.Decode(srcFile)
if err == nil {
_ = os.MkdirAll(uploadDir, os.ModePerm)
imgSlug := utils.UnderscoreString(setName)
filename := fmt.Sprintf("%s.webp", imgSlug)
filepathDst := filepath.Join(uploadDir, filename)
out, err := os.Create(filepathDst)
if err == nil {
defer out.Close()
if err := webp.Encode(out, img, &webp.Options{Lossless: false, Quality: 90}); err == nil {
req.Image = fmt.Sprintf("/images/%s/%s/%s", seriesSlug, setCode, filename)
}
}
}
}
}
// 2. Handle file 'logo' (Format SVG: file SVG disimpan langsung, JPG/PNG/GIF/WebP dikonversi ke SVG XML)
logoHeader, err := c.FormFile("logo")
if err == nil && logoHeader != nil {
if logoHeader.Size > 5*1024*1024 {
response.Error(c, http.StatusBadRequest, "ukuran file logo maksimal 5MB", nil)
return
}
_ = os.MkdirAll(uploadDir, os.ModePerm)
origFilename := logoHeader.Filename
ext := filepath.Ext(origFilename)
baseName := strings.TrimSuffix(origFilename, ext)
if baseName == "" {
baseName = setCode
}
var logoFilename string
if (len(setCode) > 5 || len(baseName) > 5) && (strings.Contains(setCode, "-P") || strings.Contains(baseName, "-P")) {
logoFilename = "PROMO.svg"
} else {
logoFilename = fmt.Sprintf("%s.svg", baseName)
}
filepathDstLogo := filepath.Join(uploadDir, logoFilename)
srcLogo, err := logoHeader.Open()
if err == nil {
defer srcLogo.Close()
logoBytes, err := io.ReadAll(srcLogo)
if err == nil {
checkLen := len(logoBytes)
if checkLen > 512 {
checkLen = 512
}
isSVG := strings.EqualFold(ext, ".svg") || strings.Contains(strings.ToLower(string(logoBytes[:checkLen])), "`, w, h, w, h, w, h, b64)
if err := os.WriteFile(filepathDstLogo, []byte(svgContent), 0644); err == nil {
req.Logo = fmt.Sprintf("/images/%s/%s/%s", seriesSlug, setCode, logoFilename)
}
}
} else {
if err := c.SaveUploadedFile(logoHeader, filepathDstLogo); err == nil {
req.Logo = fmt.Sprintf("/images/%s/%s/%s", seriesSlug, setCode, logoFilename)
}
}
}
}
}
}
} else {
if err := c.ShouldBindJSON(&req); err != nil {
response.Error(c, http.StatusBadRequest, "input tidak valid", validator.TranslateError(err))
return
}
}
updated, err := h.service.UpdateSet(id, req)
if err != nil {
response.Error(c, http.StatusBadRequest, err.Error(), nil)
return
}
response.Success(c, http.StatusOK, "set berhasil diupdate", ToSeriesSetMasterResponse(*updated))
}
func (h *Handler) Delete(c *gin.Context) {
id, err := parseID(c)
if err != nil {
response.Error(c, http.StatusBadRequest, "id tidak valid", []validator.FieldError{
{Field: "id", Message: "id harus berupa angka"},
})
return
}
if err := h.service.Delete(id); err != nil {
response.Error(c, http.StatusNotFound, err.Error(), nil)
return
}
response.Success(c, http.StatusOK, "berhasil menghapus data series/set", nil)
}
func parseID(c *gin.Context) (uint, error) {
idParam := c.Param("id")
id, err := strconv.ParseUint(idParam, 10, 64)
if err != nil {
return 0, err
}
return uint(id), nil
}