package httpapi import ( "errors" "net/http" "strings" "github.com/descrybe/descrybe-v2/apps/api/internal/catalog" "github.com/go-chi/chi/v5" "github.com/google/uuid" ) func (s *Server) handleListFieldGroups(w http.ResponseWriter, r *http.Request) { cid, _ := CompanyIDFromContext(r.Context()) items, err := s.Catalog.ListFieldGroups(r.Context(), cid) if err != nil { Error(w, http.StatusInternalServerError, "list failed") return } limit, offset := ParseLimitOffsetMax(r, maxTreePageLimit) page, total := pageSlice(items, limit, offset) JSON(w, http.StatusOK, map[string]any{"groups": page, "total": total, "limit": limit, "offset": offset}) } func (s *Server) handleCreateFieldGroup(w http.ResponseWriter, r *http.Request) { cid, _ := CompanyIDFromContext(r.Context()) var body map[string]any if err := DecodeJSON(r, &body); err != nil { Error(w, http.StatusBadRequest, "invalid json") return } name, _ := body["name"].(string) var desc *string if v, ok := body["description"]; ok { if v == nil { empty := "" desc = &empty } else if str, ok := v.(string); ok { desc = &str } } order := 0 if v, ok := body["order"].(float64); ok { order = int(v) } item, err := s.Catalog.CreateFieldGroup(r.Context(), cid, name, desc, order) if err != nil { ClientOrLog(w, http.StatusBadRequest, "could not create field group", err, catalog.ClientError) return } JSON(w, http.StatusCreated, item) } func (s *Server) handleUpdateFieldGroup(w http.ResponseWriter, r *http.Request) { cid, _ := CompanyIDFromContext(r.Context()) id, err := uuid.Parse(chi.URLParam(r, "id")) if err != nil { Error(w, http.StatusBadRequest, "invalid id") return } var body map[string]any if err := DecodeJSON(r, &body); err != nil { Error(w, http.StatusBadRequest, "invalid json") return } item, err := s.Catalog.UpdateFieldGroup(r.Context(), cid, id, body) if errors.Is(err, catalog.ErrNotFound) { Error(w, http.StatusNotFound, "not found") return } if errors.Is(err, catalog.ErrSystemImmutable) { Error(w, http.StatusForbidden, err.Error()) return } if err != nil { ClientOrLog(w, http.StatusBadRequest, "could not update field group", err, catalog.ClientError) return } JSON(w, http.StatusOK, item) } func (s *Server) handleDeleteFieldGroup(w http.ResponseWriter, r *http.Request) { cid, _ := CompanyIDFromContext(r.Context()) id, err := uuid.Parse(chi.URLParam(r, "id")) if err != nil { Error(w, http.StatusBadRequest, "invalid id") return } err = s.Catalog.DeleteFieldGroup(r.Context(), cid, id) if errors.Is(err, catalog.ErrNotFound) { Error(w, http.StatusNotFound, "not found") return } if errors.Is(err, catalog.ErrSystemImmutable) { Error(w, http.StatusForbidden, err.Error()) return } if err != nil { Error(w, http.StatusInternalServerError, "delete failed") return } JSON(w, http.StatusOK, map[string]string{"status": "ok"}) } func (s *Server) handleListStandardFields(w http.ResponseWriter, r *http.Request) { cid, _ := CompanyIDFromContext(r.Context()) enabledOnly := false switch strings.ToLower(strings.TrimSpace(r.URL.Query().Get("enabled"))) { case "1", "true", "yes": enabledOnly = true } items, err := s.Catalog.ListStandardFields(r.Context(), cid, enabledOnly) if err != nil { Error(w, http.StatusInternalServerError, "list failed") return } limit, offset := ParseLimitOffsetMax(r, maxTreePageLimit) page, total := pageSlice(items, limit, offset) JSON(w, http.StatusOK, map[string]any{"fields": page, "total": total, "limit": limit, "offset": offset}) } func (s *Server) handleCreateStandardField(w http.ResponseWriter, r *http.Request) { cid, _ := CompanyIDFromContext(r.Context()) var body map[string]any if err := DecodeJSON(r, &body); err != nil { Error(w, http.StatusBadRequest, "invalid json") return } item, err := s.Catalog.CreateStandardField(r.Context(), cid, body) if err != nil { ClientOrLog(w, http.StatusBadRequest, "could not create standard field", err, catalog.ClientError) return } JSON(w, http.StatusCreated, item) } func (s *Server) handleUpdateStandardField(w http.ResponseWriter, r *http.Request) { cid, _ := CompanyIDFromContext(r.Context()) id, err := uuid.Parse(chi.URLParam(r, "id")) if err != nil { Error(w, http.StatusBadRequest, "invalid id") return } var body map[string]any if err := DecodeJSON(r, &body); err != nil { Error(w, http.StatusBadRequest, "invalid json") return } item, err := s.Catalog.UpdateStandardField(r.Context(), cid, id, body) if errors.Is(err, catalog.ErrNotFound) { Error(w, http.StatusNotFound, "not found") return } if errors.Is(err, catalog.ErrSystemImmutable) { Error(w, http.StatusForbidden, err.Error()) return } if err != nil { ClientOrLog(w, http.StatusBadRequest, "could not update standard field", err, catalog.ClientError) return } JSON(w, http.StatusOK, item) } func (s *Server) handleDeleteStandardField(w http.ResponseWriter, r *http.Request) { cid, _ := CompanyIDFromContext(r.Context()) id, err := uuid.Parse(chi.URLParam(r, "id")) if err != nil { Error(w, http.StatusBadRequest, "invalid id") return } err = s.Catalog.DeleteStandardField(r.Context(), cid, id) if errors.Is(err, catalog.ErrNotFound) { Error(w, http.StatusNotFound, "not found") return } if errors.Is(err, catalog.ErrSystemImmutable) { Error(w, http.StatusForbidden, err.Error()) return } if err != nil { Error(w, http.StatusInternalServerError, "delete failed") return } JSON(w, http.StatusOK, map[string]string{"status": "ok"}) } func (s *Server) handleBulkStandardFieldsEnabled(w http.ResponseWriter, r *http.Request) { cid, _ := CompanyIDFromContext(r.Context()) var body struct { IDs []string `json:"ids"` Enabled *bool `json:"enabled"` } if err := DecodeJSON(r, &body); err != nil { Error(w, http.StatusBadRequest, "invalid json") return } if body.Enabled == nil { Error(w, http.StatusBadRequest, "enabled required") return } ids := make([]uuid.UUID, 0, len(body.IDs)) for _, raw := range body.IDs { id, err := uuid.Parse(strings.TrimSpace(raw)) if err != nil { Error(w, http.StatusBadRequest, "invalid id") return } ids = append(ids, id) } n, err := s.Catalog.BulkSetStandardFieldsEnabled(r.Context(), cid, ids, *body.Enabled) if err != nil { ClientOrLog(w, http.StatusBadRequest, "could not update standard fields", err, catalog.ClientError) return } JSON(w, http.StatusOK, map[string]any{"updated": n, "enabled": *body.Enabled}) } func (s *Server) handleEnableRecommendedStandardFields(w http.ResponseWriter, r *http.Request) { cid, _ := CompanyIDFromContext(r.Context()) items, err := s.Catalog.EnableRecommendedEcommerce(r.Context(), cid) if err != nil { LogAndError(w, http.StatusInternalServerError, "enable recommended fields failed", err) return } JSON(w, http.StatusOK, map[string]any{"fields": items, "status": "ok"}) } func (s *Server) handleListStructuredDescriptions(w http.ResponseWriter, r *http.Request) { cid, _ := CompanyIDFromContext(r.Context()) items, err := s.Catalog.ListStructuredDescriptions(r.Context(), cid) if err != nil { Error(w, http.StatusInternalServerError, "list failed") return } limit, offset := ParseLimitOffsetMax(r, maxTreePageLimit) page, total := pageSlice(items, limit, offset) JSON(w, http.StatusOK, map[string]any{"fields": page, "total": total, "limit": limit, "offset": offset}) } func (s *Server) handleCreateStructuredDescription(w http.ResponseWriter, r *http.Request) { cid, _ := CompanyIDFromContext(r.Context()) var body map[string]any if err := DecodeJSON(r, &body); err != nil { Error(w, http.StatusBadRequest, "invalid json") return } fieldKey := "" if v, ok := body["field_key"].(string); ok { fieldKey = v } else if v, ok := body["fieldKey"].(string); ok { fieldKey = v } typ := "text" if v, ok := body["type"].(string); ok && v != "" { typ = v } item, err := s.Catalog.CreateStructuredDescription(r.Context(), cid, fieldKey, typ) if err != nil { ClientOrLog(w, http.StatusBadRequest, "could not create structured description", err, catalog.ClientError) return } JSON(w, http.StatusCreated, item) } func (s *Server) handleDeleteStructuredDescription(w http.ResponseWriter, r *http.Request) { cid, _ := CompanyIDFromContext(r.Context()) id, err := uuid.Parse(chi.URLParam(r, "id")) if err != nil { Error(w, http.StatusBadRequest, "invalid id") return } err = s.Catalog.DeleteStructuredDescription(r.Context(), cid, id) if errors.Is(err, catalog.ErrNotFound) { Error(w, http.StatusNotFound, "not found") return } if err != nil { Error(w, http.StatusInternalServerError, "delete failed") return } JSON(w, http.StatusOK, map[string]string{"status": "ok"}) }