← back to Grant
scripts/seed-admin-user.mjs
80 lines
#!/usr/bin/env node
/**
* Seed an admin-level user account for the Grant app.
*
* Idempotent: upserts on email. Ties the row to an organization (defaults to
* the oldest org, matching resolveOrgId's deterministic fallback in lib/auth.ts).
*
* Usage:
* node scripts/seed-admin-user.mjs
* ADMIN_EMAIL=admin@studentdebtcrisis.org ADMIN_NAME="Site Admin" ADMIN_ROLE=admin node scripts/seed-admin-user.mjs
* ORG_ID=<uuid> node scripts/seed-admin-user.mjs
*
* Reads DATABASE_URL from the environment (or .env.local).
*/
import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
import pg from 'pg';
const __dirname = dirname(fileURLToPath(import.meta.url));
// Minimal .env.local loader (only if DATABASE_URL not already set).
if (!process.env.DATABASE_URL) {
try {
const env = readFileSync(join(__dirname, '..', '.env.local'), 'utf8');
for (const line of env.split('\n')) {
const m = line.match(/^\s*([A-Z0-9_]+)\s*=\s*(.*)\s*$/);
if (m && !process.env[m[1]]) process.env[m[1]] = m[2].replace(/^["']|["']$/g, '');
}
} catch { /* no .env.local — rely on real env */ }
}
const ADMIN_EMAIL = process.env.ADMIN_EMAIL || 'admin@studentdebtcrisis.org';
const ADMIN_NAME = process.env.ADMIN_NAME || 'Site Admin';
const ADMIN_ROLE = process.env.ADMIN_ROLE || 'admin'; // owner | admin | member
if (!['owner', 'admin', 'member'].includes(ADMIN_ROLE)) {
console.error(`[seed-admin] ADMIN_ROLE must be owner|admin|member, got "${ADMIN_ROLE}"`);
process.exit(1);
}
if (!process.env.DATABASE_URL) {
console.error('[seed-admin] DATABASE_URL not set');
process.exit(1);
}
const { Pool } = pg;
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const run = async () => {
// Resolve target org: explicit ORG_ID, else oldest org.
let orgId = process.env.ORG_ID;
if (!orgId) {
const r = await pool.query('SELECT id FROM organizations ORDER BY created_at ASC LIMIT 1');
if (r.rows.length === 0) {
console.error('[seed-admin] No organizations exist yet — create the org first.');
process.exit(1);
}
orgId = r.rows[0].id;
}
const res = await pool.query(
`INSERT INTO users (email, display_name, role, org_id, last_login_at)
VALUES ($1, $2, $3, $4, NOW())
ON CONFLICT (email) DO UPDATE
SET display_name = EXCLUDED.display_name,
role = EXCLUDED.role,
org_id = EXCLUDED.org_id,
updated_at = NOW()
RETURNING id, email, display_name, role, org_id`,
[ADMIN_EMAIL, ADMIN_NAME, ADMIN_ROLE, orgId],
);
const u = res.rows[0];
console.log(`[seed-admin] ✓ ${u.role} account ready: ${u.email} (${u.display_name})`);
console.log(`[seed-admin] id=${u.id} org_id=${u.org_id}`);
};
run()
.catch((e) => { console.error('[seed-admin] FAILED:', e.message); process.exit(1); })
.finally(() => pool.end());