7cd0603cfb
BREAKING: dev_session cookie bypass removed. Admin access now requires a real Auth.js v5 session (Google OAuth in production). Provision users by inserting into users + tenant_users tables. New in this commit: - db/migrations/0001_init.sql: 18-table SaaS schema with RLS (tenants, users, tenant_users, plans, add_ons, subscriptions, tenant_add_ons, products, product_images, stops, customers, orders, order_items, brand_settings, email_templates, campaigns, files, audit_log) - db/schema/: Drizzle TypeScript mirror of every table - db/client.ts: withTenant() / withPlatformAdmin() query wrappers that set Postgres GUCs (app.current_tenant_id, app.platform_admin) for RLS enforcement. Never query a tenant-scoped table without one. - db/seed.ts: seeds 3 plans, 6 add-ons, 2 tenants (Tuxedo, Indian River Direct), brand_settings, sample products/stops/customers - scripts/migrate.js: applies migrations in lexical order with tracking - scripts/db-reset.js: drops + recreates DB, runs migrate + seed - DATABASE_URL now uses rc_app (non-superuser, NOBYPASSRLS). RLS is enforced even for the app user. DATABASE_ADMIN_URL for migrations. - src/lib/admin-permissions.ts: getAdminUser() reads Auth.js session, looks up user + tenant in Postgres. brand_id kept as alias for backward compat. - src/middleware.ts: Auth.js-only route protection, dev_session gone - src/app/login/LoginClient.tsx: Google OAuth only, no demo mode - src/components/admin/AdminSidebar.tsx + AdminHeader.tsx: signOutAction replaces supabase signout - @/db/* path aliases in tsconfig.json + vitest.config.ts - drizzle.config.ts added - db/auth_schema.sql removed (was a stub; replaced by real schema) - src/app/api/dev-login/route.ts deleted - tests: updated to remove dev_session coverage
92 lines
2.4 KiB
JavaScript
92 lines
2.4 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Apply Postgres migrations from `db/migrations/*.sql` in lexical order.
|
|
* Wraps the whole thing in a transaction; tracks applied files in
|
|
* `_migrations` so re-runs are safe.
|
|
*
|
|
* Usage:
|
|
* npm run db:migrate
|
|
*
|
|
* Replaces the old `supabase/push-migrations.js` — that script was
|
|
* hardcoded to a Supabase URL. This one reads `DATABASE_URL` directly.
|
|
*/
|
|
require("dotenv").config({ path: ".env.local" });
|
|
|
|
const fs = require("node:fs");
|
|
const path = require("node:path");
|
|
const { Client } = require("pg");
|
|
|
|
const MIGRATIONS_DIR = path.join(__dirname, "..", "db", "migrations");
|
|
|
|
async function main() {
|
|
const url = process.env.DATABASE_ADMIN_URL ?? process.env.DATABASE_URL;
|
|
if (!url) {
|
|
console.error("❌ DATABASE_URL (or DATABASE_ADMIN_URL) is not set in .env.local");
|
|
process.exit(1);
|
|
}
|
|
|
|
const client = new Client({ connectionString: url });
|
|
await client.connect();
|
|
|
|
try {
|
|
await client.query(`
|
|
CREATE TABLE IF NOT EXISTS _migrations (
|
|
filename TEXT PRIMARY KEY,
|
|
applied_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
)
|
|
`);
|
|
|
|
const files = fs
|
|
.readdirSync(MIGRATIONS_DIR)
|
|
.filter((f) => f.endsWith(".sql"))
|
|
.sort();
|
|
|
|
if (files.length === 0) {
|
|
console.log("No migration files found in db/migrations/");
|
|
return;
|
|
}
|
|
|
|
const { rows: applied } = await client.query(
|
|
`SELECT filename FROM _migrations`,
|
|
);
|
|
const appliedSet = new Set(applied.map((r) => r.filename));
|
|
|
|
let appliedNow = 0;
|
|
for (const file of files) {
|
|
if (appliedSet.has(file)) {
|
|
console.log(`✓ ${file} (already applied)`);
|
|
continue;
|
|
}
|
|
const sql = fs.readFileSync(path.join(MIGRATIONS_DIR, file), "utf8");
|
|
console.log(`→ Applying ${file}...`);
|
|
try {
|
|
await client.query("BEGIN");
|
|
await client.query(sql);
|
|
await client.query(`INSERT INTO _migrations (filename) VALUES ($1)`, [
|
|
file,
|
|
]);
|
|
await client.query("COMMIT");
|
|
appliedNow += 1;
|
|
console.log(`✓ ${file}`);
|
|
} catch (err) {
|
|
await client.query("ROLLBACK");
|
|
console.error(`✗ ${file} failed:`, err.message);
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
console.log(
|
|
`\n✅ Done. ${appliedNow} new migration(s) applied. ${
|
|
files.length - appliedNow
|
|
} already current.`,
|
|
);
|
|
} finally {
|
|
await client.end();
|
|
}
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error(err);
|
|
process.exit(1);
|
|
});
|