Files
template-vitrine/backend/internal/modules/media/handler.go
T
Xor290 919c807004
ci-api / test (push) Failing after 8m7s
ci-web / test (push) Failing after 5m5s
chore: build
2026-09-20 12:18:33 +02:00

173 lines
4.6 KiB
Go

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)
}