package handlers import ( "net/http" "strings" "github.com/gin-gonic/gin" "mengyastore-backend/internal/models" ) type productPayload struct { Name string `json:"name"` Price float64 `json:"price"` DiscountPrice float64 `json:"discountPrice"` Tags string `json:"tags"` CoverURL string `json:"coverUrl"` Codes []string `json:"codes"` ScreenshotURLs []string `json:"screenshotUrls"` Description string `json:"description"` Active *bool `json:"active"` RequireLogin bool `json:"requireLogin"` MaxPerAccount int `json:"maxPerAccount"` DeliveryMode string `json:"deliveryMode"` ShowNote bool `json:"showNote"` ShowContact bool `json:"showContact"` } type togglePayload struct { Active bool `json:"active"` } func (h *AdminHandler) GetAdminToken(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"token": h.cfg.AdminToken}) } func (h *AdminHandler) ListAllProducts(c *gin.Context) { if !h.requireAdmin(c) { return } items, err := h.store.ListAll() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"data": items}) } func (h *AdminHandler) CreateProduct(c *gin.Context) { if !h.requireAdmin(c) { return } var payload productPayload if err := c.ShouldBindJSON(&payload); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid payload"}) return } screenshotURLs, valid := normalizeScreenshotURLs(payload.ScreenshotURLs) if !valid { c.JSON(http.StatusBadRequest, gin.H{"error": "screenshot urls must be 5 or fewer"}) return } active := true if payload.Active != nil { active = *payload.Active } product := models.Product{ Name: payload.Name, Price: payload.Price, DiscountPrice: payload.DiscountPrice, Tags: normalizeTags(payload.Tags), CoverURL: strings.TrimSpace(payload.CoverURL), Codes: payload.Codes, ScreenshotURLs: screenshotURLs, Description: payload.Description, Active: active, RequireLogin: payload.RequireLogin, MaxPerAccount: payload.MaxPerAccount, DeliveryMode: payload.DeliveryMode, ShowNote: payload.ShowNote, ShowContact: payload.ShowContact, } created, err := h.store.Create(product) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"data": created}) } func (h *AdminHandler) UpdateProduct(c *gin.Context) { if !h.requireAdmin(c) { return } id := c.Param("id") var payload productPayload if err := c.ShouldBindJSON(&payload); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid payload"}) return } screenshotURLs, valid := normalizeScreenshotURLs(payload.ScreenshotURLs) if !valid { c.JSON(http.StatusBadRequest, gin.H{"error": "screenshot urls must be 5 or fewer"}) return } active := false if payload.Active != nil { active = *payload.Active } patch := models.Product{ Name: payload.Name, Price: payload.Price, DiscountPrice: payload.DiscountPrice, Tags: normalizeTags(payload.Tags), CoverURL: strings.TrimSpace(payload.CoverURL), Codes: payload.Codes, ScreenshotURLs: screenshotURLs, Description: payload.Description, Active: active, RequireLogin: payload.RequireLogin, MaxPerAccount: payload.MaxPerAccount, DeliveryMode: payload.DeliveryMode, ShowNote: payload.ShowNote, ShowContact: payload.ShowContact, } updated, err := h.store.Update(id, patch) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"data": updated}) } func (h *AdminHandler) ToggleProduct(c *gin.Context) { if !h.requireAdmin(c) { return } id := c.Param("id") var payload togglePayload if err := c.ShouldBindJSON(&payload); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid payload"}) return } updated, err := h.store.Toggle(id, payload.Active) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"data": updated}) } func (h *AdminHandler) DeleteProduct(c *gin.Context) { if !h.requireAdmin(c) { return } id := c.Param("id") if err := h.store.Delete(id); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"status": "deleted"}) } func normalizeScreenshotURLs(urls []string) ([]string, bool) { cleaned := make([]string, 0, len(urls)) for _, url := range urls { trimmed := strings.TrimSpace(url) if trimmed == "" { continue } cleaned = append(cleaned, trimmed) if len(cleaned) > 5 { return nil, false } } return cleaned, true } func normalizeTags(tagsCSV string) []string { if tagsCSV == "" { return []string{} } parts := strings.Split(tagsCSV, ",") clean := make([]string, 0, len(parts)) seen := map[string]bool{} for _, p := range parts { t := strings.TrimSpace(p) if t == "" { continue } key := strings.ToLower(t) if seen[key] { continue } seen[key] = true clean = append(clean, t) if len(clean) >= 20 { break } } return clean }