You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

645 lines
20KB

  1. package handlers
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "net/http"
  6. "net/url"
  7. "strconv"
  8. "strings"
  9. "github.com/go-chi/chi/v5"
  10. "qctextbuilder/internal/buildsvc"
  11. "qctextbuilder/internal/config"
  12. "qctextbuilder/internal/domain"
  13. "qctextbuilder/internal/draftsvc"
  14. "qctextbuilder/internal/onboarding"
  15. "qctextbuilder/internal/templatesvc"
  16. )
  17. type UI struct {
  18. templateSvc *templatesvc.Service
  19. onboardSvc *onboarding.Service
  20. draftSvc *draftsvc.Service
  21. buildSvc buildsvc.Service
  22. cfg config.Config
  23. render htmlRenderer
  24. }
  25. type htmlRenderer interface {
  26. Render(w http.ResponseWriter, name string, data any)
  27. }
  28. type pageData struct {
  29. Title string
  30. Msg string
  31. Err string
  32. Current string
  33. }
  34. type homePageData struct {
  35. pageData
  36. TemplateCount int
  37. }
  38. type settingsPageData struct {
  39. pageData
  40. QCBaseURL string
  41. PollIntervalSeconds int
  42. PollTimeoutSeconds int
  43. PollMaxConcurrent int
  44. TokenConfigured bool
  45. LanguageOutputMode string
  46. }
  47. type templatesPageData struct {
  48. pageData
  49. Templates []domain.Template
  50. }
  51. type templateFieldView struct {
  52. Path string
  53. FieldKind string
  54. IsEnabled bool
  55. IsRequiredByUs bool
  56. DisplayLabel string
  57. DisplayOrder int
  58. Notes string
  59. SampleValue string
  60. }
  61. type templateDetailPageData struct {
  62. pageData
  63. Detail *templatesvc.TemplateDetail
  64. Fields []templateFieldView
  65. }
  66. type buildFieldView struct {
  67. Path string
  68. DisplayLabel string
  69. SampleValue string
  70. Value string
  71. }
  72. type buildNewPageData struct {
  73. pageData
  74. Templates []domain.Template
  75. Drafts []domain.BuildDraft
  76. SelectedDraftID string
  77. SelectedTemplateID int64
  78. SelectedManifestID string
  79. EnabledFields []buildFieldView
  80. Form buildFormInput
  81. }
  82. type buildFormInput struct {
  83. DraftID string
  84. DraftSource string
  85. DraftStatus string
  86. DraftNotes string
  87. RequestName string
  88. CompanyName string
  89. BusinessType string
  90. Username string
  91. Email string
  92. Phone string
  93. OrgNumber string
  94. StartDate string
  95. Mission string
  96. DescriptionShort string
  97. DescriptionLong string
  98. SiteLanguage string
  99. AddressLine1 string
  100. AddressLine2 string
  101. AddressCity string
  102. AddressRegion string
  103. AddressZIP string
  104. AddressCountry string
  105. }
  106. type buildDetailPageData struct {
  107. pageData
  108. Build *domain.SiteBuild
  109. EffectiveGlobal []byte
  110. CanPoll bool
  111. CanFetchEditorURL bool
  112. AutoRefreshSeconds int
  113. }
  114. func NewUI(templateSvc *templatesvc.Service, onboardSvc *onboarding.Service, draftSvc *draftsvc.Service, buildSvc buildsvc.Service, cfg config.Config, render htmlRenderer) *UI {
  115. return &UI{templateSvc: templateSvc, onboardSvc: onboardSvc, draftSvc: draftSvc, buildSvc: buildSvc, cfg: cfg, render: render}
  116. }
  117. func (u *UI) Home(w http.ResponseWriter, r *http.Request) {
  118. templates, err := u.templateSvc.ListTemplates(r.Context())
  119. if err != nil {
  120. u.render.Render(w, "home", homePageData{pageData: basePageData(r, "Home", "/"), TemplateCount: 0})
  121. return
  122. }
  123. u.render.Render(w, "home", homePageData{pageData: basePageData(r, "Home", "/"), TemplateCount: len(templates)})
  124. }
  125. func (u *UI) Settings(w http.ResponseWriter, r *http.Request) {
  126. u.render.Render(w, "settings", settingsPageData{
  127. pageData: basePageData(r, "Settings", "/settings"),
  128. QCBaseURL: u.cfg.QCBaseURL,
  129. PollIntervalSeconds: u.cfg.PollIntervalSeconds,
  130. PollTimeoutSeconds: u.cfg.PollTimeoutSeconds,
  131. PollMaxConcurrent: u.cfg.PollMaxConcurrent,
  132. TokenConfigured: strings.TrimSpace(u.cfg.QCToken) != "",
  133. LanguageOutputMode: "EN",
  134. })
  135. }
  136. func (u *UI) Templates(w http.ResponseWriter, r *http.Request) {
  137. templates, err := u.templateSvc.ListTemplates(r.Context())
  138. if err != nil {
  139. http.Error(w, err.Error(), http.StatusBadRequest)
  140. return
  141. }
  142. u.render.Render(w, "templates", templatesPageData{pageData: basePageData(r, "Templates", "/templates"), Templates: templates})
  143. }
  144. func (u *UI) SyncTemplates(w http.ResponseWriter, r *http.Request) {
  145. if _, err := u.templateSvc.SyncAITemplates(r.Context()); err != nil {
  146. http.Redirect(w, r, "/templates?err="+urlQuery(err.Error()), http.StatusSeeOther)
  147. return
  148. }
  149. http.Redirect(w, r, "/templates?msg=sync+done", http.StatusSeeOther)
  150. }
  151. func (u *UI) TemplateDetail(w http.ResponseWriter, r *http.Request) {
  152. templateID, ok := parseTemplateID(w, r)
  153. if !ok {
  154. return
  155. }
  156. detail, err := u.templateSvc.GetTemplateDetail(r.Context(), templateID)
  157. if err != nil {
  158. http.Error(w, err.Error(), http.StatusNotFound)
  159. return
  160. }
  161. fields := make([]templateFieldView, 0, len(detail.Fields))
  162. for _, f := range detail.Fields {
  163. fields = append(fields, templateFieldView{
  164. Path: f.Path,
  165. FieldKind: f.FieldKind,
  166. IsEnabled: f.IsEnabled,
  167. IsRequiredByUs: f.IsRequiredByUs,
  168. DisplayLabel: f.DisplayLabel,
  169. DisplayOrder: f.DisplayOrder,
  170. Notes: f.Notes,
  171. SampleValue: f.SampleValue,
  172. })
  173. }
  174. u.render.Render(w, "template_detail", templateDetailPageData{pageData: basePageData(r, "Template Detail", "/templates"), Detail: detail, Fields: fields})
  175. }
  176. func (u *UI) OnboardTemplate(w http.ResponseWriter, r *http.Request) {
  177. templateID, ok := parseTemplateID(w, r)
  178. if !ok {
  179. return
  180. }
  181. if _, _, err := u.onboardSvc.OnboardTemplate(r.Context(), templateID); err != nil {
  182. http.Redirect(w, r, fmt.Sprintf("/templates/%d?err=%s", templateID, urlQuery(err.Error())), http.StatusSeeOther)
  183. return
  184. }
  185. http.Redirect(w, r, fmt.Sprintf("/templates/%d?msg=onboarding+done", templateID), http.StatusSeeOther)
  186. }
  187. func (u *UI) UpdateTemplateFields(w http.ResponseWriter, r *http.Request) {
  188. templateID, ok := parseTemplateID(w, r)
  189. if !ok {
  190. return
  191. }
  192. if err := r.ParseForm(); err != nil {
  193. http.Redirect(w, r, fmt.Sprintf("/templates/%d?err=%s", templateID, urlQuery("invalid form")), http.StatusSeeOther)
  194. return
  195. }
  196. count, _ := strconv.Atoi(r.FormValue("field_count"))
  197. patches := make([]onboarding.FieldPatch, 0, count)
  198. for i := 0; i < count; i++ {
  199. path := strings.TrimSpace(r.FormValue(fmt.Sprintf("field_path_%d", i)))
  200. if path == "" {
  201. continue
  202. }
  203. enabled := r.FormValue(fmt.Sprintf("field_enabled_%d", i)) == "on"
  204. required := r.FormValue(fmt.Sprintf("field_required_%d", i)) == "on"
  205. label := r.FormValue(fmt.Sprintf("field_label_%d", i))
  206. notes := r.FormValue(fmt.Sprintf("field_notes_%d", i))
  207. order, err := strconv.Atoi(strings.TrimSpace(r.FormValue(fmt.Sprintf("field_order_%d", i))))
  208. if err != nil {
  209. http.Redirect(w, r, fmt.Sprintf("/templates/%d?err=%s", templateID, urlQuery("invalid display order")), http.StatusSeeOther)
  210. return
  211. }
  212. patches = append(patches, onboarding.FieldPatch{
  213. Path: path,
  214. IsEnabled: boolPtr(enabled),
  215. IsRequiredByUs: boolPtr(required),
  216. DisplayLabel: strPtr(label),
  217. DisplayOrder: intPtr(order),
  218. Notes: strPtr(notes),
  219. })
  220. }
  221. manifestID := r.FormValue("manifest_id")
  222. if _, _, err := u.onboardSvc.UpdateTemplateFields(r.Context(), templateID, manifestID, patches); err != nil {
  223. http.Redirect(w, r, fmt.Sprintf("/templates/%d?err=%s", templateID, urlQuery(err.Error())), http.StatusSeeOther)
  224. return
  225. }
  226. http.Redirect(w, r, fmt.Sprintf("/templates/%d?msg=fields+saved", templateID), http.StatusSeeOther)
  227. }
  228. func (u *UI) BuildNew(w http.ResponseWriter, r *http.Request) {
  229. selectedTemplateID, _ := strconv.ParseInt(strings.TrimSpace(r.URL.Query().Get("template_id")), 10, 64)
  230. selectedDraftID := strings.TrimSpace(r.URL.Query().Get("draft_id"))
  231. form := buildFormInput{
  232. DraftID: selectedDraftID,
  233. DraftSource: "ui",
  234. DraftStatus: "draft",
  235. }
  236. fieldValues := map[string]string{}
  237. if selectedDraftID != "" {
  238. draft, err := u.draftSvc.GetDraft(r.Context(), selectedDraftID)
  239. if err == nil {
  240. selectedTemplateID = draft.TemplateID
  241. form = buildFormInputFromDraft(draft)
  242. fieldValues = parseFieldValuesJSON(draft.FieldValuesJSON)
  243. }
  244. }
  245. data, err := u.loadBuildNewPageData(r, basePageData(r, "New Build", "/builds/new"), selectedDraftID, selectedTemplateID, form, fieldValues)
  246. if err != nil {
  247. http.Error(w, err.Error(), http.StatusBadRequest)
  248. return
  249. }
  250. u.render.Render(w, "build_new", data)
  251. }
  252. func (u *UI) CreateBuild(w http.ResponseWriter, r *http.Request) {
  253. if err := r.ParseForm(); err != nil {
  254. http.Redirect(w, r, "/builds/new?err=invalid+form", http.StatusSeeOther)
  255. return
  256. }
  257. form := buildFormInputFromRequest(r)
  258. fieldValues := parseBuildFieldValues(r)
  259. globalData := buildsvc.BuildGlobalData(buildsvc.GlobalDataInput{
  260. CompanyName: form.CompanyName,
  261. BusinessType: form.BusinessType,
  262. Username: form.Username,
  263. Email: form.Email,
  264. Phone: form.Phone,
  265. OrgNumber: form.OrgNumber,
  266. StartDate: form.StartDate,
  267. Mission: form.Mission,
  268. DescriptionShort: form.DescriptionShort,
  269. DescriptionLong: form.DescriptionLong,
  270. SiteLanguage: form.SiteLanguage,
  271. AddressLine1: form.AddressLine1,
  272. AddressLine2: form.AddressLine2,
  273. AddressCity: form.AddressCity,
  274. AddressRegion: form.AddressRegion,
  275. AddressZIP: form.AddressZIP,
  276. AddressCountry: form.AddressCountry,
  277. })
  278. templateID, err := strconv.ParseInt(strings.TrimSpace(r.FormValue("template_id")), 10, 64)
  279. if err != nil || templateID <= 0 {
  280. http.Redirect(w, r, "/builds/new?err=invalid+template", http.StatusSeeOther)
  281. return
  282. }
  283. result, err := u.buildSvc.StartBuild(r.Context(), buildsvc.StartBuildRequest{
  284. TemplateID: templateID,
  285. RequestName: form.RequestName,
  286. GlobalData: globalData,
  287. FieldValues: fieldValues,
  288. })
  289. if err != nil {
  290. data, loadErr := u.loadBuildNewPageData(r, pageData{
  291. Title: "New Build",
  292. Err: err.Error(),
  293. Current: "/builds/new",
  294. }, form.DraftID, templateID, form, fieldValues)
  295. if loadErr != nil {
  296. http.Error(w, loadErr.Error(), http.StatusBadRequest)
  297. return
  298. }
  299. u.render.Render(w, "build_new", data)
  300. return
  301. }
  302. if form.DraftID != "" {
  303. _, _ = u.draftSvc.SaveDraft(r.Context(), draftsvc.UpsertDraftRequest{
  304. DraftID: form.DraftID,
  305. TemplateID: templateID,
  306. ManifestID: strings.TrimSpace(r.FormValue("manifest_id")),
  307. Source: form.DraftSource,
  308. RequestName: form.RequestName,
  309. GlobalData: globalData,
  310. FieldValues: fieldValues,
  311. Status: "submitted",
  312. Notes: form.DraftNotes,
  313. })
  314. }
  315. http.Redirect(w, r, fmt.Sprintf("/builds/%s?msg=build+started", result.BuildID), http.StatusSeeOther)
  316. }
  317. func (u *UI) SaveDraft(w http.ResponseWriter, r *http.Request) {
  318. if err := r.ParseForm(); err != nil {
  319. http.Redirect(w, r, "/builds/new?err=invalid+form", http.StatusSeeOther)
  320. return
  321. }
  322. form := buildFormInputFromRequest(r)
  323. fieldValues := parseBuildFieldValues(r)
  324. templateID, err := strconv.ParseInt(strings.TrimSpace(r.FormValue("template_id")), 10, 64)
  325. if err != nil || templateID <= 0 {
  326. http.Redirect(w, r, "/builds/new?err=invalid+template", http.StatusSeeOther)
  327. return
  328. }
  329. globalData := buildsvc.BuildGlobalData(buildsvc.GlobalDataInput{
  330. CompanyName: form.CompanyName,
  331. BusinessType: form.BusinessType,
  332. Username: form.Username,
  333. Email: form.Email,
  334. Phone: form.Phone,
  335. OrgNumber: form.OrgNumber,
  336. StartDate: form.StartDate,
  337. Mission: form.Mission,
  338. DescriptionShort: form.DescriptionShort,
  339. DescriptionLong: form.DescriptionLong,
  340. SiteLanguage: form.SiteLanguage,
  341. AddressLine1: form.AddressLine1,
  342. AddressLine2: form.AddressLine2,
  343. AddressCity: form.AddressCity,
  344. AddressRegion: form.AddressRegion,
  345. AddressZIP: form.AddressZIP,
  346. AddressCountry: form.AddressCountry,
  347. })
  348. draft, err := u.draftSvc.SaveDraft(r.Context(), draftsvc.UpsertDraftRequest{
  349. DraftID: form.DraftID,
  350. TemplateID: templateID,
  351. ManifestID: strings.TrimSpace(r.FormValue("manifest_id")),
  352. Source: form.DraftSource,
  353. RequestName: form.RequestName,
  354. GlobalData: globalData,
  355. FieldValues: fieldValues,
  356. Status: defaultDraftStatus(form.DraftStatus),
  357. Notes: form.DraftNotes,
  358. })
  359. if err != nil {
  360. data, loadErr := u.loadBuildNewPageData(r, pageData{
  361. Title: "New Build",
  362. Err: err.Error(),
  363. Current: "/builds/new",
  364. }, form.DraftID, templateID, form, fieldValues)
  365. if loadErr != nil {
  366. http.Error(w, loadErr.Error(), http.StatusBadRequest)
  367. return
  368. }
  369. u.render.Render(w, "build_new", data)
  370. return
  371. }
  372. http.Redirect(w, r, fmt.Sprintf("/builds/new?template_id=%d&draft_id=%s&msg=draft+saved", templateID, urlQuery(draft.ID)), http.StatusSeeOther)
  373. }
  374. func (u *UI) BuildDetail(w http.ResponseWriter, r *http.Request) {
  375. buildID := strings.TrimSpace(chi.URLParam(r, "id"))
  376. build, err := u.buildSvc.GetBuild(r.Context(), buildID)
  377. if err != nil {
  378. http.Error(w, err.Error(), http.StatusNotFound)
  379. return
  380. }
  381. status := strings.ToLower(strings.TrimSpace(build.QCStatus))
  382. canPoll := status == "queued" || status == "processing"
  383. canFetchEditor := (status == "done" || status == "failed" || status == "timeout") &&
  384. build.QCSiteID != nil &&
  385. strings.TrimSpace(build.QCEditorURL) == ""
  386. autoRefresh := 0
  387. if canPoll && u.cfg.PollIntervalSeconds > 0 {
  388. autoRefresh = u.cfg.PollIntervalSeconds
  389. }
  390. effectiveGlobal := build.GlobalDataJSON
  391. if payloadGlobal, err := extractGlobalDataFromFinalPayload(build.FinalSitesPayload); err == nil && len(payloadGlobal) > 0 {
  392. effectiveGlobal = payloadGlobal
  393. }
  394. u.render.Render(w, "build_detail", buildDetailPageData{
  395. pageData: basePageData(r, "Build Detail", "/builds"),
  396. Build: build,
  397. EffectiveGlobal: effectiveGlobal,
  398. CanPoll: canPoll,
  399. CanFetchEditorURL: canFetchEditor,
  400. AutoRefreshSeconds: autoRefresh,
  401. })
  402. }
  403. func (u *UI) PollBuild(w http.ResponseWriter, r *http.Request) {
  404. buildID := strings.TrimSpace(chi.URLParam(r, "id"))
  405. if err := u.buildSvc.PollOnce(r.Context(), buildID); err != nil {
  406. http.Redirect(w, r, fmt.Sprintf("/builds/%s?err=%s", buildID, urlQuery(err.Error())), http.StatusSeeOther)
  407. return
  408. }
  409. http.Redirect(w, r, fmt.Sprintf("/builds/%s?msg=poll+done", buildID), http.StatusSeeOther)
  410. }
  411. func (u *UI) FetchEditorURL(w http.ResponseWriter, r *http.Request) {
  412. buildID := strings.TrimSpace(chi.URLParam(r, "id"))
  413. if err := u.buildSvc.FetchEditorURL(r.Context(), buildID); err != nil {
  414. http.Redirect(w, r, fmt.Sprintf("/builds/%s?err=%s", buildID, urlQuery(err.Error())), http.StatusSeeOther)
  415. return
  416. }
  417. http.Redirect(w, r, fmt.Sprintf("/builds/%s?msg=editor+url+loaded", buildID), http.StatusSeeOther)
  418. }
  419. func basePageData(r *http.Request, title, current string) pageData {
  420. q := r.URL.Query()
  421. return pageData{Title: title, Msg: q.Get("msg"), Err: q.Get("err"), Current: current}
  422. }
  423. func parseTemplateID(w http.ResponseWriter, r *http.Request) (int64, bool) {
  424. rawID := chi.URLParam(r, "id")
  425. templateID, err := strconv.ParseInt(rawID, 10, 64)
  426. if err != nil {
  427. http.Error(w, "invalid template id", http.StatusBadRequest)
  428. return 0, false
  429. }
  430. return templateID, true
  431. }
  432. func urlQuery(s string) string {
  433. return url.QueryEscape(s)
  434. }
  435. func boolPtr(v bool) *bool { return &v }
  436. func intPtr(v int) *int { return &v }
  437. func strPtr(v string) *string {
  438. return &v
  439. }
  440. func (u *UI) loadBuildNewPageData(r *http.Request, page pageData, selectedDraftID string, selectedTemplateID int64, form buildFormInput, fieldValues map[string]string) (buildNewPageData, error) {
  441. templates, err := u.templateSvc.ListTemplates(r.Context())
  442. if err != nil {
  443. return buildNewPageData{}, err
  444. }
  445. drafts, err := u.draftSvc.ListDrafts(r.Context(), 50)
  446. if err != nil {
  447. return buildNewPageData{}, err
  448. }
  449. data := buildNewPageData{
  450. pageData: page,
  451. Templates: templates,
  452. Drafts: drafts,
  453. SelectedDraftID: selectedDraftID,
  454. SelectedTemplateID: selectedTemplateID,
  455. Form: form,
  456. }
  457. if selectedTemplateID <= 0 {
  458. return data, nil
  459. }
  460. detail, err := u.templateSvc.GetTemplateDetail(r.Context(), selectedTemplateID)
  461. if err != nil || detail.Manifest == nil {
  462. return data, nil
  463. }
  464. data.SelectedManifestID = detail.Manifest.ID
  465. for _, f := range detail.Fields {
  466. if !f.IsEnabled || f.FieldKind != "text" {
  467. continue
  468. }
  469. data.EnabledFields = append(data.EnabledFields, buildFieldView{
  470. Path: f.Path,
  471. DisplayLabel: f.DisplayLabel,
  472. SampleValue: f.SampleValue,
  473. Value: strings.TrimSpace(fieldValues[f.Path]),
  474. })
  475. }
  476. return data, nil
  477. }
  478. func buildFormInputFromRequest(r *http.Request) buildFormInput {
  479. return buildFormInput{
  480. DraftID: strings.TrimSpace(r.FormValue("draft_id")),
  481. DraftSource: strings.TrimSpace(r.FormValue("draft_source")),
  482. DraftStatus: strings.TrimSpace(r.FormValue("draft_status")),
  483. DraftNotes: strings.TrimSpace(r.FormValue("draft_notes")),
  484. RequestName: strings.TrimSpace(r.FormValue("request_name")),
  485. CompanyName: strings.TrimSpace(r.FormValue("company_name")),
  486. BusinessType: strings.TrimSpace(r.FormValue("business_type")),
  487. Username: strings.TrimSpace(r.FormValue("username")),
  488. Email: strings.TrimSpace(r.FormValue("email")),
  489. Phone: strings.TrimSpace(r.FormValue("phone")),
  490. OrgNumber: strings.TrimSpace(r.FormValue("org_number")),
  491. StartDate: strings.TrimSpace(r.FormValue("start_date")),
  492. Mission: strings.TrimSpace(r.FormValue("mission")),
  493. DescriptionShort: strings.TrimSpace(r.FormValue("description_short")),
  494. DescriptionLong: strings.TrimSpace(r.FormValue("description_long")),
  495. SiteLanguage: strings.TrimSpace(r.FormValue("site_language")),
  496. AddressLine1: strings.TrimSpace(r.FormValue("address_line1")),
  497. AddressLine2: strings.TrimSpace(r.FormValue("address_line2")),
  498. AddressCity: strings.TrimSpace(r.FormValue("address_city")),
  499. AddressRegion: strings.TrimSpace(r.FormValue("address_region")),
  500. AddressZIP: strings.TrimSpace(r.FormValue("address_zip")),
  501. AddressCountry: strings.TrimSpace(r.FormValue("address_country")),
  502. }
  503. }
  504. func buildFormInputFromDraft(draft *domain.BuildDraft) buildFormInput {
  505. form := buildFormInput{
  506. DraftID: draft.ID,
  507. DraftSource: draft.Source,
  508. DraftStatus: draft.Status,
  509. DraftNotes: draft.Notes,
  510. RequestName: draft.RequestName,
  511. }
  512. mergeGlobalDataIntoForm(&form, draft.GlobalDataJSON)
  513. return form
  514. }
  515. func parseBuildFieldValues(r *http.Request) map[string]string {
  516. fieldValues := map[string]string{}
  517. count, _ := strconv.Atoi(strings.TrimSpace(r.FormValue("field_count")))
  518. for i := 0; i < count; i++ {
  519. path := strings.TrimSpace(r.FormValue(fmt.Sprintf("field_path_%d", i)))
  520. value := strings.TrimSpace(r.FormValue(fmt.Sprintf("field_value_%d", i)))
  521. if path != "" {
  522. fieldValues[path] = value
  523. }
  524. }
  525. return fieldValues
  526. }
  527. func extractGlobalDataFromFinalPayload(raw []byte) ([]byte, error) {
  528. if len(raw) == 0 {
  529. return nil, nil
  530. }
  531. var payload struct {
  532. GlobalData map[string]any `json:"globalData"`
  533. }
  534. if err := json.Unmarshal(raw, &payload); err != nil {
  535. return nil, err
  536. }
  537. if len(payload.GlobalData) == 0 {
  538. return nil, nil
  539. }
  540. data, err := json.Marshal(payload.GlobalData)
  541. if err != nil {
  542. return nil, err
  543. }
  544. return data, nil
  545. }
  546. func parseFieldValuesJSON(raw []byte) map[string]string {
  547. out := map[string]string{}
  548. if len(raw) == 0 {
  549. return out
  550. }
  551. _ = json.Unmarshal(raw, &out)
  552. return out
  553. }
  554. func mergeGlobalDataIntoForm(form *buildFormInput, raw []byte) {
  555. if form == nil || len(raw) == 0 {
  556. return
  557. }
  558. var global map[string]any
  559. if err := json.Unmarshal(raw, &global); err != nil {
  560. return
  561. }
  562. form.CompanyName = getString(global["companyName"])
  563. form.BusinessType = getString(global["businessType"])
  564. form.Username = getString(global["username"])
  565. form.Email = getString(global["email"])
  566. form.Phone = getString(global["phone"])
  567. form.OrgNumber = getString(global["orgNumber"])
  568. form.StartDate = getString(global["startDate"])
  569. form.Mission = getString(global["mission"])
  570. form.DescriptionShort = getString(global["descriptionShort"])
  571. form.DescriptionLong = getString(global["descriptionLong"])
  572. form.SiteLanguage = getString(global["siteLanguage"])
  573. address, _ := global["address"].(map[string]any)
  574. form.AddressLine1 = getString(address["line1"])
  575. form.AddressLine2 = getString(address["line2"])
  576. form.AddressCity = getString(address["city"])
  577. form.AddressRegion = getString(address["region"])
  578. form.AddressZIP = getString(address["zip"])
  579. form.AddressCountry = getString(address["country"])
  580. }
  581. func getString(v any) string {
  582. s, _ := v.(string)
  583. return strings.TrimSpace(s)
  584. }
  585. func defaultDraftStatus(status string) string {
  586. switch strings.ToLower(strings.TrimSpace(status)) {
  587. case "reviewed", "submitted":
  588. return strings.ToLower(strings.TrimSpace(status))
  589. default:
  590. return "draft"
  591. }
  592. }