- Introduced a new page for educator annual passes, displaying relevant products with a dedicated layout and loading/error states. - Updated AreaTabs component to include the educator tab and adjusted routing logic. - Modified useAuth to redirect users to the products page after login. - Adjusted mock product prices and stock quantities in the database seeding script to reflect new pricing strategy. These changes enhance the user experience for educators and improve product visibility in the application.
223 lines
6.6 KiB
TypeScript
223 lines
6.6 KiB
TypeScript
/**
|
|
* 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)
|
|
})
|