Files
Lageplan/prisma/seed.js

268 lines
9.7 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const { PrismaClient } = require('@prisma/client')
const bcrypt = require('bcryptjs')
const prisma = new PrismaClient()
async function main() {
console.log('🌱 Starting seed...')
// Create default tenant
const defaultTenant = await prisma.tenant.upsert({
where: { slug: 'demo-feuerwehr' },
update: {},
create: {
name: 'Demo Feuerwehr',
slug: 'demo-feuerwehr',
description: 'Standard-Mandant für Demonstrationszwecke',
},
})
console.log('✅ Default tenant created:', defaultTenant.name)
// Create default users
const adminPassword = await bcrypt.hash('admin123', 12)
const editorPassword = await bcrypt.hash('editor123', 12)
const viewerPassword = await bcrypt.hash('viewer123', 12)
const admin = await prisma.user.upsert({
where: { email: 'admin@lageplan.local' },
update: { role: 'SERVER_ADMIN' },
create: {
email: 'admin@lageplan.local',
name: 'Server Admin',
password: adminPassword,
role: 'SERVER_ADMIN',
},
})
const editor = await prisma.user.upsert({
where: { email: 'editor@lageplan.local' },
update: { role: 'TENANT_ADMIN' },
create: {
email: 'editor@lageplan.local',
name: 'Einsatzleiter',
password: editorPassword,
role: 'TENANT_ADMIN',
},
})
const viewer = await prisma.user.upsert({
where: { email: 'viewer@lageplan.local' },
update: { role: 'OPERATOR' },
create: {
email: 'viewer@lageplan.local',
name: 'Bediener',
password: viewerPassword,
role: 'OPERATOR',
},
})
console.log('✅ Users created:', { admin: admin.email, editor: editor.email, viewer: viewer.email })
// Create tenant memberships
for (const u of [editor, viewer]) {
await prisma.tenantMembership.upsert({
where: { userId_tenantId: { userId: u.id, tenantId: defaultTenant.id } },
update: {},
create: {
userId: u.id,
tenantId: defaultTenant.id,
role: u.id === editor.id ? 'TENANT_ADMIN' : 'OPERATOR',
},
})
}
console.log('✅ Tenant memberships created')
// ─── FKS Icon Categories & Symbols (Bildquelle: Feuerwehr Koordination Schweiz FKS) ───
const fs = require('fs')
const path = require('path')
// Category definitions with file-name patterns for auto-assignment
const catDefs = [
{ name: 'Feuer / Brand', description: 'Feuer, Brand, Brandschutz', sortOrder: 1,
patterns: ['Feuer', 'Brand', 'Explosion', 'Entrauchung', 'Rauch', 'Kamin', 'Luefter', 'Sprinkler'] },
{ name: 'Wasser', description: 'Wasser, Hydranten, Leitungen', sortOrder: 2,
patterns: ['Wasser', 'Hydrant', 'Reservoir', 'Druckleitung', 'Druckleistung', 'Transportleitung',
'Schieber', 'Wasserloeschposten', 'Wasserversorgung', 'Wasserleitung', 'Ueberschwemmung',
'Offener_Wasserverlauf', 'Moeglicher_Wasserbezugsort', 'Stehendes_Gewaesser',
'Kleinloeschgeraet', 'Gefahr_durch_Loeschen_mit_Wasser'] },
{ name: 'Gefahren / Stoffe', description: 'Gefahrstoffe, Chemie, ABC', sortOrder: 3,
patterns: ['Gefaehrlich', 'Chemie', 'Chemikalien', 'Biologisch', 'Radioaktiv', 'Gas', 'Elektri',
'Elektrotableau', 'Achtung', 'Leitungsdaehte'] },
{ name: 'Rettung / Personen', description: 'Rettung, Sanität, Personen', sortOrder: 4,
patterns: ['Rettung', 'Retten', 'Sanitaet', 'Patienten', 'Sammelplatz', 'Sammelstelle',
'Totensammelstelle', 'Sprungretter', 'Helikopterlandeplatz'] },
{ name: 'Leitern / Geräte', description: 'Leitern, Fahrzeuge, Geräte', sortOrder: 5,
patterns: ['Leiter', 'Anhaengeleiter', 'Strebenleiter', 'ADL', 'TLF', 'HRF', 'SWHP'] },
{ name: 'Gebäude / Schäden', description: 'Gebäude, Zerstörung, Schäden', sortOrder: 6,
patterns: ['Beschaedigung', 'Teilzerstoerung', 'Totalzerstoerung', 'Decke_eingestuerzt',
'Umfassungswaende', 'AnzahlGeschosse', 'Eingang', 'Treppen', 'Lift', 'Bruecke',
'Rutschgebiet', 'Strasse', 'Bahnlin'] },
{ name: 'Einsatzführung', description: 'Führung, Organisation, Kommunikation', sortOrder: 7,
patterns: ['Einsatzleiter', 'KP_Font', 'Offizier', 'Beobachtungsposten', 'Kontrollstelle',
'Funk', 'Informationszentrum', 'Medienkontaktstelle', 'Fernsignaltableau',
'Materialdepot', 'Schluesseldepot', 'Abschnitt', 'Absperrung'] },
{ name: 'Organisationen', description: 'Feuerwehr, Polizei, Armee, Zivilschutz', sortOrder: 8,
patterns: ['Feuerwehr', 'Polizei', 'Armee', 'Zivilschutz', 'Chemiewehr', 'MS_de', 'Anmarsch'] },
{ name: 'Entwicklung / Taktik', description: 'Entwicklungsgrenzen, Ausbreitung', sortOrder: 9,
patterns: ['Entwicklungsgrenze', 'Horizontale_Entwicklung', 'Vertikale_Entwicklung',
'VertikaleEntwicklung', 'Unfall'] },
{ name: 'Verschiedenes', description: 'Weitere Signaturen', sortOrder: 10,
patterns: ['Massstab', 'Nordrichtung', 'Windrichtung'] },
]
// Delete ALL old system icons (regardless of fileKey pattern)
const deletedIcons = await prisma.iconAsset.deleteMany({
where: { isSystem: true },
})
console.log(`🗑️ ${deletedIcons.count} old system icons removed`)
// Clean up empty global categories
const oldGlobalCats = await prisma.iconCategory.findMany({ where: { tenantId: null } })
for (const oldCat of oldGlobalCats) {
const remaining = await prisma.iconAsset.count({ where: { categoryId: oldCat.id } })
if (remaining === 0) {
await prisma.iconCategory.delete({ where: { id: oldCat.id } }).catch(() => {})
}
}
// Create new global categories
const catMap = {}
for (const def of catDefs) {
const cat = await prisma.iconCategory.upsert({
where: { id: def.name.replace(/[^a-zA-Z0-9]/g, '-').toLowerCase() },
update: { name: def.name, description: def.description, sortOrder: def.sortOrder, isGlobal: true },
create: {
id: def.name.replace(/[^a-zA-Z0-9]/g, '-').toLowerCase(),
name: def.name,
description: def.description,
sortOrder: def.sortOrder,
isGlobal: true,
tenantId: null,
},
})
catMap[def.name] = cat
}
console.log(`${Object.keys(catMap).length} icon categories created`)
// Assign icon to category based on filename pattern matching
function findCategory(filename) {
for (const def of catDefs) {
for (const pattern of def.patterns) {
if (filename.includes(pattern)) return catMap[def.name]
}
}
return catMap['Verschiedenes']
}
// Load SVG icons from public/signaturen/
const signaturenDir = path.join(process.cwd(), 'public', 'signaturen')
let svgFiles = []
try {
svgFiles = fs.readdirSync(signaturenDir).filter(f => f.endsWith('.svg')).sort()
} catch (e) {
console.warn('⚠️ public/signaturen/ not found, skipping icon seed')
}
let created = 0
for (const file of svgFiles) {
// Clean name: remove .svg, remove _de/_DE suffix
let name = file.replace('.svg', '')
name = name.replace(/_de$/i, '').replace(/_DE$/i, '').replace(/-de$/i, '')
name = name.replace(/[_-]/g, ' ').replace(/\s+/g, ' ').trim()
const fileKey = `signaturen/${file}`
const category = findCategory(file)
const existing = await prisma.iconAsset.findFirst({ where: { fileKey } })
if (!existing) {
await prisma.iconAsset.create({
data: {
name,
categoryId: category.id,
fileKey,
mimeType: 'image/svg+xml',
isSystem: true,
width: 48,
height: 48,
},
})
created++
}
}
console.log(`✅ FKS Signaturen: ${created} new icons created (${svgFiles.length} total SVGs)`)
// Create a demo project
const demoProject = await prisma.project.upsert({
where: { id: 'demo-project-001' },
update: {},
create: {
id: 'demo-project-001',
title: 'Demo Einsatz - Wohnungsbrand',
location: 'Musterstrasse 42, 8000 Zürich',
description: 'Beispiel-Einsatz zur Demonstration der Funktionen',
ownerId: editor.id,
tenantId: defaultTenant.id,
mapCenter: { lng: 8.5417, lat: 47.3769 },
mapZoom: 17,
},
})
console.log('✅ Demo project created:', demoProject.title)
// Create default hose types (Swiss fire department standards)
const hoseTypes = [
{
name: '55mm Transportleitung',
diameterMm: 55,
lengthPerPieceM: 10,
flowRateLpm: 500,
frictionCoeff: 0.034,
description: 'Standard Transportleitung, 3er Verteiler (1500/3 = 500 l/min)',
isDefault: true,
sortOrder: 1,
},
{
name: '75mm Zubringerleitung',
diameterMm: 75,
lengthPerPieceM: 20,
flowRateLpm: 1500,
frictionCoeff: 0.012,
description: 'Zubringerleitung vom Hydranten zum Verteiler',
isDefault: false,
sortOrder: 2,
},
]
for (const ht of hoseTypes) {
await prisma.hoseType.upsert({
where: { name: ht.name },
update: {
diameterMm: ht.diameterMm,
lengthPerPieceM: ht.lengthPerPieceM,
flowRateLpm: ht.flowRateLpm,
frictionCoeff: ht.frictionCoeff,
description: ht.description,
isDefault: ht.isDefault,
sortOrder: ht.sortOrder,
},
create: ht,
})
}
console.log('✅ Hose types created:', hoseTypes.length)
// ─── Journal Check Templates (SOMA) ─────────────────────
// No default SOMA templates — each tenant defines their own via Admin → SOMA tab
console.log(' SOMA templates: keine Standard-Vorgaben (Tenants konfigurieren eigene)')
console.log('🎉 Seed completed successfully!')
}
main()
.catch((e) => {
console.error('❌ Seed failed:', e)
process.exit(1)
})
.finally(async () => {
await prisma.$disconnect()
})