/** * Database Seed Script * * Seeds the database with initial mock product data for development and testing. * Run with: pnpm db:seed */ import 'dotenv/config' import { drizzle } from 'drizzle-orm/postgres-js' import { and, eq } from 'drizzle-orm' import postgres from 'postgres' import * as schema from './schema' import { products, roles, productRoleVisibility } from './schema' /** * Standard roles for the system * MVP: All roles are created, but assignment is manual * Phase 2/3: educator and company roles require approval workflow */ const standardRoles = [ { code: 'private' as const, displayName: 'Privatperson', description: 'Für private Besucher und Einzelpersonen', requiresApproval: false, sortOrder: 1, active: true, }, { code: 'educator' as const, displayName: 'Pädagoge', description: 'Für Lehrer, Erzieher und pädagogische Fachkräfte', requiresApproval: true, sortOrder: 2, active: true, }, { code: 'company' as const, displayName: 'Unternehmen', description: 'Für Firmenkunden und B2B-Partner', requiresApproval: true, sortOrder: 3, active: true, }, ] /** * Sample annual pass products for experimenta */ const mockProducts: Array<{ navProductId: string name: string description: string price: string stockQuantity: number category: string active: boolean roles: Array<'private' | 'educator' | 'company'> }> = [ { navProductId: 'MSPACE-JK-2025', name: 'Makerspace Jahreskarte', description: 'Unbegrenzter Zugang zum Makerspace für 365 Tage. Nutze modernste Werkzeuge, 3D-Drucker, Lasercutter und vieles mehr. Perfekt für Maker, Tüftler und kreative Köpfe.', price: '30.00', stockQuantity: 100, category: 'makerspace-annual-pass', active: true, roles: ['private', 'educator', 'company'], }, { navProductId: 'EXPERIMENTA-JK-2025', name: 'experimenta Jahreskarte', description: 'Erlebe die Ausstellungswelt der experimenta ein ganzes Jahr lang. Mit freiem Eintritt zu allen Ausstellungen, Science Dome Shows und Sonderausstellungen.', price: '49.00', stockQuantity: 200, category: 'annual-pass', active: true, roles: ['private', 'educator', 'company'], }, { navProductId: 'PAEDAGOGEN-JK-2025', name: 'Pädagogen Jahreskarte', description: 'Speziell für Lehrkräfte und Pädagogen. Mit exklusiven Fortbildungsangeboten, didaktischen Materialien und freiem Zugang zu allen Ausstellungen.', price: '0.00', stockQuantity: 99999, category: 'educator-annual-pass', active: true, roles: ['private', 'educator'], }, ] async function seed() { // Get database connection from environment const connectionString = process.env.DATABASE_URL if (!connectionString) { throw new Error('DATABASE_URL environment variable is not set') } console.log('🌱 Starting database seed...') // Create database connection with schema const client = postgres(connectionString) const db = drizzle(client, { schema }) try { // 1. Insert/Update Roles console.log(`👥 Inserting ${standardRoles.length} roles...`) const insertedRoles = [] for (const roleData of standardRoles) { const [role] = await db .insert(roles) .values(roleData) .onConflictDoUpdate({ target: roles.code, set: { displayName: roleData.displayName, description: roleData.description, requiresApproval: roleData.requiresApproval, sortOrder: roleData.sortOrder, active: roleData.active, updatedAt: new Date(), }, }) .returning() insertedRoles.push(role) } console.log(`✅ Successfully inserted/updated ${insertedRoles.length} roles:`) insertedRoles.forEach((role) => { console.log( ` - ${role.displayName} (${role.code}) ${role.requiresApproval ? '[requires approval]' : '[auto-approved]'}` ) }) // 2. Insert/Update Products console.log(`\n📦 Inserting ${mockProducts.length} products...`) const insertedProducts = [] for (const productData of mockProducts) { const [product] = await db .insert(products) .values(productData) .onConflictDoUpdate({ target: products.navProductId, set: { name: productData.name, description: productData.description, price: productData.price, stockQuantity: productData.stockQuantity, category: productData.category, active: productData.active, updatedAt: new Date(), }, }) .returning() insertedProducts.push(product) } console.log(`✅ Successfully inserted/updated ${insertedProducts.length} products:`) insertedProducts.forEach((product) => { console.log(` - ${product.name} (${product.navProductId}) - €${product.price}`) }) // 3. Assign Roles to Products (using roles array from mock data) console.log(`\n🔗 Assigning roles to products...`) let assignmentCount = 0 for (let i = 0; i < insertedProducts.length; i++) { const product = insertedProducts[i] const productData = mockProducts[i] const roleCodes = productData.roles || [] for (const roleCode of roleCodes) { // Find role display name for logging const role = insertedRoles.find((r) => r.code === roleCode) if (!role) { console.warn(` ⚠️ Role '${roleCode}' not found for product ${product.name}`) continue } // Check if assignment already exists const existing = await db.query.productRoleVisibility.findFirst({ where: and( eq(productRoleVisibility.productId, product.id), eq(productRoleVisibility.roleCode, roleCode) ), }) if (!existing) { await db.insert(productRoleVisibility).values({ productId: product.id, roleCode, // Direct reference to roles.code (PK) }) assignmentCount++ console.log(` - ${product.name} → ${role.displayName}`) } } } console.log(`✅ Created ${assignmentCount} product-role assignments`) console.log('\n✨ Database seed completed successfully!') } catch (error) { console.error('❌ Error seeding database:', error) throw error } finally { // Close database connection await client.end() } } // Run seed function seed() .then(() => { process.exit(0) }) .catch((error) => { console.error('Fatal error:', error) process.exit(1) })