/** * One-shot backfill: title-case existing `users.name` rows. * * After this lands, every NEW signup gets canonicalised in the better-auth * `user.create.before` hook (see apps/api/src/auth/auth.ts). Existing rows * pre-date that hook and still carry whatever the user typed at signup: * `mehmet`, `MEHMET`, `İLKER`, `OTO`, …. This script applies the same * `normalizeName()` Turkish-locale-aware title-case to historical rows so * mail subjects (`Sase.tr'ye hoş geldin, mehmet` → `, Mehmet`) and dashboard * greetings render consistently. * * Safe to re-run: the UPDATE is gated on `name <> normalized`, so already- * canonical rows aren't touched. * * Usage: * pnpm tsx scripts/backfill-user-names.ts --dry-run # preview only * pnpm tsx scripts/backfill-user-names.ts # write * * Run against BOTH prod (sase) and dev (sase_dev) DBs separately by pointing * DATABASE_URL at each. mailAudit.md §9.3 #9. */ import * as path from "node:path"; import * as dotenv from "dotenv"; import postgres from "postgres"; import { normalizeName } from "@sase/shared"; dotenv.config({ path: path.join(__dirname, "../apps/api/.env") }); const argv = process.argv.slice(2); const dryRun = argv.includes("--dry-run"); async function main() { if (!process.env.DATABASE_URL) { console.error("DATABASE_URL is not set"); process.exit(1); } const sql = postgres(process.env.DATABASE_URL, { max: 1 }); const rows = await sql<{ id: string; name: string }[]>` SELECT id, name FROM users WHERE name IS NOT NULL AND name <> '' `; console.log(`[backfill] scanned ${rows.length} users`); let changed = 0; let unchanged = 0; const samples: Array<{ before: string; after: string }> = []; for (const r of rows) { const norm = normalizeName(r.name); if (norm === r.name) { unchanged++; continue; } if (samples.length < 15) samples.push({ before: r.name, after: norm }); if (!dryRun) { await sql`UPDATE users SET name = ${norm}, updated_at = NOW() WHERE id = ${r.id}`; } changed++; } console.log(`[backfill] ${changed} changed, ${unchanged} already canonical`); if (samples.length) { console.log("[backfill] sample diffs:"); for (const s of samples) console.log(` '${s.before}' → '${s.after}'`); } if (dryRun) console.log("[backfill] DRY RUN — no writes"); await sql.end(); } main().catch((e) => { console.error(e); process.exit(1); });