MOPC-App/prisma/seed.ts

489 lines
14 KiB
TypeScript
Raw Normal View History

import {
PrismaClient,
UserRole,
UserStatus,
ProgramStatus,
RoundStatus,
SettingType,
SettingCategory,
} from '@prisma/client'
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 Super Admin
// ==========================================================================
console.log('👤 Creating super admin...')
const admin = await prisma.user.upsert({
where: { email: 'matt.ciaccio@gmail.com' },
update: {},
create: {
email: 'matt.ciaccio@gmail.com',
name: 'Matt Ciaccio',
role: UserRole.SUPER_ADMIN,
status: UserStatus.ACTIVE,
},
})
console.log(` Created admin: ${admin.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')
// ==========================================================================
// Create Sample Jury Members
// ==========================================================================
console.log('👥 Creating sample jury members...')
const juryMembers = [
{
email: 'jury1@example.com',
name: 'Dr. Marine Expert',
expertiseTags: ['marine_biology', 'conservation', 'policy'],
},
{
email: 'jury2@example.com',
name: 'Tech Innovator',
expertiseTags: ['technology', 'innovation', 'startups'],
},
{
email: 'jury3@example.com',
name: 'Ocean Advocate',
expertiseTags: ['conservation', 'sustainability', 'education'],
},
]
for (const jury of juryMembers) {
await prisma.user.upsert({
where: { email: jury.email },
update: {},
create: {
email: jury.email,
name: jury.name,
role: UserRole.JURY_MEMBER,
status: UserStatus.INVITED,
expertiseTags: jury.expertiseTags,
maxAssignments: 15,
},
})
console.log(` Created jury member: ${jury.email}`)
}
// ==========================================================================
// Create Sample Projects
// ==========================================================================
console.log('📦 Creating sample projects...')
const sampleProjects = [
{
title: 'OceanAI - Plastic Detection System',
teamName: 'BlueWave Tech',
description: 'AI-powered system using satellite imagery and drones to detect and map ocean plastic concentrations for targeted cleanup operations.',
tags: ['technology', 'ai', 'plastic_pollution'],
},
{
title: 'Coral Restoration Network',
teamName: 'ReefGuard Foundation',
description: 'Community-driven coral nursery and transplantation program using innovative 3D-printed substrates.',
tags: ['conservation', 'coral', 'community'],
},
{
title: 'SeaTrack - Sustainable Fishing Tracker',
teamName: 'FishRight Solutions',
description: 'Blockchain-based supply chain tracking system ensuring sustainable fishing practices from ocean to table.',
tags: ['technology', 'sustainable_fishing', 'blockchain'],
},
]
for (const project of sampleProjects) {
await prisma.project.create({
data: {
roundId: round1.id,
title: project.title,
teamName: project.teamName,
description: project.description,
tags: project.tags,
},
})
console.log(` Created project: ${project.title}`)
}
console.log('')
console.log('✅ Seeding completed successfully!')
console.log('')
console.log('📧 Admin login: matt.ciaccio@gmail.com')
console.log(' (Use magic link authentication)')
}
main()
.catch((e) => {
console.error('❌ Seeding failed:', e)
process.exit(1)
})
.finally(async () => {
await prisma.$disconnect()
})