import { PrismaClient, UserRole, UserStatus, ProgramStatus, RoundStatus, SettingType, SettingCategory, } from '@prisma/client' import bcrypt from 'bcryptjs' const prisma = new PrismaClient() async function main() { console.log('🌱 Seeding database...') // ========================================================================== // Create System Settings // ========================================================================== console.log('📋 Creating system settings...') const settings = [ // AI Settings { key: 'ai_enabled', value: 'false', type: SettingType.BOOLEAN, category: SettingCategory.AI, description: 'Enable AI-powered jury assignment suggestions', }, { key: 'ai_provider', value: 'openai', type: SettingType.STRING, category: SettingCategory.AI, description: 'AI provider for smart assignment (openai)', }, { key: 'ai_model', value: 'gpt-4o', type: SettingType.STRING, category: SettingCategory.AI, description: 'OpenAI model to use for suggestions', }, { key: 'ai_send_descriptions', value: 'false', type: SettingType.BOOLEAN, category: SettingCategory.AI, description: 'Send anonymized project descriptions to AI', }, // Branding Settings { key: 'platform_name', value: 'Monaco Ocean Protection Challenge', type: SettingType.STRING, category: SettingCategory.BRANDING, description: 'Platform display name', }, { key: 'primary_color', value: '#de0f1e', type: SettingType.STRING, category: SettingCategory.BRANDING, description: 'Primary brand color (hex)', }, { key: 'secondary_color', value: '#053d57', type: SettingType.STRING, category: SettingCategory.BRANDING, description: 'Secondary brand color (hex)', }, { key: 'accent_color', value: '#557f8c', type: SettingType.STRING, category: SettingCategory.BRANDING, description: 'Accent color (hex)', }, // Security Settings { key: 'session_duration_hours', value: '24', type: SettingType.NUMBER, category: SettingCategory.SECURITY, description: 'Session duration in hours', }, { key: 'magic_link_expiry_minutes', value: '15', type: SettingType.NUMBER, category: SettingCategory.SECURITY, description: 'Magic link expiry time in minutes', }, { key: 'rate_limit_requests_per_minute', value: '60', type: SettingType.NUMBER, category: SettingCategory.SECURITY, description: 'API rate limit per minute', }, // Storage Settings { key: 'storage_provider', value: 's3', type: SettingType.STRING, category: SettingCategory.STORAGE, description: 'Storage provider: s3 (MinIO) or local (filesystem)', }, { key: 'local_storage_path', value: './uploads', type: SettingType.STRING, category: SettingCategory.STORAGE, description: 'Base path for local file storage', }, { key: 'max_file_size_mb', value: '500', type: SettingType.NUMBER, category: SettingCategory.STORAGE, description: 'Maximum file upload size in MB', }, { key: 'avatar_max_size_mb', value: '5', type: SettingType.NUMBER, category: SettingCategory.STORAGE, description: 'Maximum avatar image size in MB', }, { key: 'allowed_file_types', value: JSON.stringify(['application/pdf', 'video/mp4', 'video/quicktime', 'image/png', 'image/jpeg']), type: SettingType.JSON, category: SettingCategory.STORAGE, description: 'Allowed MIME types for file uploads', }, { key: 'allowed_image_types', value: JSON.stringify(['image/png', 'image/jpeg', 'image/webp']), type: SettingType.JSON, category: SettingCategory.STORAGE, description: 'Allowed MIME types for avatar/logo uploads', }, // Default Settings { key: 'default_timezone', value: 'Europe/Monaco', type: SettingType.STRING, category: SettingCategory.DEFAULTS, description: 'Default timezone for date displays', }, { key: 'default_page_size', value: '20', type: SettingType.NUMBER, category: SettingCategory.DEFAULTS, description: 'Default pagination size', }, { key: 'autosave_interval_seconds', value: '30', type: SettingType.NUMBER, category: SettingCategory.DEFAULTS, description: 'Autosave interval for evaluation forms', }, // WhatsApp Settings (Phase 2) { key: 'whatsapp_enabled', value: 'false', type: SettingType.BOOLEAN, category: SettingCategory.WHATSAPP, description: 'Enable WhatsApp notifications', }, { key: 'whatsapp_provider', value: 'META', type: SettingType.STRING, category: SettingCategory.WHATSAPP, description: 'WhatsApp provider (META or TWILIO)', }, { key: 'whatsapp_meta_phone_number_id', value: '', type: SettingType.STRING, category: SettingCategory.WHATSAPP, description: 'Meta WhatsApp Phone Number ID', }, { key: 'whatsapp_meta_access_token', value: '', type: SettingType.SECRET, category: SettingCategory.WHATSAPP, description: 'Meta WhatsApp Access Token', isSecret: true, }, { key: 'whatsapp_meta_business_account_id', value: '', type: SettingType.STRING, category: SettingCategory.WHATSAPP, description: 'Meta WhatsApp Business Account ID', }, { key: 'whatsapp_twilio_account_sid', value: '', type: SettingType.SECRET, category: SettingCategory.WHATSAPP, description: 'Twilio Account SID', isSecret: true, }, { key: 'whatsapp_twilio_auth_token', value: '', type: SettingType.SECRET, category: SettingCategory.WHATSAPP, description: 'Twilio Auth Token', isSecret: true, }, { key: 'whatsapp_twilio_phone_number', value: '', type: SettingType.STRING, category: SettingCategory.WHATSAPP, description: 'Twilio WhatsApp Phone Number', }, // OpenAI API Key (Phase 2) { key: 'openai_api_key', value: '', type: SettingType.SECRET, category: SettingCategory.AI, description: 'OpenAI API Key for AI-powered features', isSecret: true, }, ] for (const setting of settings) { await prisma.systemSettings.upsert({ where: { key: setting.key }, update: {}, create: setting, }) } // ========================================================================== // Create Admin Users // ========================================================================== console.log('👤 Creating admin users...') const adminAccounts = [ { email: 'matt@monaco-opc.com', name: 'Matt', password: '195260Mp!', }, { email: 'admin@monaco-opc.com', name: 'Admin', password: 'XDvVSDkPJKDXozlx', }, ] for (const account of adminAccounts) { const passwordHash = await bcrypt.hash(account.password, 12) const user = await prisma.user.upsert({ where: { email: account.email }, update: { passwordHash }, create: { email: account.email, name: account.name, role: UserRole.SUPER_ADMIN, status: UserStatus.ACTIVE, passwordHash, mustSetPassword: false, passwordSetAt: new Date(), onboardingCompletedAt: new Date(), }, }) console.log(` Created admin: ${user.email}`) } // ========================================================================== // Create Sample Program // ========================================================================== console.log('📁 Creating sample program...') const program = await prisma.program.upsert({ where: { name_year: { name: 'Monaco Ocean Protection Challenge', year: 2026 } }, update: {}, create: { name: 'Monaco Ocean Protection Challenge', year: 2026, status: ProgramStatus.ACTIVE, description: 'Annual ocean conservation startup competition supporting innovative solutions for ocean protection.', }, }) console.log(` Created program: ${program.name} ${program.year}`) // ========================================================================== // Create Round 1 // ========================================================================== console.log('🔄 Creating Round 1...') const round1 = await prisma.round.upsert({ where: { id: 'round-1-2026', // Use a deterministic ID for upsert }, update: {}, create: { id: 'round-1-2026', programId: program.id, name: 'Round 1 - Semi-Finalists Selection', status: RoundStatus.DRAFT, requiredReviews: 3, votingStartAt: new Date('2026-02-18T09:00:00Z'), votingEndAt: new Date('2026-02-23T18:00:00Z'), settingsJson: { allowGracePeriods: true, showAggregatesAfterClose: true, juryCanSeeOwnPastEvaluations: true, }, }, }) console.log(` Created round: ${round1.name}`) // ========================================================================== // Create Evaluation Form for Round 1 // ========================================================================== console.log('📝 Creating evaluation form...') await prisma.evaluationForm.upsert({ where: { roundId_version: { roundId: round1.id, version: 1, }, }, update: {}, create: { roundId: round1.id, version: 1, isActive: true, criteriaJson: [ { id: 'need_clarity', label: 'Need Clarity', description: 'How clearly is the problem/need articulated?', scale: '1-5', weight: 1, required: true, }, { id: 'solution_relevance', label: 'Solution Relevance', description: 'How relevant and innovative is the proposed solution?', scale: '1-5', weight: 1, required: true, }, { id: 'gap_analysis', label: 'Gap Analysis', description: 'How well does the project analyze existing gaps in the market?', scale: '1-5', weight: 1, required: true, }, { id: 'target_customers', label: 'Target Customer Clarity', description: 'How clearly are target customers/beneficiaries defined?', scale: '1-5', weight: 1, required: true, }, { id: 'ocean_impact', label: 'Ocean Impact', description: 'What is the potential positive impact on ocean conservation?', scale: '1-5', weight: 2, // Higher weight for ocean impact required: true, }, ], scalesJson: { '1-5': { min: 1, max: 5, labels: { 1: 'Poor', 2: 'Below Average', 3: 'Average', 4: 'Good', 5: 'Excellent', }, }, '1-10': { min: 1, max: 10, labels: { 1: 'Poor', 5: 'Average', 10: 'Excellent', }, }, }, }, }) console.log(' Created evaluation form v1') console.log('') console.log('✅ Seeding completed successfully!') console.log('') console.log('Admin accounts:') for (const account of adminAccounts) { console.log(` ${account.email}`) } console.log('') console.log('Next: run seed-candidatures.ts to import real participant data') } main() .catch((e) => { console.error('❌ Seeding failed:', e) process.exit(1) }) .finally(async () => { await prisma.$disconnect() })