package templatesvc import ( "context" "encoding/json" "fmt" "sort" "strings" "qctextbuilder/internal/domain" "qctextbuilder/internal/qcclient" "qctextbuilder/internal/store" ) type Service struct { qc qcclient.Client templateStore store.TemplateStore manifestStore store.ManifestStore } type TemplateDetail struct { Template *domain.Template `json:"template"` Manifest *domain.TemplateManifest `json:"manifest,omitempty"` Fields []domain.TemplateField `json:"fields"` } func New(qc qcclient.Client, templateStore store.TemplateStore, manifestStore store.ManifestStore) *Service { return &Service{ qc: qc, templateStore: templateStore, manifestStore: manifestStore, } } func (s *Service) SyncAITemplates(ctx context.Context) ([]domain.Template, error) { templates, err := s.qc.ListAITemplates(ctx) if err != nil { return nil, err } out := make([]domain.Template, 0, len(templates)) for _, t := range templates { raw, _ := json.Marshal(t) out = append(out, domain.Template{ ID: t.ID, Name: t.Name, Description: t.Description, Locale: strings.ToUpper(t.Locale), ThumbnailURL: t.ThumbnailURL, TemplatePreviewURL: t.TemplatePreviewURL, Type: t.Type, PaletteReady: t.PaletteReady.Bool(), RawJSON: raw, IsAITemplate: strings.EqualFold(t.Type, "ai"), ManifestStatus: "missing", }) } if err := s.templateStore.UpsertTemplates(ctx, out); err != nil { return nil, err } return out, nil } func (s *Service) ListTemplates(ctx context.Context) ([]domain.Template, error) { templates, err := s.templateStore.ListTemplates(ctx) if err != nil { return nil, fmt.Errorf("list templates: %w", err) } sort.Slice(templates, func(i, j int) bool { if templates[i].Name == templates[j].Name { return templates[i].ID < templates[j].ID } return templates[i].Name < templates[j].Name }) return templates, nil } func (s *Service) GetTemplateDetail(ctx context.Context, templateID int64) (*TemplateDetail, error) { template, err := s.templateStore.GetTemplateByID(ctx, templateID) if err != nil { return nil, fmt.Errorf("get template: %w", err) } detail := &TemplateDetail{ Template: template, Fields: []domain.TemplateField{}, } manifest, err := s.manifestStore.GetActiveManifestByTemplateID(ctx, templateID) if err != nil { if isNotFoundErr(err) { return detail, nil } return nil, fmt.Errorf("get active manifest: %w", err) } fields, err := s.manifestStore.ListFieldsByManifestID(ctx, manifest.ID) if err != nil { return nil, fmt.Errorf("list fields: %w", err) } sort.Slice(fields, func(i, j int) bool { if fields[i].DisplayOrder == fields[j].DisplayOrder { return fields[i].Path < fields[j].Path } return fields[i].DisplayOrder < fields[j].DisplayOrder }) detail.Manifest = manifest detail.Fields = fields return detail, nil } func isNotFoundErr(err error) bool { return strings.Contains(strings.ToLower(strings.TrimSpace(err.Error())), "not found") }