package handlers import ( "encoding/json" "net/http" "strconv" "github.com/go-chi/chi/v5" "qctextbuilder/internal/buildsvc" "qctextbuilder/internal/onboarding" "qctextbuilder/internal/templatesvc" ) type API struct { templateSvc *templatesvc.Service onboardSvc *onboarding.Service buildSvc buildsvc.Service } func NewAPI(templateSvc *templatesvc.Service, onboardSvc *onboarding.Service, buildSvc buildsvc.Service) *API { return &API{ templateSvc: templateSvc, onboardSvc: onboardSvc, buildSvc: buildSvc, } } func (a *API) Health(w http.ResponseWriter, _ *http.Request) { writeJSON(w, http.StatusOK, map[string]any{"status": "ok"}) } func (a *API) SyncTemplates(w http.ResponseWriter, r *http.Request) { templates, err := a.templateSvc.SyncAITemplates(r.Context()) if err != nil { writeJSON(w, http.StatusBadGateway, map[string]any{"error": err.Error()}) return } writeJSON(w, http.StatusOK, map[string]any{"count": len(templates), "templates": templates}) } func (a *API) ListTemplates(w http.ResponseWriter, r *http.Request) { templates, err := a.templateSvc.ListTemplates(r.Context()) if err != nil { writeJSON(w, http.StatusBadRequest, map[string]any{"error": err.Error()}) return } writeJSON(w, http.StatusOK, map[string]any{"count": len(templates), "templates": templates}) } func (a *API) GetTemplateDetail(w http.ResponseWriter, r *http.Request) { rawID := chi.URLParam(r, "id") templateID, err := strconv.ParseInt(rawID, 10, 64) if err != nil { writeJSON(w, http.StatusBadRequest, map[string]any{"error": "invalid template id"}) return } detail, err := a.templateSvc.GetTemplateDetail(r.Context(), templateID) if err != nil { writeJSON(w, http.StatusNotFound, map[string]any{"error": err.Error()}) return } writeJSON(w, http.StatusOK, detail) } func (a *API) OnboardTemplate(w http.ResponseWriter, r *http.Request) { rawID := chi.URLParam(r, "id") templateID, err := strconv.ParseInt(rawID, 10, 64) if err != nil { writeJSON(w, http.StatusBadRequest, map[string]any{"error": "invalid template id"}) return } manifest, fields, err := a.onboardSvc.OnboardTemplate(r.Context(), templateID) if err != nil { writeJSON(w, http.StatusBadRequest, map[string]any{"error": err.Error()}) return } writeJSON(w, http.StatusOK, map[string]any{ "manifestId": manifest.ID, "fieldCount": len(fields), "status": "reviewed", }) } type updateTemplateFieldsRequest struct { ManifestID string `json:"manifestId"` Fields []updateTemplateFieldItem `json:"fields"` } type updateTemplateFieldItem struct { Path string `json:"path"` IsEnabled *bool `json:"isEnabled,omitempty"` IsRequiredByUs *bool `json:"isRequiredByUs,omitempty"` DisplayLabel *string `json:"displayLabel,omitempty"` DisplayOrder *int `json:"displayOrder,omitempty"` Notes *string `json:"notes,omitempty"` } func (a *API) UpdateTemplateFields(w http.ResponseWriter, r *http.Request) { rawID := chi.URLParam(r, "id") templateID, err := strconv.ParseInt(rawID, 10, 64) if err != nil { writeJSON(w, http.StatusBadRequest, map[string]any{"error": "invalid template id"}) return } var req updateTemplateFieldsRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeJSON(w, http.StatusBadRequest, map[string]any{"error": "invalid JSON body"}) return } if len(req.Fields) == 0 { writeJSON(w, http.StatusBadRequest, map[string]any{"error": "fields is required"}) return } patches := make([]onboarding.FieldPatch, 0, len(req.Fields)) for _, f := range req.Fields { patches = append(patches, onboarding.FieldPatch{ Path: f.Path, IsEnabled: f.IsEnabled, IsRequiredByUs: f.IsRequiredByUs, DisplayLabel: f.DisplayLabel, DisplayOrder: f.DisplayOrder, Notes: f.Notes, }) } manifest, fields, err := a.onboardSvc.UpdateTemplateFields(r.Context(), templateID, req.ManifestID, patches) if err != nil { writeJSON(w, http.StatusBadRequest, map[string]any{"error": err.Error()}) return } writeJSON(w, http.StatusOK, map[string]any{ "templateId": templateID, "manifestId": manifest.ID, "fieldCount": len(fields), "fields": fields, }) } func (a *API) StartBuild(w http.ResponseWriter, r *http.Request) { var req buildsvc.StartBuildRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeJSON(w, http.StatusBadRequest, map[string]any{"error": "invalid JSON body"}) return } result, err := a.buildSvc.StartBuild(r.Context(), req) if err != nil { writeJSON(w, http.StatusBadRequest, map[string]any{"error": err.Error()}) return } writeJSON(w, http.StatusAccepted, result) } func (a *API) GetBuild(w http.ResponseWriter, r *http.Request) { buildID := chi.URLParam(r, "id") build, err := a.buildSvc.GetBuild(r.Context(), buildID) if err != nil { writeJSON(w, http.StatusNotFound, map[string]any{"error": err.Error()}) return } writeJSON(w, http.StatusOK, build) } func (a *API) PollBuildOnce(w http.ResponseWriter, r *http.Request) { buildID := chi.URLParam(r, "id") if err := a.buildSvc.PollOnce(r.Context(), buildID); err != nil { writeJSON(w, http.StatusBadRequest, map[string]any{"error": err.Error()}) return } build, err := a.buildSvc.GetBuild(r.Context(), buildID) if err != nil { writeJSON(w, http.StatusNotFound, map[string]any{"error": err.Error()}) return } writeJSON(w, http.StatusOK, build) } func (a *API) FetchBuildEditorURL(w http.ResponseWriter, r *http.Request) { buildID := chi.URLParam(r, "id") if err := a.buildSvc.FetchEditorURL(r.Context(), buildID); err != nil { writeJSON(w, http.StatusBadRequest, map[string]any{"error": err.Error()}) return } build, err := a.buildSvc.GetBuild(r.Context(), buildID) if err != nil { writeJSON(w, http.StatusNotFound, map[string]any{"error": err.Error()}) return } writeJSON(w, http.StatusOK, build) } func writeJSON(w http.ResponseWriter, status int, v any) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) _ = json.NewEncoder(w).Encode(v) }