package handlers import ( "encoding/json" "net/http" "strconv" "strings" "github.com/go-chi/chi/v5" "qctextbuilder/internal/buildsvc" "qctextbuilder/internal/draftsvc" "qctextbuilder/internal/onboarding" "qctextbuilder/internal/templatesvc" ) type API struct { templateSvc *templatesvc.Service onboardSvc *onboarding.Service draftSvc *draftsvc.Service buildSvc buildsvc.Service } func NewAPI(templateSvc *templatesvc.Service, onboardSvc *onboarding.Service, draftSvc *draftsvc.Service, buildSvc buildsvc.Service) *API { return &API{ templateSvc: templateSvc, onboardSvc: onboardSvc, draftSvc: draftSvc, 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"` WebsiteSection *string `json:"websiteSection,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, WebsiteSection: f.WebsiteSection, 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) } type upsertDraftRequest struct { TemplateID int64 `json:"templateId"` ManifestID string `json:"manifestId"` Source string `json:"source"` RequestName string `json:"requestName"` GlobalData map[string]any `json:"globalData"` FieldValues map[string]string `json:"fieldValues"` Status string `json:"status"` Notes string `json:"notes"` } func (a *API) IntakeDraft(w http.ResponseWriter, r *http.Request) { var req upsertDraftRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeJSON(w, http.StatusBadRequest, map[string]any{"error": "invalid JSON body"}) return } draft, err := a.draftSvc.SaveDraft(r.Context(), draftsvc.UpsertDraftRequest{ TemplateID: req.TemplateID, ManifestID: req.ManifestID, Source: defaultStr(req.Source, "intake-api"), RequestName: req.RequestName, GlobalData: req.GlobalData, FieldValues: req.FieldValues, Status: defaultStr(req.Status, "draft"), Notes: req.Notes, }) if err != nil { writeJSON(w, http.StatusBadRequest, map[string]any{"error": err.Error()}) return } writeJSON(w, http.StatusCreated, draft) } func (a *API) ListDrafts(w http.ResponseWriter, r *http.Request) { limit, _ := strconv.Atoi(strings.TrimSpace(r.URL.Query().Get("limit"))) drafts, err := a.draftSvc.ListDrafts(r.Context(), limit) if err != nil { writeJSON(w, http.StatusBadRequest, map[string]any{"error": err.Error()}) return } writeJSON(w, http.StatusOK, map[string]any{"count": len(drafts), "drafts": drafts}) } func (a *API) GetDraft(w http.ResponseWriter, r *http.Request) { draftID := strings.TrimSpace(chi.URLParam(r, "id")) draft, err := a.draftSvc.GetDraft(r.Context(), draftID) if err != nil { writeJSON(w, http.StatusNotFound, map[string]any{"error": err.Error()}) return } writeJSON(w, http.StatusOK, draft) } func (a *API) UpdateDraft(w http.ResponseWriter, r *http.Request) { draftID := strings.TrimSpace(chi.URLParam(r, "id")) var req upsertDraftRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeJSON(w, http.StatusBadRequest, map[string]any{"error": "invalid JSON body"}) return } draft, err := a.draftSvc.SaveDraft(r.Context(), draftsvc.UpsertDraftRequest{ DraftID: draftID, TemplateID: req.TemplateID, ManifestID: req.ManifestID, Source: req.Source, RequestName: req.RequestName, GlobalData: req.GlobalData, FieldValues: req.FieldValues, Status: req.Status, Notes: req.Notes, }) if err != nil { writeJSON(w, http.StatusBadRequest, map[string]any{"error": err.Error()}) return } writeJSON(w, http.StatusOK, draft) } 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) } func defaultStr(v, fallback string) string { if strings.TrimSpace(v) == "" { return fallback } return strings.TrimSpace(v) }