This commit is contained in:
2026-08-23 20:49:40 +02:00
parent 3b678ca857
commit f3d4fb56ed
31 changed files with 3608 additions and 111 deletions
@@ -0,0 +1,86 @@
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
}