package catalog import ( "context" "github.com/descrybe/descrybe-v2/apps/api/internal/aiprompts" "github.com/descrybe/descrybe-v2/apps/api/internal/company" "github.com/google/uuid" "github.com/jackc/pgx/v5/pgconn" ) // DefaultFormulaTarget is one freshly created category to seed formulas onto. type DefaultFormulaTarget struct { ID uuid.UUID UniqueID string Name string } // pgxExecer is the subset of pgxpool.Pool / pgx.Tx used here so the helper works // inside the CSV import transaction as well as on the plain pool. type pgxExecer interface { Exec(ctx context.Context, sql string, args ...any) (pgconn.CommandTag, error) } // applyDefaultCategoryFormulasSQL fills prompt / title_template / description_template // on newly created categories. Only empty columns are written, so a tenant that // already authored a formula (or an A1 category carrying its Slovenian legacy // prompt) is never overwritten. const applyDefaultCategoryFormulasSQL = ` UPDATE categories AS c SET prompt = CASE WHEN c.prompt IS NULL OR c.prompt = '{}'::jsonb THEN v.prompt::jsonb ELSE c.prompt END, title_template = CASE WHEN c.title_template IS NULL OR c.title_template::text IN ('null', '{}') THEN NULLIF(v.title_template, '')::jsonb ELSE c.title_template END, description_template = CASE WHEN c.description_template IS NULL OR c.description_template::text IN ('null', '{}') THEN NULLIF(v.description_template, '')::jsonb ELSE c.description_template END, updated_at = now() FROM ( SELECT * FROM unnest($2::uuid[], $3::text[], $4::text[], $5::text[]) AS t(id, prompt, title_template, description_template) ) AS v WHERE c.id = v.id AND c.company_id = $1` // ApplyDefaultCategoryFormulas gives newly created categories the platform-default // formulas (English, same style as the A1 legacy templates) when they have none. // // New categories get a working title/description/meta formula out of the box // instead of falling through to generic "1-3 paragraphs" copy. Existing rows are // untouched — this only runs at create time. A1 categories keep their Slovenian // prompts because seed-a1 writes them and the SQL never overwrites a set column. func ApplyDefaultCategoryFormulas(ctx context.Context, db pgxExecer, companyID uuid.UUID, targets ...DefaultFormulaTarget) error { if db == nil || len(targets) == 0 { return nil } ids := make([]uuid.UUID, 0, len(targets)) prompts := make([]string, 0, len(targets)) titles := make([]string, 0, len(targets)) descs := make([]string, 0, len(targets)) for _, t := range targets { if t.ID == uuid.Nil { continue } def := aiprompts.DefaultCategoryFormula(t.UniqueID, t.Name) if def.Overlay == "" { continue } // "*" applies to every content language: the formula text is English while // {{language}} still selects the output language at render time. promptJSON, err := company.EncodeLangPromptMap(company.LangPromptMap{"*": def.Overlay}) if err != nil { return err } ids = append(ids, t.ID) prompts = append(prompts, string(promptJSON)) titles = append(titles, def.TitleTemplateJSON) descs = append(descs, def.DescriptionTemplateJSON) } if len(ids) == 0 { return nil } _, err := db.Exec(ctx, applyDefaultCategoryFormulasSQL, companyID, ids, prompts, titles, descs) return err }