// Clears ride history so testing can start from a clean slate. // // Deletes every ride and everything hanging off one — offers, chat messages, // calls, ratings — plus the payment orders that funded them. Accounts are left // alone on purpose: wiping users would sign every test phone out and send the // driver back through vetting, which is rarely what you want when you just // need the numbers back at zero. // // Usage: // node scripts/reset-rides.mjs --yes clear ride history // node scripts/reset-rides.mjs dry run, shows what would go // // This is destructive and has no undo, so it refuses to run without --yes. import pg from "pg"; import { readFileSync } from "fs"; const env = readFileSync(new URL("../.env", import.meta.url), "utf8"); const databaseUrl = env .split("\n") .find((l) => l.startsWith("DATABASE_URL=")) ?.split("=") .slice(1) .join("=") .trim() .replace(/^"|"$/g, ""); if (!databaseUrl) { console.error("DATABASE_URL not found in .env"); process.exit(1); } const confirmed = process.argv.includes("--yes"); // Neon and other serverless Postgres can take a while to wake; without a // timeout the script hangs forever on a cold database instead of saying so. const pool = new pg.Pool({ connectionString: databaseUrl, connectionTimeoutMillis: 30_000, }); // Child tables first. Most cascade from rides anyway, but deleting explicitly // keeps the counts honest and doesn't rely on every FK being ON DELETE CASCADE. const TABLES = [ "ride_ratings", "messages", "calls", "ride_offers", "rides", "payment_orders", ]; // Kept, and why — printed so the blast radius is never a surprise. const KEPT = [ ["users", "sign-ins stay valid on every test device"], ["drivers", "profile and approval survive, so a driver can go online at once"], ["push_tokens", "devices stay registered for notifications"], ]; const counts = async () => { const out = {}; for (const table of [...TABLES, "users", "drivers"]) { const { rows } = await pool.query(`SELECT COUNT(*)::int AS n FROM ${table}`); out[table] = rows[0].n; } return out; }; const before = await counts(); console.log("Current contents:"); for (const table of TABLES) { console.log(` ${table.padEnd(16)} ${String(before[table]).padStart(5)}`); } console.log("\nWould be kept:"); for (const [table, why] of KEPT) { const n = before[table]; console.log(` ${table.padEnd(16)} ${n === undefined ? "" : String(n).padStart(5)} ${why}`); } if (!confirmed) { console.log("\nDry run. Re-run with --yes to actually delete."); await pool.end(); process.exit(0); } const client = await pool.connect(); try { // One transaction: a partial wipe would leave orphaned messages or offers // pointing at rides that no longer exist. await client.query("BEGIN"); for (const table of TABLES) { const res = await client.query(`DELETE FROM ${table}`); console.log(`\nDeleted ${res.rowCount} from ${table}`); } // Restart the id sequences so a fresh run starts at ride #1 rather than // continuing from wherever the old data left off. Guarded by to_regclass so // a missing sequence can't fail the whole reset. for (const seq of ["rides_ride_id_seq", "ride_offers_id_seq"]) { await client.query( `SELECT CASE WHEN to_regclass($1) IS NOT NULL THEN setval($1, 1, false) END`, [seq], ); } await client.query("COMMIT"); } catch (error) { await client.query("ROLLBACK"); console.error("\nReset failed, nothing was deleted:", error.message); client.release(); await pool.end(); process.exit(1); } finally { client.release(); } const after = await counts(); console.log("\nAfter:"); for (const table of [...TABLES, "users", "drivers"]) { console.log(` ${table.padEnd(16)} ${String(after[table]).padStart(5)}`); } console.log("\nRide history cleared. Accounts and driver profiles untouched."); await pool.end();