package product import ( "context" "encoding/json" "errors" "log/slog" "net/http" "github.com/food-ai/backend/internal/infra/middleware" "github.com/go-chi/chi/v5" ) // ProductRepository is the data layer interface used by Handler. type ProductRepository interface { List(ctx context.Context, userID string) ([]*Product, error) Create(ctx context.Context, userID string, req CreateRequest) (*Product, error) BatchCreate(ctx context.Context, userID string, items []CreateRequest) ([]*Product, error) Update(ctx context.Context, id, userID string, req UpdateRequest) (*Product, error) Delete(ctx context.Context, id, userID string) error } // Handler handles /products HTTP requests. type Handler struct { repo ProductRepository } // NewHandler creates a new Handler. func NewHandler(repo ProductRepository) *Handler { return &Handler{repo: repo} } // List handles GET /products. func (h *Handler) List(w http.ResponseWriter, r *http.Request) { userID := middleware.UserIDFromCtx(r.Context()) products, err := h.repo.List(r.Context(), userID) if err != nil { slog.Error("list products", "user_id", userID, "err", err) writeErrorJSON(w, http.StatusInternalServerError, "failed to list products") return } if products == nil { products = []*Product{} } writeJSON(w, http.StatusOK, products) } // Create handles POST /products. func (h *Handler) Create(w http.ResponseWriter, r *http.Request) { userID := middleware.UserIDFromCtx(r.Context()) var req CreateRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeErrorJSON(w, http.StatusBadRequest, "invalid request body") return } if req.Name == "" { writeErrorJSON(w, http.StatusBadRequest, "name is required") return } p, err := h.repo.Create(r.Context(), userID, req) if err != nil { slog.Error("create product", "user_id", userID, "err", err) writeErrorJSON(w, http.StatusInternalServerError, "failed to create product") return } writeJSON(w, http.StatusCreated, p) } // BatchCreate handles POST /products/batch. func (h *Handler) BatchCreate(w http.ResponseWriter, r *http.Request) { userID := middleware.UserIDFromCtx(r.Context()) var items []CreateRequest if err := json.NewDecoder(r.Body).Decode(&items); err != nil { writeErrorJSON(w, http.StatusBadRequest, "invalid request body") return } if len(items) == 0 { writeJSON(w, http.StatusCreated, []*Product{}) return } products, err := h.repo.BatchCreate(r.Context(), userID, items) if err != nil { slog.Error("batch create products", "user_id", userID, "err", err) writeErrorJSON(w, http.StatusInternalServerError, "failed to create products") return } writeJSON(w, http.StatusCreated, products) } // Update handles PUT /products/{id}. func (h *Handler) Update(w http.ResponseWriter, r *http.Request) { userID := middleware.UserIDFromCtx(r.Context()) id := chi.URLParam(r, "id") var req UpdateRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeErrorJSON(w, http.StatusBadRequest, "invalid request body") return } p, err := h.repo.Update(r.Context(), id, userID, req) if errors.Is(err, ErrNotFound) { writeErrorJSON(w, http.StatusNotFound, "product not found") return } if err != nil { slog.Error("update product", "id", id, "err", err) writeErrorJSON(w, http.StatusInternalServerError, "failed to update product") return } writeJSON(w, http.StatusOK, p) } // Delete handles DELETE /products/{id}. func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) { userID := middleware.UserIDFromCtx(r.Context()) id := chi.URLParam(r, "id") if err := h.repo.Delete(r.Context(), id, userID); err != nil { if errors.Is(err, ErrNotFound) { writeErrorJSON(w, http.StatusNotFound, "product not found") return } slog.Error("delete product", "id", id, "err", err) writeErrorJSON(w, http.StatusInternalServerError, "failed to delete product") return } w.WriteHeader(http.StatusNoContent) } type errorResponse struct { Error string `json:"error"` } func writeErrorJSON(w http.ResponseWriter, status int, msg string) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) _ = json.NewEncoder(w).Encode(errorResponse{Error: msg}) } func writeJSON(w http.ResponseWriter, status int, v any) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) _ = json.NewEncoder(w).Encode(v) }