2025-12-02 09:54:38 +01:00
|
|
|
import 'dotenv/config';
|
|
|
|
|
|
2026-03-09 11:18:22 +01:00
|
|
|
import { spawn } from 'node:child_process';
|
|
|
|
|
import { dirname, resolve } from 'node:path';
|
|
|
|
|
import { fileURLToPath } from 'node:url';
|
2025-12-02 09:54:38 +01:00
|
|
|
import postgres from 'postgres';
|
|
|
|
|
|
|
|
|
|
const connectionString = process.env.DATABASE_URL ?? process.env.PRIVATE_DATABASE_URL ?? '';
|
2026-03-09 11:18:22 +01:00
|
|
|
const shouldSeed = process.argv.includes('--seed') || process.argv.includes('-s');
|
|
|
|
|
const shouldSyncSchema = !process.argv.includes('--skip-schema-sync');
|
|
|
|
|
const scriptPath = fileURLToPath(import.meta.url);
|
|
|
|
|
const packageRoot = resolve(dirname(scriptPath), '../..');
|
|
|
|
|
const pnpmBinary = process.platform === 'win32' ? 'pnpm.cmd' : 'pnpm';
|
2025-12-02 09:54:38 +01:00
|
|
|
|
|
|
|
|
if (!connectionString) {
|
|
|
|
|
console.error('DATABASE_URL or PRIVATE_DATABASE_URL environment variable is required');
|
|
|
|
|
process.exit(1);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-09 11:18:22 +01:00
|
|
|
async function runPnpmCommand(commandLabel: string, args: string[]) {
|
|
|
|
|
await new Promise<void>((resolvePromise, rejectPromise) => {
|
|
|
|
|
const child = spawn(pnpmBinary, args, {
|
|
|
|
|
cwd: packageRoot,
|
|
|
|
|
env: process.env,
|
|
|
|
|
stdio: 'inherit'
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
child.on('error', (error) => {
|
|
|
|
|
rejectPromise(error);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
child.on('exit', (code) => {
|
|
|
|
|
if (code === 0) {
|
|
|
|
|
resolvePromise();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rejectPromise(new Error(`${commandLabel} command failed with exit code ${code ?? 'unknown'}`));
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function runSchemaSync() {
|
|
|
|
|
console.log('Syncing schema with drizzle-kit push...');
|
|
|
|
|
await runPnpmCommand('Schema sync', ['db', 'push']);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function runSeedEssential() {
|
|
|
|
|
console.log('Seeding essential reference data (roles, submissions, question-types)...');
|
|
|
|
|
await runPnpmCommand('Essential seed', ['seed', '--roles', '--submissions', '--question-types']);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function runSeed() {
|
|
|
|
|
console.log('Seeding database...');
|
|
|
|
|
await runPnpmCommand('Seed', ['seed']);
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-02 09:54:38 +01:00
|
|
|
async function dbSetup() {
|
|
|
|
|
const sql = postgres(connectionString);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
console.log('Setting up database roles...');
|
|
|
|
|
// Create authenticated role if it doesn't exist (used in RLS policies)
|
|
|
|
|
const authenticatedRoleExists = await sql`
|
|
|
|
|
SELECT 1 FROM pg_roles WHERE rolname = 'authenticated'
|
|
|
|
|
`;
|
|
|
|
|
if (authenticatedRoleExists.length === 0) {
|
|
|
|
|
await sql`CREATE ROLE authenticated`;
|
|
|
|
|
console.log('✓ authenticated role created');
|
|
|
|
|
} else {
|
|
|
|
|
console.log('✓ authenticated role already exists');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create anon role if it doesn't exist (used in RLS policies)
|
|
|
|
|
const anonRoleExists = await sql`
|
|
|
|
|
SELECT 1 FROM pg_roles WHERE rolname = 'anon'
|
|
|
|
|
`;
|
|
|
|
|
if (anonRoleExists.length === 0) {
|
|
|
|
|
await sql`CREATE ROLE anon`;
|
|
|
|
|
console.log('✓ anon role created');
|
|
|
|
|
} else {
|
|
|
|
|
console.log('✓ anon role already exists');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Note: 'public' role exists by default in PostgreSQL
|
|
|
|
|
|
2026-03-09 11:18:22 +01:00
|
|
|
if (shouldSyncSchema) {
|
|
|
|
|
await runSchemaSync();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await runSeedEssential();
|
|
|
|
|
|
|
|
|
|
if (shouldSeed) {
|
|
|
|
|
await runSeed();
|
|
|
|
|
}
|
2025-12-02 09:54:38 +01:00
|
|
|
} catch (error) {
|
2026-03-09 11:18:22 +01:00
|
|
|
console.error('Failed to setup database:', error);
|
2025-12-02 09:54:38 +01:00
|
|
|
process.exit(1);
|
|
|
|
|
} finally {
|
|
|
|
|
await sql.end();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dbSetup();
|