|
- package memory
-
- import (
- "context"
- "encoding/json"
- "errors"
- "sort"
- "sync"
- "time"
-
- "qctextbuilder/internal/domain"
- "qctextbuilder/internal/store"
- )
-
- type Store struct {
- mu sync.RWMutex
- templates map[int64]domain.Template
- manifests map[int64]domain.TemplateManifest
- manifestField map[string][]domain.TemplateField
- builds map[string]domain.SiteBuild
- drafts map[string]domain.BuildDraft
- settings domain.AppSettings
- hasSettings bool
- }
-
- func New() *Store {
- return &Store{
- templates: make(map[int64]domain.Template),
- manifests: make(map[int64]domain.TemplateManifest),
- manifestField: make(map[string][]domain.TemplateField),
- builds: make(map[string]domain.SiteBuild),
- drafts: make(map[string]domain.BuildDraft),
- }
- }
-
- func (s *Store) UpsertTemplates(_ context.Context, templates []domain.Template) error {
- s.mu.Lock()
- defer s.mu.Unlock()
-
- for _, t := range templates {
- existing, ok := s.templates[t.ID]
- if ok {
- t.IsOnboarded = existing.IsOnboarded
- t.ManifestStatus = existing.ManifestStatus
- t.LastDiscoveredAt = existing.LastDiscoveredAt
- }
- s.templates[t.ID] = t
- }
- return nil
- }
-
- func (s *Store) GetTemplateByID(_ context.Context, id int64) (*domain.Template, error) {
- s.mu.RLock()
- defer s.mu.RUnlock()
-
- t, ok := s.templates[id]
- if !ok {
- return nil, store.ErrNotFound
- }
- copy := t
- return ©, nil
- }
-
- func (s *Store) ListTemplates(_ context.Context) ([]domain.Template, error) {
- s.mu.RLock()
- defer s.mu.RUnlock()
-
- out := make([]domain.Template, 0, len(s.templates))
- for _, t := range s.templates {
- out = append(out, t)
- }
- return out, nil
- }
-
- func (s *Store) SetTemplateManifestStatus(_ context.Context, templateID int64, status string, onboarded bool) error {
- s.mu.Lock()
- defer s.mu.Unlock()
-
- t, ok := s.templates[templateID]
- if !ok {
- return store.ErrNotFound
- }
- t.ManifestStatus = status
- t.IsOnboarded = onboarded
- s.templates[templateID] = t
- return nil
- }
-
- func (s *Store) CreateManifest(_ context.Context, manifest domain.TemplateManifest, fields []domain.TemplateField) error {
- s.mu.Lock()
- defer s.mu.Unlock()
-
- s.manifests[manifest.TemplateID] = manifest
- s.manifestField[manifest.ID] = fields
- return nil
- }
-
- func (s *Store) GetActiveManifestByTemplateID(_ context.Context, templateID int64) (*domain.TemplateManifest, error) {
- s.mu.RLock()
- defer s.mu.RUnlock()
-
- m, ok := s.manifests[templateID]
- if !ok {
- return nil, store.ErrNotFound
- }
- copy := m
- return ©, nil
- }
-
- func (s *Store) ListFieldsByManifestID(_ context.Context, manifestID string) ([]domain.TemplateField, error) {
- s.mu.RLock()
- defer s.mu.RUnlock()
-
- fields, ok := s.manifestField[manifestID]
- if !ok {
- return nil, store.ErrNotFound
- }
- out := make([]domain.TemplateField, 0, len(fields))
- out = append(out, fields...)
- return out, nil
- }
-
- func (s *Store) UpdateFields(_ context.Context, manifestID string, fields []domain.TemplateField) error {
- s.mu.Lock()
- defer s.mu.Unlock()
-
- if _, ok := s.manifestField[manifestID]; !ok {
- return store.ErrNotFound
- }
- next := make([]domain.TemplateField, len(fields))
- copy(next, fields)
- s.manifestField[manifestID] = next
- return nil
- }
-
- func (s *Store) CreateBuild(_ context.Context, build domain.SiteBuild) error {
- s.mu.Lock()
- defer s.mu.Unlock()
-
- if _, exists := s.builds[build.ID]; exists {
- return errors.New("build already exists")
- }
- s.builds[build.ID] = build
- return nil
- }
-
- func (s *Store) GetBuildByID(_ context.Context, id string) (*domain.SiteBuild, error) {
- s.mu.RLock()
- defer s.mu.RUnlock()
-
- build, ok := s.builds[id]
- if !ok {
- return nil, store.ErrNotFound
- }
- copy := build
- return ©, nil
- }
-
- func (s *Store) ListBuildsByStatuses(_ context.Context, statuses []string, limit int) ([]domain.SiteBuild, error) {
- s.mu.RLock()
- defer s.mu.RUnlock()
-
- allowed := make(map[string]struct{}, len(statuses))
- for _, status := range statuses {
- allowed[status] = struct{}{}
- }
-
- out := make([]domain.SiteBuild, 0)
- for _, build := range s.builds {
- if len(allowed) > 0 {
- if _, ok := allowed[build.QCStatus]; !ok {
- continue
- }
- }
- out = append(out, build)
- if limit > 0 && len(out) >= limit {
- break
- }
- }
- return out, nil
- }
-
- func (s *Store) MarkBuildSubmitted(_ context.Context, buildID string, jobID int64, status string, qcResult json.RawMessage, startedAt time.Time) error {
- s.mu.Lock()
- defer s.mu.Unlock()
-
- build, ok := s.builds[buildID]
- if !ok {
- return store.ErrNotFound
- }
- build.QCJobID = &jobID
- build.QCStatus = status
- build.QCResultJSON = cloneRaw(qcResult)
- build.StartedAt = &startedAt
- s.builds[buildID] = build
- return nil
- }
-
- func (s *Store) UpdateBuildFromJob(_ context.Context, buildID string, status string, siteID *int64, previewURL string, qcResult json.RawMessage, qcError json.RawMessage, finishedAt *time.Time) error {
- s.mu.Lock()
- defer s.mu.Unlock()
-
- build, ok := s.builds[buildID]
- if !ok {
- return store.ErrNotFound
- }
- build.QCStatus = status
- build.QCResultJSON = cloneRaw(qcResult)
- build.QCErrorJSON = cloneRaw(qcError)
- build.QCPreviewURL = previewURL
- if siteID != nil {
- id := *siteID
- build.QCSiteID = &id
- }
- build.FinishedAt = finishedAt
- s.builds[buildID] = build
- return nil
- }
-
- func (s *Store) UpdateBuildEditorURL(_ context.Context, buildID string, editorURL string, qcResult json.RawMessage) error {
- s.mu.Lock()
- defer s.mu.Unlock()
-
- build, ok := s.builds[buildID]
- if !ok {
- return store.ErrNotFound
- }
- build.QCEditorURL = editorURL
- build.QCResultJSON = cloneRaw(qcResult)
- s.builds[buildID] = build
- return nil
- }
-
- func cloneRaw(raw json.RawMessage) json.RawMessage {
- if raw == nil {
- return nil
- }
- out := make([]byte, len(raw))
- copy(out, raw)
- return json.RawMessage(out)
- }
-
- func (s *Store) UpsertSettings(_ context.Context, settings domain.AppSettings) error {
- s.mu.Lock()
- defer s.mu.Unlock()
- s.settings = settings
- s.hasSettings = true
- return nil
- }
-
- func (s *Store) GetSettings(_ context.Context) (*domain.AppSettings, error) {
- s.mu.RLock()
- defer s.mu.RUnlock()
- if !s.hasSettings {
- return nil, store.ErrNotFound
- }
- value := s.settings
- return &value, nil
- }
-
- func (s *Store) CreateDraft(_ context.Context, draft domain.BuildDraft) error {
- s.mu.Lock()
- defer s.mu.Unlock()
- if _, exists := s.drafts[draft.ID]; exists {
- return errors.New("draft already exists")
- }
- s.drafts[draft.ID] = draft
- return nil
- }
-
- func (s *Store) UpdateDraft(_ context.Context, draft domain.BuildDraft) error {
- s.mu.Lock()
- defer s.mu.Unlock()
- if _, exists := s.drafts[draft.ID]; !exists {
- return store.ErrNotFound
- }
- s.drafts[draft.ID] = draft
- return nil
- }
-
- func (s *Store) GetDraftByID(_ context.Context, id string) (*domain.BuildDraft, error) {
- s.mu.RLock()
- defer s.mu.RUnlock()
- draft, ok := s.drafts[id]
- if !ok {
- return nil, store.ErrNotFound
- }
- copy := draft
- copy.GlobalDataJSON = cloneRaw(draft.GlobalDataJSON)
- copy.FieldValuesJSON = cloneRaw(draft.FieldValuesJSON)
- copy.DraftContextJSON = cloneRaw(draft.DraftContextJSON)
- copy.SuggestionStateJSON = cloneRaw(draft.SuggestionStateJSON)
- return ©, nil
- }
-
- func (s *Store) ListDrafts(_ context.Context, limit int) ([]domain.BuildDraft, error) {
- s.mu.RLock()
- defer s.mu.RUnlock()
- out := make([]domain.BuildDraft, 0, len(s.drafts))
- for _, draft := range s.drafts {
- copy := draft
- copy.GlobalDataJSON = cloneRaw(draft.GlobalDataJSON)
- copy.FieldValuesJSON = cloneRaw(draft.FieldValuesJSON)
- copy.DraftContextJSON = cloneRaw(draft.DraftContextJSON)
- copy.SuggestionStateJSON = cloneRaw(draft.SuggestionStateJSON)
- out = append(out, copy)
- }
- sort.Slice(out, func(i, j int) bool {
- return out[i].UpdatedAt.After(out[j].UpdatedAt)
- })
- if limit > 0 && len(out) > limit {
- out = out[:limit]
- }
- return out, nil
- }
|