package cardmaster import ( "encoding/json" "fmt" "image" _ "image/gif" _ "image/jpeg" _ "image/png" "net/http" "os" "path/filepath" "strconv" "strings" "time" "cardverse/internal/modules/seriessetmaster" "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 seriesSetRepo seriessetmaster.Repository } func NewHandler(service Service, seriesSetRepo seriessetmaster.Repository) *Handler { return &Handler{ service: service, seriesSetRepo: seriesSetRepo, } } func (h *Handler) GetAll(c *gin.Context) { var query ListCardMasterQuery if err := c.ShouldBindQuery(&query); err != nil { response.Error(c, http.StatusBadRequest, "query parameter tidak valid", validator.TranslateError(err)) return } list, total, err := h.service.GetAllPaginated(query) if err != nil { response.Error(c, http.StatusInternalServerError, "gagal mengambil data kartu", err.Error()) return } pagination := response.NewPagination(query.Page, query.Limit, total) response.SuccessWithPagination(c, http.StatusOK, "berhasil mengambil data kartu", ToCardMasterResponseList(list), pagination) } func (h *Handler) GetElements(c *gin.Context) { elements := h.service.GetElements() response.Success(c, http.StatusOK, "berhasil mengambil data elemen", elements) } 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 } cardData, 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 kartu", ToCardMasterResponse(*cardData)) } func (h *Handler) Create(c *gin.Context) { var req CreateCardMasterRequest contentType := c.GetHeader("Content-Type") if strings.HasPrefix(contentType, "multipart/form-data") { if jsonData := c.PostForm("data"); jsonData != "" { if err := json.Unmarshal([]byte(jsonData), &req); err != nil { response.Error(c, http.StatusBadRequest, "format data JSON pada multipart tidak valid", nil) return } } else { if err := c.ShouldBind(&req); err != nil { response.Error(c, http.StatusBadRequest, "input tidak valid", validator.TranslateError(err)) return } } fileHeader, err := c.FormFile("image") if err == nil && fileHeader != nil { if fileHeader.Size > 10*1024*1024 { response.Error(c, http.StatusBadRequest, "ukuran file gambar maksimal 10MB", nil) return } if req.IDSet == 0 { response.Error(c, http.StatusBadRequest, "id_set wajib diisi untuk upload gambar", nil) return } setInfo, err := h.seriesSetRepo.FindByID(req.IDSet) if err != nil || setInfo == nil { response.Error(c, http.StatusBadRequest, "set dengan id_set tersebut tidak ditemukan", nil) return } seriesSlug := utils.Slugify(setInfo.SeriesName) setCode := setInfo.SetCode uploadDir := filepath.Join("./public/images", seriesSlug, setCode, "images") if err := os.MkdirAll(uploadDir, os.ModePerm); err != nil { response.Error(c, http.StatusInternalServerError, "gagal membuat direktori gambar kartu", err.Error()) return } srcFile, err := fileHeader.Open() if err != nil { response.Error(c, http.StatusInternalServerError, "gagal membuka file gambar", err.Error()) return } defer srcFile.Close() img, _, err := image.Decode(srcFile) if err != nil { response.Error(c, http.StatusBadRequest, "file bukan merupakan gambar yang valid", err.Error()) return } filename := formatCardImageFilename(setCode, req.Number) dstPath := filepath.Join(uploadDir, filename) out, err := os.Create(dstPath) if err != nil { response.Error(c, http.StatusInternalServerError, "gagal membuat file WebP kartu", 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 mengonversi gambar ke WebP", err.Error()) return } req.Image = fmt.Sprintf("/images/%s/%s/images/%s", seriesSlug, setCode, filename) } } else { if err := c.ShouldBindJSON(&req); err != nil { response.Error(c, http.StatusBadRequest, "input tidak valid", validator.TranslateError(err)) return } } createdCard, err := h.service.Create(req) if err != nil { response.Error(c, http.StatusBadRequest, err.Error(), nil) return } response.Success(c, http.StatusCreated, "kartu berhasil dibuat", ToCardMasterResponse(*createdCard)) } func (h *Handler) Update(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 UpdateCardMasterRequest contentType := c.GetHeader("Content-Type") if strings.HasPrefix(contentType, "multipart/form-data") { if jsonData := c.PostForm("data"); jsonData != "" { if err := json.Unmarshal([]byte(jsonData), &req); err != nil { response.Error(c, http.StatusBadRequest, "format data JSON pada multipart tidak valid", nil) return } } else { if err := c.ShouldBind(&req); err != nil { response.Error(c, http.StatusBadRequest, "input tidak valid", validator.TranslateError(err)) return } } fileHeader, err := c.FormFile("image") if err == nil && fileHeader != nil { if fileHeader.Size > 10*1024*1024 { response.Error(c, http.StatusBadRequest, "ukuran file gambar maksimal 10MB", nil) return } existingCard, err := h.service.GetByID(id) if err != nil || existingCard == nil { response.Error(c, http.StatusNotFound, "kartu tidak ditemukan", nil) return } targetIDSet := req.IDSet if targetIDSet == 0 { targetIDSet = existingCard.IDSet } targetNumber := req.Number if targetNumber == "" { targetNumber = existingCard.Number } setInfo, err := h.seriesSetRepo.FindByID(targetIDSet) if err != nil || setInfo == nil { response.Error(c, http.StatusBadRequest, "set dengan id_set tersebut tidak ditemukan", nil) return } seriesSlug := utils.Slugify(setInfo.SeriesName) setCode := setInfo.SetCode uploadDir := filepath.Join("./public/images", seriesSlug, setCode, "images") if err := os.MkdirAll(uploadDir, os.ModePerm); err != nil { response.Error(c, http.StatusInternalServerError, "gagal membuat direktori gambar kartu", err.Error()) return } srcFile, err := fileHeader.Open() if err != nil { response.Error(c, http.StatusInternalServerError, "gagal membuka file gambar", err.Error()) return } defer srcFile.Close() img, _, err := image.Decode(srcFile) if err != nil { response.Error(c, http.StatusBadRequest, "file bukan merupakan gambar yang valid", err.Error()) return } filename := formatCardImageFilename(setCode, targetNumber) dstPath := filepath.Join(uploadDir, filename) out, err := os.Create(dstPath) if err != nil { response.Error(c, http.StatusInternalServerError, "gagal membuat file WebP kartu", 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 mengonversi gambar ke WebP", err.Error()) return } req.Image = fmt.Sprintf("/images/%s/%s/images/%s", seriesSlug, setCode, filename) } } else { if err := c.ShouldBindJSON(&req); err != nil { response.Error(c, http.StatusBadRequest, "input tidak valid", validator.TranslateError(err)) return } } updatedCard, err := h.service.Update(id, req) if err != nil { response.Error(c, http.StatusNotFound, err.Error(), nil) return } response.Success(c, http.StatusOK, "kartu berhasil diupdate", ToCardMasterResponse(*updatedCard)) } 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, "kartu berhasil dihapus", 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 } func formatCardImageFilename(setCode, number string) string { cleanNum := strings.TrimSpace(number) if cleanNum != "" { cleanNum = strings.ReplaceAll(cleanNum, "/", "_") cleanNum = strings.ReplaceAll(cleanNum, "\\", "_") if !strings.HasSuffix(strings.ToLower(cleanNum), ".webp") { cleanNum = cleanNum + ".webp" } return cleanNum } return fmt.Sprintf("%s-1_%d.webp", setCode, time.Now().UnixNano()) }