185 lines
5.6 KiB
TypeScript
185 lines
5.6 KiB
TypeScript
// prisma/seed.ts
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import * as dotenv from 'dotenv';
|
|
import { hash } from 'bcryptjs';
|
|
|
|
// Load environment variables BEFORE importing PrismaClient
|
|
// This ensures DATABASE_URL is available when PrismaClient is instantiated
|
|
const envPaths = [
|
|
path.resolve(process.cwd(), '.env.local'),
|
|
path.resolve(process.cwd(), '.env'),
|
|
path.resolve(__dirname, '..', '.env.local'),
|
|
path.resolve(__dirname, '..', '.env'),
|
|
];
|
|
|
|
let envLoaded = false;
|
|
for (const envPath of envPaths) {
|
|
if (fs.existsSync(envPath)) {
|
|
dotenv.config({ path: envPath });
|
|
envLoaded = true;
|
|
console.log(`📁 Loaded environment from: ${envPath}`);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!envLoaded) {
|
|
console.warn(`⚠️ No .env file found. Checked: ${envPaths.join(', ')}`);
|
|
}
|
|
|
|
const databaseUrl = process.env.RUNTIME_DATABASE_URL || process.env.DATABASE_URL;
|
|
|
|
// Verify DATABASE_URL is set
|
|
if (!databaseUrl) {
|
|
throw new Error(
|
|
'DATABASE_URL environment variable is required. Please set it in .env.local\n' +
|
|
`Checked paths: ${envPaths.join(', ')}\n` +
|
|
`Current working directory: ${process.cwd()}`
|
|
);
|
|
}
|
|
|
|
console.log(`🔗 Using database: ${databaseUrl.replace(/:[^:@]+@/, ':****@')}`);
|
|
|
|
// Import PrismaClient AFTER environment is loaded
|
|
import { PrismaClient } from '@prisma/client';
|
|
|
|
// Try to use PrismaPg adapter if available (same pattern as db.ts)
|
|
let prismaAdapter: any = undefined;
|
|
try {
|
|
const { PrismaPg } = require('@prisma/adapter-pg');
|
|
const { Pool } = require('pg');
|
|
// Use same pool configuration as src/lib/db.ts for consistency
|
|
const pool = new Pool({
|
|
connectionString: databaseUrl,
|
|
max: Number(process.env.PG_POOL_MAX || 10),
|
|
idleTimeoutMillis: Number(process.env.PG_POOL_IDLE_TIMEOUT || 30000),
|
|
connectionTimeoutMillis: Number(process.env.PG_POOL_CONN_TIMEOUT || 2000),
|
|
});
|
|
prismaAdapter = new PrismaPg(pool);
|
|
console.log('✅ Using PrismaPg adapter with connection pooling');
|
|
} catch (error) {
|
|
console.log('⚠️ PrismaPg adapter not available, using default client');
|
|
}
|
|
|
|
// Initialize PrismaClient with adapter if available, otherwise use standard connection
|
|
// Note: Prisma 7.x adapter is passed via connection string, not constructor option
|
|
const prisma = new PrismaClient({
|
|
log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'],
|
|
...(prismaAdapter && { adapter: prismaAdapter }),
|
|
});
|
|
|
|
type GuestRow = { inviteCode: string; name: string; maxPax: number };
|
|
|
|
function parseCsv(text: string): GuestRow[] {
|
|
const [headerLine, ...lines] = text.trim().split(/\r?\n/);
|
|
const headers = headerLine.split(',').map(h => h.trim());
|
|
const idx = {
|
|
inviteCode: headers.indexOf('inviteCode'),
|
|
name: headers.indexOf('name'),
|
|
maxPax: headers.indexOf('maxPax'),
|
|
};
|
|
if (idx.inviteCode === -1 || idx.name === -1) {
|
|
throw new Error(`CSV must contain 'inviteCode' and 'name' headers`);
|
|
}
|
|
|
|
const rows: GuestRow[] = [];
|
|
for (const line of lines) {
|
|
if (!line.trim()) continue;
|
|
const cols = line.split(',').map(c => c.trim().replace(/^"|"$/g, '')); // Remove quotes
|
|
const inviteCode = (cols[idx.inviteCode] || '').trim().toUpperCase();
|
|
const name = (cols[idx.name] || '').trim().replace(/\s+/g, ' ');
|
|
const maxPaxRaw = idx.maxPax >= 0 ? (cols[idx.maxPax] || '').trim() : '';
|
|
const maxPax: number = Number.isFinite(+maxPaxRaw) && +maxPaxRaw > 0 ? Number(maxPaxRaw) : 1;
|
|
|
|
if (!/^[A-Z0-9]{6}$/.test(inviteCode)) {
|
|
throw new Error(`Invalid inviteCode '${inviteCode}' (must be 6 A-Z/0-9)`);
|
|
}
|
|
if (!name) throw new Error(`Missing name for inviteCode '${inviteCode}'`);
|
|
rows.push({ inviteCode, name, maxPax });
|
|
}
|
|
return rows;
|
|
}
|
|
|
|
async function main() {
|
|
console.log('🌱 Starting database seed...');
|
|
|
|
// Seed guests from CSV
|
|
const csvPath = path.resolve('data/guests.csv');
|
|
if (!fs.existsSync(csvPath)) {
|
|
throw new Error(`Cannot find ${csvPath}`);
|
|
}
|
|
|
|
const csv = fs.readFileSync(csvPath, 'utf8');
|
|
const rows = parseCsv(csv);
|
|
|
|
const seen = new Set<string>();
|
|
for (const r of rows) {
|
|
if (seen.has(r.inviteCode)) {
|
|
throw new Error(`Duplicate inviteCode in CSV: ${r.inviteCode}`);
|
|
}
|
|
seen.add(r.inviteCode);
|
|
}
|
|
|
|
console.log(`📝 Seeding ${rows.length} guests...`);
|
|
for (const r of rows) {
|
|
await prisma.guest.upsert({
|
|
where: { inviteCode: r.inviteCode },
|
|
update: {
|
|
name: r.name,
|
|
maxPax: r.maxPax,
|
|
},
|
|
create: {
|
|
inviteCode: r.inviteCode,
|
|
name: r.name,
|
|
maxPax: r.maxPax,
|
|
},
|
|
});
|
|
}
|
|
console.log(`✅ Seeded ${rows.length} guests`);
|
|
|
|
// Seed admin user
|
|
const adminPassword = process.env.ADMIN_PASSWORD || 'admin123';
|
|
const hashedPassword = await hash(adminPassword, 10);
|
|
|
|
await prisma.admin.upsert({
|
|
where: { username: 'admin' },
|
|
update: {},
|
|
create: {
|
|
username: 'admin',
|
|
password: hashedPassword,
|
|
email: 'admin@themoyos.co.za',
|
|
},
|
|
});
|
|
console.log('✅ Seeded admin user (username: admin)');
|
|
|
|
// Seed sample tables
|
|
const tables = [
|
|
{ name: 'Table 1', capacity: 8 },
|
|
{ name: 'Table 2', capacity: 8 },
|
|
{ name: 'Table 3', capacity: 10 },
|
|
{ name: 'Table 4', capacity: 10 },
|
|
{ name: 'Table 5', capacity: 6 },
|
|
];
|
|
|
|
console.log('📝 Seeding tables...');
|
|
for (const table of tables) {
|
|
await prisma.table.upsert({
|
|
where: { name: table.name },
|
|
update: {},
|
|
create: table,
|
|
});
|
|
}
|
|
console.log(`✅ Seeded ${tables.length} tables`);
|
|
|
|
console.log('🎉 Database seed complete!');
|
|
}
|
|
|
|
main()
|
|
.catch((e) => {
|
|
console.error('❌ Seed failed:', e);
|
|
process.exit(1);
|
|
})
|
|
.finally(async () => {
|
|
await prisma.$disconnect();
|
|
});
|