Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

196 linhas
5.5KB

  1. package draftsvc
  2. import (
  3. "context"
  4. "encoding/json"
  5. "errors"
  6. "fmt"
  7. "strconv"
  8. "strings"
  9. "time"
  10. "qctextbuilder/internal/domain"
  11. "qctextbuilder/internal/store"
  12. )
  13. type UpsertDraftRequest struct {
  14. DraftID string `json:"draftId,omitempty"`
  15. TemplateID *int64 `json:"templateId,omitempty"`
  16. ManifestID string `json:"manifestId,omitempty"`
  17. Source string `json:"source,omitempty"`
  18. RequestName string `json:"requestName,omitempty"`
  19. GlobalData map[string]any `json:"globalData"`
  20. FieldValues map[string]string `json:"fieldValues"`
  21. DraftContext *domain.DraftContext `json:"draftContext,omitempty"`
  22. SuggestionState *domain.DraftSuggestionState `json:"suggestionState,omitempty"`
  23. Status string `json:"status,omitempty"`
  24. Notes string `json:"notes,omitempty"`
  25. }
  26. type Service struct {
  27. drafts store.DraftStore
  28. templates store.TemplateStore
  29. manifests store.ManifestStore
  30. }
  31. func New(draftStore store.DraftStore, templateStore store.TemplateStore, manifestStore store.ManifestStore) *Service {
  32. return &Service{
  33. drafts: draftStore,
  34. templates: templateStore,
  35. manifests: manifestStore,
  36. }
  37. }
  38. func (s *Service) SaveDraft(ctx context.Context, req UpsertDraftRequest) (*domain.BuildDraft, error) {
  39. templateID := int64(0)
  40. if req.TemplateID != nil {
  41. templateID = *req.TemplateID
  42. }
  43. var existing *domain.BuildDraft
  44. if strings.TrimSpace(req.DraftID) != "" {
  45. var err error
  46. existing, err = s.drafts.GetDraftByID(ctx, strings.TrimSpace(req.DraftID))
  47. if err != nil {
  48. return nil, fmt.Errorf("get draft: %w", err)
  49. }
  50. if templateID == 0 {
  51. templateID = existing.TemplateID
  52. }
  53. }
  54. if templateID > 0 {
  55. template, err := s.templates.GetTemplateByID(ctx, templateID)
  56. if err != nil {
  57. return nil, fmt.Errorf("get template: %w", err)
  58. }
  59. if !template.IsAITemplate {
  60. return nil, errors.New("only ai templates are allowed")
  61. }
  62. }
  63. manifestID := strings.TrimSpace(req.ManifestID)
  64. if templateID <= 0 {
  65. manifestID = ""
  66. } else if manifestID == "" {
  67. manifest, err := s.manifests.GetActiveManifestByTemplateID(ctx, templateID)
  68. if err != nil {
  69. return nil, fmt.Errorf("get active manifest: %w", err)
  70. }
  71. manifestID = manifest.ID
  72. }
  73. globalDataJSON, err := json.Marshal(req.GlobalData)
  74. if err != nil {
  75. return nil, errors.New("globalData is invalid JSON")
  76. }
  77. fieldValuesJSON, err := json.Marshal(req.FieldValues)
  78. if err != nil {
  79. return nil, errors.New("fieldValues is invalid JSON")
  80. }
  81. draftContextJSON, err := buildDraftContextJSON(req, existing)
  82. if err != nil {
  83. return nil, err
  84. }
  85. suggestionStateJSON, err := buildSuggestionStateJSON(req, existing)
  86. if err != nil {
  87. return nil, err
  88. }
  89. now := time.Now().UTC()
  90. source := defaultString(req.Source, "ui")
  91. status := normalizeDraftStatus(req.Status)
  92. if status == "" {
  93. status = "draft"
  94. }
  95. draft := domain.BuildDraft{
  96. ID: strings.TrimSpace(req.DraftID),
  97. TemplateID: templateID,
  98. ManifestID: manifestID,
  99. Source: source,
  100. RequestName: strings.TrimSpace(req.RequestName),
  101. GlobalDataJSON: globalDataJSON,
  102. FieldValuesJSON: fieldValuesJSON,
  103. DraftContextJSON: draftContextJSON,
  104. SuggestionStateJSON: suggestionStateJSON,
  105. Status: status,
  106. Notes: strings.TrimSpace(req.Notes),
  107. UpdatedAt: now,
  108. }
  109. if draft.ID == "" {
  110. draft.ID = strconv.FormatInt(time.Now().UnixNano(), 10)
  111. draft.CreatedAt = now
  112. if err := s.drafts.CreateDraft(ctx, draft); err != nil {
  113. return nil, fmt.Errorf("create draft: %w", err)
  114. }
  115. } else {
  116. existing, err := s.drafts.GetDraftByID(ctx, draft.ID)
  117. if err != nil {
  118. return nil, fmt.Errorf("get draft: %w", err)
  119. }
  120. draft.CreatedAt = existing.CreatedAt
  121. if err := s.drafts.UpdateDraft(ctx, draft); err != nil {
  122. return nil, fmt.Errorf("update draft: %w", err)
  123. }
  124. }
  125. return s.drafts.GetDraftByID(ctx, draft.ID)
  126. }
  127. func buildDraftContextJSON(req UpsertDraftRequest, existing *domain.BuildDraft) (json.RawMessage, error) {
  128. if req.DraftContext == nil {
  129. if existing != nil {
  130. return existing.DraftContextJSON, nil
  131. }
  132. return nil, nil
  133. }
  134. raw, err := json.Marshal(req.DraftContext)
  135. if err != nil {
  136. return nil, errors.New("draftContext is invalid JSON")
  137. }
  138. return raw, nil
  139. }
  140. func buildSuggestionStateJSON(req UpsertDraftRequest, existing *domain.BuildDraft) (json.RawMessage, error) {
  141. if req.SuggestionState == nil {
  142. if existing != nil {
  143. return existing.SuggestionStateJSON, nil
  144. }
  145. return nil, nil
  146. }
  147. raw, err := json.Marshal(req.SuggestionState)
  148. if err != nil {
  149. return nil, errors.New("suggestionState is invalid JSON")
  150. }
  151. return raw, nil
  152. }
  153. func (s *Service) GetDraft(ctx context.Context, draftID string) (*domain.BuildDraft, error) {
  154. return s.drafts.GetDraftByID(ctx, strings.TrimSpace(draftID))
  155. }
  156. func (s *Service) ListDrafts(ctx context.Context, limit int) ([]domain.BuildDraft, error) {
  157. if limit <= 0 {
  158. limit = 50
  159. }
  160. return s.drafts.ListDrafts(ctx, limit)
  161. }
  162. func normalizeDraftStatus(status string) string {
  163. switch strings.ToLower(strings.TrimSpace(status)) {
  164. case "":
  165. return ""
  166. case "draft", "reviewed", "submitted":
  167. return strings.ToLower(strings.TrimSpace(status))
  168. default:
  169. return "draft"
  170. }
  171. }
  172. func defaultString(value, fallback string) string {
  173. if strings.TrimSpace(value) == "" {
  174. return fallback
  175. }
  176. return strings.TrimSpace(value)
  177. }