Files
2026-01-16 19:04:48 +02:00

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();
});