No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.

297 líneas
9.0KB

  1. package handlers
  2. import (
  3. "encoding/json"
  4. "net/http"
  5. "strconv"
  6. "strings"
  7. "github.com/go-chi/chi/v5"
  8. "qctextbuilder/internal/buildsvc"
  9. "qctextbuilder/internal/draftsvc"
  10. "qctextbuilder/internal/onboarding"
  11. "qctextbuilder/internal/templatesvc"
  12. )
  13. type API struct {
  14. templateSvc *templatesvc.Service
  15. onboardSvc *onboarding.Service
  16. draftSvc *draftsvc.Service
  17. buildSvc buildsvc.Service
  18. }
  19. func NewAPI(templateSvc *templatesvc.Service, onboardSvc *onboarding.Service, draftSvc *draftsvc.Service, buildSvc buildsvc.Service) *API {
  20. return &API{
  21. templateSvc: templateSvc,
  22. onboardSvc: onboardSvc,
  23. draftSvc: draftSvc,
  24. buildSvc: buildSvc,
  25. }
  26. }
  27. func (a *API) Health(w http.ResponseWriter, _ *http.Request) {
  28. writeJSON(w, http.StatusOK, map[string]any{"status": "ok"})
  29. }
  30. func (a *API) SyncTemplates(w http.ResponseWriter, r *http.Request) {
  31. templates, err := a.templateSvc.SyncAITemplates(r.Context())
  32. if err != nil {
  33. writeJSON(w, http.StatusBadGateway, map[string]any{"error": err.Error()})
  34. return
  35. }
  36. writeJSON(w, http.StatusOK, map[string]any{"count": len(templates), "templates": templates})
  37. }
  38. func (a *API) ListTemplates(w http.ResponseWriter, r *http.Request) {
  39. templates, err := a.templateSvc.ListTemplates(r.Context())
  40. if err != nil {
  41. writeJSON(w, http.StatusBadRequest, map[string]any{"error": err.Error()})
  42. return
  43. }
  44. writeJSON(w, http.StatusOK, map[string]any{"count": len(templates), "templates": templates})
  45. }
  46. func (a *API) GetTemplateDetail(w http.ResponseWriter, r *http.Request) {
  47. rawID := chi.URLParam(r, "id")
  48. templateID, err := strconv.ParseInt(rawID, 10, 64)
  49. if err != nil {
  50. writeJSON(w, http.StatusBadRequest, map[string]any{"error": "invalid template id"})
  51. return
  52. }
  53. detail, err := a.templateSvc.GetTemplateDetail(r.Context(), templateID)
  54. if err != nil {
  55. writeJSON(w, http.StatusNotFound, map[string]any{"error": err.Error()})
  56. return
  57. }
  58. writeJSON(w, http.StatusOK, detail)
  59. }
  60. func (a *API) OnboardTemplate(w http.ResponseWriter, r *http.Request) {
  61. rawID := chi.URLParam(r, "id")
  62. templateID, err := strconv.ParseInt(rawID, 10, 64)
  63. if err != nil {
  64. writeJSON(w, http.StatusBadRequest, map[string]any{"error": "invalid template id"})
  65. return
  66. }
  67. manifest, fields, err := a.onboardSvc.OnboardTemplate(r.Context(), templateID)
  68. if err != nil {
  69. writeJSON(w, http.StatusBadRequest, map[string]any{"error": err.Error()})
  70. return
  71. }
  72. writeJSON(w, http.StatusOK, map[string]any{
  73. "manifestId": manifest.ID,
  74. "fieldCount": len(fields),
  75. "status": "reviewed",
  76. })
  77. }
  78. type updateTemplateFieldsRequest struct {
  79. ManifestID string `json:"manifestId"`
  80. Fields []updateTemplateFieldItem `json:"fields"`
  81. }
  82. type updateTemplateFieldItem struct {
  83. Path string `json:"path"`
  84. IsEnabled *bool `json:"isEnabled,omitempty"`
  85. IsRequiredByUs *bool `json:"isRequiredByUs,omitempty"`
  86. DisplayLabel *string `json:"displayLabel,omitempty"`
  87. DisplayOrder *int `json:"displayOrder,omitempty"`
  88. WebsiteSection *string `json:"websiteSection,omitempty"`
  89. Notes *string `json:"notes,omitempty"`
  90. }
  91. func (a *API) UpdateTemplateFields(w http.ResponseWriter, r *http.Request) {
  92. rawID := chi.URLParam(r, "id")
  93. templateID, err := strconv.ParseInt(rawID, 10, 64)
  94. if err != nil {
  95. writeJSON(w, http.StatusBadRequest, map[string]any{"error": "invalid template id"})
  96. return
  97. }
  98. var req updateTemplateFieldsRequest
  99. if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
  100. writeJSON(w, http.StatusBadRequest, map[string]any{"error": "invalid JSON body"})
  101. return
  102. }
  103. if len(req.Fields) == 0 {
  104. writeJSON(w, http.StatusBadRequest, map[string]any{"error": "fields is required"})
  105. return
  106. }
  107. patches := make([]onboarding.FieldPatch, 0, len(req.Fields))
  108. for _, f := range req.Fields {
  109. patches = append(patches, onboarding.FieldPatch{
  110. Path: f.Path,
  111. IsEnabled: f.IsEnabled,
  112. IsRequiredByUs: f.IsRequiredByUs,
  113. DisplayLabel: f.DisplayLabel,
  114. DisplayOrder: f.DisplayOrder,
  115. WebsiteSection: f.WebsiteSection,
  116. Notes: f.Notes,
  117. })
  118. }
  119. manifest, fields, err := a.onboardSvc.UpdateTemplateFields(r.Context(), templateID, req.ManifestID, patches)
  120. if err != nil {
  121. writeJSON(w, http.StatusBadRequest, map[string]any{"error": err.Error()})
  122. return
  123. }
  124. writeJSON(w, http.StatusOK, map[string]any{
  125. "templateId": templateID,
  126. "manifestId": manifest.ID,
  127. "fieldCount": len(fields),
  128. "fields": fields,
  129. })
  130. }
  131. func (a *API) StartBuild(w http.ResponseWriter, r *http.Request) {
  132. var req buildsvc.StartBuildRequest
  133. if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
  134. writeJSON(w, http.StatusBadRequest, map[string]any{"error": "invalid JSON body"})
  135. return
  136. }
  137. result, err := a.buildSvc.StartBuild(r.Context(), req)
  138. if err != nil {
  139. writeJSON(w, http.StatusBadRequest, map[string]any{"error": err.Error()})
  140. return
  141. }
  142. writeJSON(w, http.StatusAccepted, result)
  143. }
  144. type upsertDraftRequest struct {
  145. TemplateID int64 `json:"templateId"`
  146. ManifestID string `json:"manifestId"`
  147. Source string `json:"source"`
  148. RequestName string `json:"requestName"`
  149. GlobalData map[string]any `json:"globalData"`
  150. FieldValues map[string]string `json:"fieldValues"`
  151. Status string `json:"status"`
  152. Notes string `json:"notes"`
  153. }
  154. func (a *API) IntakeDraft(w http.ResponseWriter, r *http.Request) {
  155. var req upsertDraftRequest
  156. if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
  157. writeJSON(w, http.StatusBadRequest, map[string]any{"error": "invalid JSON body"})
  158. return
  159. }
  160. draft, err := a.draftSvc.SaveDraft(r.Context(), draftsvc.UpsertDraftRequest{
  161. TemplateID: req.TemplateID,
  162. ManifestID: req.ManifestID,
  163. Source: defaultStr(req.Source, "intake-api"),
  164. RequestName: req.RequestName,
  165. GlobalData: req.GlobalData,
  166. FieldValues: req.FieldValues,
  167. Status: defaultStr(req.Status, "draft"),
  168. Notes: req.Notes,
  169. })
  170. if err != nil {
  171. writeJSON(w, http.StatusBadRequest, map[string]any{"error": err.Error()})
  172. return
  173. }
  174. writeJSON(w, http.StatusCreated, draft)
  175. }
  176. func (a *API) ListDrafts(w http.ResponseWriter, r *http.Request) {
  177. limit, _ := strconv.Atoi(strings.TrimSpace(r.URL.Query().Get("limit")))
  178. drafts, err := a.draftSvc.ListDrafts(r.Context(), limit)
  179. if err != nil {
  180. writeJSON(w, http.StatusBadRequest, map[string]any{"error": err.Error()})
  181. return
  182. }
  183. writeJSON(w, http.StatusOK, map[string]any{"count": len(drafts), "drafts": drafts})
  184. }
  185. func (a *API) GetDraft(w http.ResponseWriter, r *http.Request) {
  186. draftID := strings.TrimSpace(chi.URLParam(r, "id"))
  187. draft, err := a.draftSvc.GetDraft(r.Context(), draftID)
  188. if err != nil {
  189. writeJSON(w, http.StatusNotFound, map[string]any{"error": err.Error()})
  190. return
  191. }
  192. writeJSON(w, http.StatusOK, draft)
  193. }
  194. func (a *API) UpdateDraft(w http.ResponseWriter, r *http.Request) {
  195. draftID := strings.TrimSpace(chi.URLParam(r, "id"))
  196. var req upsertDraftRequest
  197. if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
  198. writeJSON(w, http.StatusBadRequest, map[string]any{"error": "invalid JSON body"})
  199. return
  200. }
  201. draft, err := a.draftSvc.SaveDraft(r.Context(), draftsvc.UpsertDraftRequest{
  202. DraftID: draftID,
  203. TemplateID: req.TemplateID,
  204. ManifestID: req.ManifestID,
  205. Source: req.Source,
  206. RequestName: req.RequestName,
  207. GlobalData: req.GlobalData,
  208. FieldValues: req.FieldValues,
  209. Status: req.Status,
  210. Notes: req.Notes,
  211. })
  212. if err != nil {
  213. writeJSON(w, http.StatusBadRequest, map[string]any{"error": err.Error()})
  214. return
  215. }
  216. writeJSON(w, http.StatusOK, draft)
  217. }
  218. func (a *API) GetBuild(w http.ResponseWriter, r *http.Request) {
  219. buildID := chi.URLParam(r, "id")
  220. build, err := a.buildSvc.GetBuild(r.Context(), buildID)
  221. if err != nil {
  222. writeJSON(w, http.StatusNotFound, map[string]any{"error": err.Error()})
  223. return
  224. }
  225. writeJSON(w, http.StatusOK, build)
  226. }
  227. func (a *API) PollBuildOnce(w http.ResponseWriter, r *http.Request) {
  228. buildID := chi.URLParam(r, "id")
  229. if err := a.buildSvc.PollOnce(r.Context(), buildID); err != nil {
  230. writeJSON(w, http.StatusBadRequest, map[string]any{"error": err.Error()})
  231. return
  232. }
  233. build, err := a.buildSvc.GetBuild(r.Context(), buildID)
  234. if err != nil {
  235. writeJSON(w, http.StatusNotFound, map[string]any{"error": err.Error()})
  236. return
  237. }
  238. writeJSON(w, http.StatusOK, build)
  239. }
  240. func (a *API) FetchBuildEditorURL(w http.ResponseWriter, r *http.Request) {
  241. buildID := chi.URLParam(r, "id")
  242. if err := a.buildSvc.FetchEditorURL(r.Context(), buildID); err != nil {
  243. writeJSON(w, http.StatusBadRequest, map[string]any{"error": err.Error()})
  244. return
  245. }
  246. build, err := a.buildSvc.GetBuild(r.Context(), buildID)
  247. if err != nil {
  248. writeJSON(w, http.StatusNotFound, map[string]any{"error": err.Error()})
  249. return
  250. }
  251. writeJSON(w, http.StatusOK, build)
  252. }
  253. func writeJSON(w http.ResponseWriter, status int, v any) {
  254. w.Header().Set("Content-Type", "application/json")
  255. w.WriteHeader(status)
  256. _ = json.NewEncoder(w).Encode(v)
  257. }
  258. func defaultStr(v, fallback string) string {
  259. if strings.TrimSpace(v) == "" {
  260. return fallback
  261. }
  262. return strings.TrimSpace(v)
  263. }