/* eslint-disable */ /** * Migration: add an index on `quotes.status`. * * Why this exists * ─────────────── * The /quotes page's tab badges ("New (4)", "Completed (7,849)", * "Email Failed (57)") and its server-side tab filter both run * queries of the form `status = "emailed" || status = "approved" || ...` * on the `quotes` collection. With ~8.5k quotes and no `status` index, * every load is a full table scan — the 22 separate per-status count * queries in the old `getQuoteStatusCounts` were 22 scans, costing * ~2.6s wall time on every page load. The new combined-counts query * collapses to 1 scan (~150ms measured on the live instance), but * an index turns even that 1 scan into a sub-100ms B-tree lookup. * * Schema * ────── * quotes.status select (existing field, no schema change) * * The index is non-unique (many rows share each status). * * Idempotent — safe to re-run. Checks for the index before adding * it; subsequent runs print "already present, skipping." * * Run with: * npx tsx scripts/migrations/2026-08-04-add-quotes-status-index.ts */ import PocketBase from "pocketbase"; import { readFileSync } from "node:fs"; import { resolve } from "node:path"; function loadEnvFile(name: string) { try { const raw = readFileSync(resolve(process.cwd(), name), "utf8"); for (const line of raw.split("\n")) { const trimmed = line.trim(); if (!trimmed || trimmed.startsWith("#")) continue; const m = trimmed.match(/^([A-Z0-9_]+)\s*=\s*(.+)$/i); if (m && !process.env[m[1]]) { process.env[m[1]] = m[2].replace(/^["']|["']$/g, "").trim(); } } } catch { // missing file — fine } } loadEnvFile(".env"); loadEnvFile(".env.local"); async function main() { const url = process.env.NEXT_PUBLIC_POCKETBASE_URL; const email = process.env.PB_ADMIN_EMAIL; const password = process.env.PB_ADMIN_PASSWORD; if (!url || !email || !password) { throw new Error( "Missing NEXT_PUBLIC_POCKETBASE_URL / PB_ADMIN_EMAIL / PB_ADMIN_PASSWORD in env", ); } const pb = new PocketBase(url); await pb.admins.authWithPassword(email, password); const collection = await pb.collections.getOne("quotes"); const INDEX_NAME = "idx_quotes_status"; const INDEX_SQL = `CREATE INDEX \`${INDEX_NAME}\` ON \`quotes\` (\`status\`)`; const existing = (collection.indexes || []) as string[]; if (existing.includes(INDEX_SQL)) { console.log("quotes.status index already present, skipping."); return; } await pb.collections.update(collection.id, { indexes: [...existing, INDEX_SQL], }); console.log("Added quotes.status index (idx_quotes_status)."); console.log("Run /quotes in the browser — the initial load should be visibly faster."); } main().catch((err) => { console.error(err); process.exit(1); });