fixes
This commit is contained in:
@@ -0,0 +1,220 @@
|
||||
package catalog
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/descrybe/descrybe-v2/apps/api/internal/aiprompts"
|
||||
"github.com/descrybe/descrybe-v2/apps/api/internal/billing"
|
||||
"github.com/descrybe/descrybe-v2/apps/api/internal/company"
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
// Canonical Postgres id for the migrated A1 Slovenija tenant (seed-a1 default).
|
||||
const a1CompanyID = "604f23a8-b66e-4b21-8b45-0d72b68f4790"
|
||||
|
||||
// platformDemoCompanyName matches migrator / seed-demo standalone demo tenant.
|
||||
const platformDemoCompanyName = "Platform Demo"
|
||||
|
||||
// RepairCategoryEnhancePromptsResult is the dry-run / apply summary for
|
||||
// RepairA1DemoCategoryEnhancePrompts.
|
||||
type RepairCategoryEnhancePromptsResult struct {
|
||||
CompaniesScanned int `json:"companies_scanned"`
|
||||
CategoriesSeen int `json:"categories_seen"`
|
||||
WouldUpdate int `json:"would_update"`
|
||||
Updated int `json:"updated"`
|
||||
AlreadyOK int `json:"already_ok"`
|
||||
EmptySkipped int `json:"empty_skipped"`
|
||||
ByCompany map[string]int `json:"by_company"`
|
||||
DryRun bool `json:"dry_run"`
|
||||
}
|
||||
|
||||
// RepairA1DemoCategoryEnhancePrompts replaces legacy HTML marketing categories.prompt
|
||||
// values for A1 Slovenija + Platform Demo with aiprompts.CategoryEnhanceUserTemplate.
|
||||
//
|
||||
// LOCAL repair only (idempotent):
|
||||
// - Writes JSON-compatible user overlays under both "sl" (prompt->>'sl') and "*"
|
||||
// (LangPromptAny) so language stays via {{language}}, not hardcoded-only copy.
|
||||
// - Touches ONLY categories.prompt — never title_template / description_template
|
||||
// (unique name/description formulas stay intact; AppendFormulaConstraints encodes
|
||||
// them as plain-text instructions at enhance render time).
|
||||
// - dryRun=true: count WouldUpdate only; dryRun=false: apply and set Updated.
|
||||
//
|
||||
// Entrypoint: go run ./cmd/repair-category-prompts (-dry-run | -apply).
|
||||
func RepairA1DemoCategoryEnhancePrompts(ctx context.Context, pool *pgxpool.Pool, dryRun bool) (RepairCategoryEnhancePromptsResult, error) {
|
||||
out := RepairCategoryEnhancePromptsResult{
|
||||
DryRun: dryRun,
|
||||
ByCompany: map[string]int{},
|
||||
}
|
||||
if pool == nil {
|
||||
return out, fmt.Errorf("postgres pool is required")
|
||||
}
|
||||
|
||||
a1ID, err := uuid.Parse(a1CompanyID)
|
||||
if err != nil {
|
||||
return out, fmt.Errorf("a1 company id: %w", err)
|
||||
}
|
||||
|
||||
companyRows, err := pool.Query(ctx, `
|
||||
SELECT id, name
|
||||
FROM companies
|
||||
WHERE id = $1
|
||||
OR name = $2
|
||||
OR COALESCE(legacy_company_id, '') = $3
|
||||
ORDER BY name`, a1ID, platformDemoCompanyName, billing.A1LegacyCompanyID)
|
||||
if err != nil {
|
||||
return out, fmt.Errorf("list target companies: %w", err)
|
||||
}
|
||||
defer companyRows.Close()
|
||||
|
||||
type co struct {
|
||||
id uuid.UUID
|
||||
name string
|
||||
}
|
||||
companies := make([]co, 0, 2)
|
||||
for companyRows.Next() {
|
||||
var c co
|
||||
if err := companyRows.Scan(&c.id, &c.name); err != nil {
|
||||
return out, err
|
||||
}
|
||||
companies = append(companies, c)
|
||||
}
|
||||
if err := companyRows.Err(); err != nil {
|
||||
return out, err
|
||||
}
|
||||
out.CompaniesScanned = len(companies)
|
||||
if len(companies) == 0 {
|
||||
return out, fmt.Errorf("no A1 / Platform Demo companies found")
|
||||
}
|
||||
|
||||
want, err := repairedCategoryEnhancePromptMap()
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
|
||||
for _, c := range companies {
|
||||
n, err := repairCompanyCategoryEnhancePrompts(ctx, pool, c.id, c.name, want, dryRun, &out)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
if n > 0 {
|
||||
out.ByCompany[c.name] = n
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// repairedCategoryEnhancePromptMap is the canonical stored shape: "sl" (prompt->>'sl'
|
||||
// for A1/Demo) plus LangPromptAny ("*") so PromptForLanguage resolves for any content
|
||||
// language. Copy stays language-agnostic via {{language}} — not hardcoded Slovenian.
|
||||
func repairedCategoryEnhancePromptMap() (company.LangPromptMap, error) {
|
||||
tpl := strings.TrimSpace(aiprompts.CategoryEnhanceUserTemplate)
|
||||
if tpl == "" {
|
||||
return nil, fmt.Errorf("CategoryEnhanceUserTemplate is empty")
|
||||
}
|
||||
return company.LangPromptMap{
|
||||
"sl": tpl,
|
||||
company.LangPromptAny: tpl,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func categoryEnhancePromptMapOK(m company.LangPromptMap, want company.LangPromptMap) bool {
|
||||
if !company.HasAnyPrompt(m) || !company.HasAnyPrompt(want) {
|
||||
return false
|
||||
}
|
||||
tpl := strings.TrimSpace(want[company.LangPromptAny])
|
||||
if tpl == "" {
|
||||
tpl = strings.TrimSpace(want["sl"])
|
||||
}
|
||||
if tpl == "" {
|
||||
return false
|
||||
}
|
||||
// Accept already-repaired maps: every non-empty value equals the shared template
|
||||
// and LangPromptAny (or legacy sl-only) is present.
|
||||
hasKey := false
|
||||
for lang, p := range m {
|
||||
p = strings.TrimSpace(p)
|
||||
if p == "" {
|
||||
continue
|
||||
}
|
||||
if p != tpl {
|
||||
return false
|
||||
}
|
||||
if lang == company.LangPromptAny || lang == "sl" {
|
||||
hasKey = true
|
||||
}
|
||||
}
|
||||
return hasKey
|
||||
}
|
||||
|
||||
func repairCompanyCategoryEnhancePrompts(
|
||||
ctx context.Context,
|
||||
pool *pgxpool.Pool,
|
||||
companyID uuid.UUID,
|
||||
companyName string,
|
||||
want company.LangPromptMap,
|
||||
dryRun bool,
|
||||
out *RepairCategoryEnhancePromptsResult,
|
||||
) (int, error) {
|
||||
rows, err := pool.Query(ctx, `
|
||||
SELECT id, COALESCE(prompt, '{}'::jsonb)
|
||||
FROM categories
|
||||
WHERE company_id = $1`, companyID)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("list categories for %s: %w", companyName, err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
updatedHere := 0
|
||||
for rows.Next() {
|
||||
var id uuid.UUID
|
||||
var raw []byte
|
||||
if err := rows.Scan(&id, &raw); err != nil {
|
||||
return updatedHere, err
|
||||
}
|
||||
out.CategoriesSeen++
|
||||
|
||||
m, err := company.DecodeLangPromptMap(raw)
|
||||
if err != nil {
|
||||
return updatedHere, fmt.Errorf("decode prompt category=%s company=%s: %w", id, companyName, err)
|
||||
}
|
||||
if !company.HasAnyPrompt(m) {
|
||||
out.EmptySkipped++
|
||||
continue
|
||||
}
|
||||
if categoryEnhancePromptMapOK(m, want) {
|
||||
out.AlreadyOK++
|
||||
continue
|
||||
}
|
||||
|
||||
out.WouldUpdate++
|
||||
if dryRun {
|
||||
updatedHere++
|
||||
continue
|
||||
}
|
||||
|
||||
cleaned, err := company.SanitizeLangPromptMap(want, MaxCategoryPromptRunes)
|
||||
if err != nil {
|
||||
return updatedHere, fmt.Errorf("sanitize prompt category=%s: %w", id, err)
|
||||
}
|
||||
encoded, err := company.EncodeLangPromptMap(cleaned)
|
||||
if err != nil {
|
||||
return updatedHere, err
|
||||
}
|
||||
ct, err := pool.Exec(ctx, `
|
||||
UPDATE categories
|
||||
SET prompt = $3::jsonb, updated_at = now()
|
||||
WHERE id = $1 AND company_id = $2`, id, companyID, string(encoded))
|
||||
if err != nil {
|
||||
return updatedHere, fmt.Errorf("update prompt category=%s: %w", id, err)
|
||||
}
|
||||
if ct.RowsAffected() == 0 {
|
||||
return updatedHere, fmt.Errorf("category %s not updated (company mismatch?)", id)
|
||||
}
|
||||
out.Updated++
|
||||
updatedHere++
|
||||
}
|
||||
return updatedHere, rows.Err()
|
||||
}
|
||||
Reference in New Issue
Block a user