/** * Provision an admin user by email. * Run: npx tsx scripts/provision-admin.ts [email] [role] * * Role options: platform_admin, brand_admin, store_employee */ import pg from "pg"; import * as fs from "fs"; import * as path from "path"; // Load .env.local manually const envPath = path.join(process.cwd(), ".env.local"); if (fs.existsSync(envPath)) { const envContent = fs.readFileSync(envPath, "utf-8"); for (const line of envContent.split("\n")) { const trimmed = line.trim(); if (trimmed && !trimmed.startsWith("#")) { const [key, ...valueParts] = trimmed.split("="); process.env[key.trim()] = valueParts.join("=").trim(); } } } const { Pool } = pg; async function main() { const email = process.argv[2] ?? "admin@example.com"; const role = process.argv[3] ?? "platform_admin"; console.log("DATABASE_URL:", process.env.DATABASE_URL ? "set" : "NOT SET"); const pool = new Pool({ connectionString: process.env.DATABASE_URL }); try { // Find the Neon Auth user by email const userResult = await pool.query( "SELECT id FROM neon_auth.user WHERE email = $1", [email] ); if (userResult.rows.length === 0) { console.error(`User ${email} not found in neon_auth.user`); console.error("Please sign up first at /login"); process.exit(1); } const userId = userResult.rows[0].id; console.log(`Found Neon Auth user: ${userId} (${email})`); // Check if already in admin_users const existingAdmin = await pool.query( "SELECT id FROM admin_users WHERE user_id = $1", [userId] ); let adminUserId: string; if (existingAdmin.rows.length > 0) { adminUserId = existingAdmin.rows[0].id; console.log(`User already in admin_users: ${adminUserId}`); } else { // Insert into admin_users const insertResult = await pool.query( `INSERT INTO admin_users (user_id, email, name, role, can_manage_orders, can_manage_products, can_manage_stops, can_manage_customers, can_manage_wholesale, can_manage_billing, can_manage_settings, can_manage_water_log, can_manage_time_tracking, can_manage_route_trace, can_manage_reports, can_manage_communications) VALUES ($1, $2, $3, $4, true, true, true, true, true, true, true, true, true, true, true, true) RETURNING id`, [userId, email, email.split("@")[0], role] ); adminUserId = insertResult.rows[0].id; console.log(`Created admin_users record: ${adminUserId}`); } // Get brands const brandsResult = await pool.query("SELECT id, name, slug FROM brands LIMIT 10"); if (brandsResult.rows.length === 0) { console.error("No brands found. Please create a brand first."); process.exit(1); } console.log("\nAvailable brands:"); brandsResult.rows.forEach((b, i) => { console.log(` ${i + 1}. ${b.name} (${b.slug}) - ${b.id}`); }); // Link to first brand (or create a default one) const brand = brandsResult.rows[0]; // Check if already linked const existingLink = await pool.query( "SELECT 1 FROM admin_user_brands WHERE admin_user_id = $1 AND brand_id = $2", [adminUserId, brand.id] ); if (existingLink.rows.length > 0) { console.log(`Already linked to brand: ${brand.name}`); } else { await pool.query( "INSERT INTO admin_user_brands (admin_user_id, brand_id) VALUES ($1, $2)", [adminUserId, brand.id] ); console.log(`Linked to brand: ${brand.name} (${brand.slug})`); } console.log(`\n✅ ${email} is now provisioned as ${role}!`); console.log(` They can access the admin at http://localhost:4000/admin`); } finally { await pool.end(); } } main().catch(console.error);