// Command repair-category-prompts rewrites A1 / Platform Demo categories.prompt // values into aiprompts role-sectioned overlays (Title / Description / Meta / // Attributes). Prefers wp_product_categories.sql (SEED_A1_WP_CATEGORIES / // scripts/seed) as source of truth, else a1-category-prompts.json. Legacy combined // Slovenian name+description blobs are split so naming rules land under Title // and HTML under Description; otherwise CategoryEnhanceUserTemplate is used. // Also repairs empty or brand-only title_template and empty description_template // from legacy / HTML / meta rules (never brand-only). // // Usage (from apps/api): // // go run ./cmd/repair-category-prompts -dry-run // go run ./cmd/repair-category-prompts -apply // go run ./cmd/repair-category-prompts -apply -wp-categories ../../scripts/seed/wp_product_categories.sql // go run ./cmd/repair-category-prompts -apply -prompts ../../scripts/seed/a1-category-prompts.json // // DATABASE_URL / -postgres required. Default is dry-run (count only). package main import ( "context" "encoding/json" "flag" "fmt" "log" "os" "strings" "time" "github.com/descrybe/descrybe-v2/apps/api/internal/catalog" "github.com/jackc/pgx/v5/pgxpool" ) func main() { postgresURL := flag.String("postgres", os.Getenv("DATABASE_URL"), "Postgres URL (DATABASE_URL)") dryRun := flag.Bool("dry-run", true, "count rows that would update (default true)") apply := flag.Bool("apply", false, "write updates (implies not dry-run)") promptsPath := flag.String("prompts", "", "path to a1-category-prompts.json (optional fallback)") wpPath := flag.String("wp-categories", os.Getenv("SEED_A1_WP_CATEGORIES"), "path to wp_product_categories.sql (preferred source of truth)") flag.Parse() if strings.TrimSpace(*postgresURL) == "" { log.Fatal("-postgres / DATABASE_URL is required") } doDryRun := *dryRun if *apply { doDryRun = false } ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) defer cancel() pg, err := pgxpool.New(ctx, *postgresURL) if err != nil { log.Fatalf("postgres: %v", err) } defer pg.Close() opts := catalog.RepairA1DemoOptions{ SeedPromptsPath: strings.TrimSpace(*promptsPath), WPCategoriesPath: strings.TrimSpace(*wpPath), } // Always print dry-run counts first when applying, so operators see the delta. preview, err := catalog.RepairA1DemoCategoryEnhancePromptsWithOptions(ctx, pg, true, opts) if err != nil { log.Fatalf("dry-run: %v", err) } printResult("dry-run", preview) if doDryRun { return } res, err := catalog.RepairA1DemoCategoryEnhancePromptsWithOptions(ctx, pg, false, opts) if err != nil { log.Fatalf("apply: %v", err) } printResult("apply", res) } func printResult(phase string, res catalog.RepairCategoryEnhancePromptsResult) { b, err := json.MarshalIndent(res, "", " ") if err != nil { log.Printf("%s: %+v", phase, res) return } fmt.Printf("%s:\n%s\n", phase, string(b)) }