package media import ( "errors" "net/http" "github.com/gin-gonic/gin" "github.com/google/uuid" ) type Handler struct { service *Service } func NewHandler(service *Service) *Handler { return &Handler{service: service} } type mediaResponse struct { ID uuid.UUID `json:"id"` Filename string `json:"filename"` URL string `json:"url"` MimeType string `json:"mime_type"` SizeBytes int64 `json:"size_bytes"` AltText string `json:"alt_text"` ProductID *uuid.UUID `json:"product_id,omitempty"` } func toResponse(m *Media) mediaResponse { return mediaResponse{ ID: m.ID, Filename: m.Filename, URL: m.URL, MimeType: m.MimeType, SizeBytes: m.SizeBytes, AltText: m.AltText, ProductID: m.ProductID, } } // parseOptionalProductID reads the product_id query/form value, if any. // Absent means "media not tied to a product" (e.g. contact link icons), // distinct from an explicit product scope. func parseOptionalProductID(raw string) (*uuid.UUID, error) { if raw == "" { return nil, nil } id, err := uuid.Parse(raw) if err != nil { return nil, err } return &id, nil } func (h *Handler) Get(c *gin.Context) { id, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"}) return } m, err := h.service.Get(c.Request.Context(), id) if err != nil { if errors.Is(err, ErrNotFound) { c.JSON(http.StatusNotFound, gin.H{"error": "media not found"}) return } c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to load media"}) return } c.JSON(http.StatusOK, toResponse(m)) } func (h *Handler) List(c *gin.Context) { productID, err := parseOptionalProductID(c.Query("product_id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid product_id"}) return } list, err := h.service.List(c.Request.Context(), productID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list media"}) return } resp := make([]mediaResponse, 0, len(list)) for _, m := range list { resp = append(resp, toResponse(m)) } c.JSON(http.StatusOK, gin.H{"media": resp}) } func (h *Handler) Upload(c *gin.Context) { fileHeader, err := c.FormFile("file") if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "missing file field"}) return } file, err := fileHeader.Open() if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "failed to open uploaded file"}) return } defer file.Close() contentType := fileHeader.Header.Get("Content-Type") altText := c.PostForm("alt_text") productID, err := parseOptionalProductID(c.PostForm("product_id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid product_id"}) return } m, err := h.service.Upload(c.Request.Context(), fileHeader.Filename, file, fileHeader.Size, contentType, altText, productID) if err != nil { switch { case errors.Is(err, ErrFileTooLarge): c.JSON(http.StatusRequestEntityTooLarge, gin.H{"error": "file exceeds the maximum allowed size"}) case errors.Is(err, ErrUnsupportedType): c.JSON(http.StatusUnsupportedMediaType, gin.H{"error": "unsupported file type"}) case errors.Is(err, ErrProductNotFound): c.JSON(http.StatusBadRequest, gin.H{"error": "product not found"}) default: c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to upload file"}) } return } c.JSON(http.StatusCreated, toResponse(m)) } type updateRequest struct { AltText string `json:"alt_text"` } func (h *Handler) Update(c *gin.Context) { id, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"}) return } var req updateRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "validation error", "details": err.Error()}) return } m, err := h.service.UpdateAltText(c.Request.Context(), id, req.AltText) if err != nil { if errors.Is(err, ErrNotFound) { c.JSON(http.StatusNotFound, gin.H{"error": "media not found"}) return } c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update media"}) return } c.JSON(http.StatusOK, toResponse(m)) } func (h *Handler) Delete(c *gin.Context) { id, err := uuid.Parse(c.Param("id")) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"}) return } if err := h.service.Delete(c.Request.Context(), id); err != nil { if errors.Is(err, ErrNotFound) { c.JSON(http.StatusNotFound, gin.H{"error": "media not found"}) return } c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to delete media"}) return } c.Status(http.StatusNoContent) }