Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

176 Zeilen
4.7KB

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