551 lines
16 KiB
Go
551 lines
16 KiB
Go
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])), "<svg")
|
|
|
|
if isSVG {
|
|
if err := os.WriteFile(filepathDstLogo, logoBytes, 0644); err == nil {
|
|
req.Logo = fmt.Sprintf("/images/%s/%s/%s", seriesSlug, req.SetCode, logoFilename)
|
|
}
|
|
} else {
|
|
img, _, err := image.Decode(bytes.NewReader(logoBytes))
|
|
if err == nil && img != nil {
|
|
var buf bytes.Buffer
|
|
if err := png.Encode(&buf, img); err == nil {
|
|
b64 := base64.StdEncoding.EncodeToString(buf.Bytes())
|
|
bounds := img.Bounds()
|
|
w, h := bounds.Dx(), bounds.Dy()
|
|
svgContent := fmt.Sprintf(`<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="%d" height="%d" viewBox="0 0 %d %d"><image width="%d" height="%d" xlink:href="data:image/png;base64,%s"/></svg>`, 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])), "<svg")
|
|
|
|
if isSVG {
|
|
if err := os.WriteFile(filepathDstLogo, logoBytes, 0644); err == nil {
|
|
req.Logo = fmt.Sprintf("/images/%s/%s/%s", seriesSlug, setCode, logoFilename)
|
|
}
|
|
} else {
|
|
img, _, err := image.Decode(bytes.NewReader(logoBytes))
|
|
if err == nil && img != nil {
|
|
var buf bytes.Buffer
|
|
if err := png.Encode(&buf, img); err == nil {
|
|
b64 := base64.StdEncoding.EncodeToString(buf.Bytes())
|
|
bounds := img.Bounds()
|
|
w, h := bounds.Dx(), bounds.Dy()
|
|
svgContent := fmt.Sprintf(`<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="%d" height="%d" viewBox="0 0 %d %d"><image width="%d" height="%d" xlink:href="data:image/png;base64,%s"/></svg>`, 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
|
|
}
|