docs: add incident report for symbol loss + recovery script

This commit is contained in:
Pepe Ziberi
2026-05-20 13:58:37 +02:00
parent 5adadd246e
commit fdd928720a
2 changed files with 351 additions and 0 deletions

217
prisma/recover-symbols.js Normal file
View File

@@ -0,0 +1,217 @@
/**
* Recovery-Skript für gelöschte Symbole
*
* ACHTUNG: Dieses Skript kann die Symbole in der Sidebar wiederherstellen,
* aber BESTEHENDE ZEICHNUNGEN (Features) bleiben broken, weil die alten
* iconId-UUIDs in den Feature-Properties für immer verloren sind.
*
* Ausführung:
* node prisma/recover-symbols.js
*
* Umgebung:
* DATABASE_URL muss gesetzt sein (oder .env.docker geladen werden)
*/
const { PrismaClient } = require('@prisma/client')
const fs = require('fs')
const path = require('path')
const prisma = new PrismaClient()
const SVG_DIR = path.join(__dirname, '..', 'public', 'signaturen')
// Kategorie-Definitionen (wie im Original-Seed)
const catDefs = [
{
name: 'Wasser',
description: 'Wasser, Hydranten, Leitungen',
sortOrder: 2,
patterns: ['Wasser', 'Hydrant', 'Reservoir', 'Druckleitung', 'Druckleistung', 'Transportleitung',
'Oberflurhydrant', 'Unterflurhydrant', 'Innenhydrant', 'Wasserloeschposten', 'Wasserversorgung',
'Moeglicher_Wasserbezugsort', 'Stehendes_Gewaesser', 'Schieber'],
},
{
name: 'Feuer',
description: 'Brand, Feuerwehr, Entwicklung',
sortOrder: 3,
patterns: ['Feuer', 'Brandherd', 'Entwicklungsgrenze', 'Horizontale_Entwicklung', 'Vertikale'],
},
{
name: 'Gefahr / Stoffe',
description: 'Chemie, Gas, Biologie, Strom',
sortOrder: 4,
patterns: ['Chemie', 'Chemikalien', 'Biologische', 'Gas', 'Elektrizitaet', 'Gefaehrliche',
'Explosion', 'Gefahr_durch_Loeschen'],
},
{
name: 'Technik / Gebäude',
description: 'Lifte, Türen, Treppen, Tableaus',
sortOrder: 5,
patterns: ['Lift', 'Treppe', 'Eingang', 'Tableau', 'Entrauchung', 'Sprinkler', 'Kamin',
'Brandmeldezentrale', 'Brandschutztueren', 'Elektrotableau', 'Fernsignaltableau',
'Luefter', 'Decke_eingestuerzt', 'AnzahlGeschosse'],
},
{
name: 'Gebäude / Schäden',
description: 'Gebäude, Zerstörung, Schäden',
sortOrder: 6,
patterns: ['Beschaedigung', 'Teilzerstoerung', 'Totalzerstoerung', 'Bruecke'],
},
{
name: 'Einsatzführung',
description: 'Führung, Organisation, Kommunikation',
sortOrder: 7,
patterns: ['Einsatzleiter', 'ADL', 'KP_', 'Kontrollstelle', 'Informationszentrum',
'Medienkontaktstelle', 'Sanitaet', 'Sanitaetshilfsstelle', 'Totensammelstelle',
'Materialdepot', 'Schluesseldepot', 'Sammelstelle', 'Sammelplatz', 'Beobachtungsposten'],
},
{
name: 'Fahrzeuge / Geräte',
description: 'Feuerwehrfahrzeuge, Leitern, Geräte',
sortOrder: 8,
patterns: ['TLF', 'HRF', 'Anhaengeleiter', 'Leiter', 'Sprungretter', 'Kleinloeschgeraet',
'SWHP', 'MS_de'],
},
{
name: 'Kommunikation / Sonstiges',
description: 'Funk, Wind, Massstab, etc.',
sortOrder: 9,
patterns: ['Funk', 'Windrichtung', 'Nordrichtung', 'Massstab', 'Helikopter', 'Armee',
'Zivilschutz', 'Rettungen', 'Unfall', 'Rutschgebiet', 'Ueberschwemmung',
'Abschnitt', 'Absperrung', 'Achtung', 'Anmarsch'],
},
]
function findCategory(filename) {
const base = filename.replace(/\.svg$/i, '')
for (const cat of catDefs) {
for (const p of cat.patterns) {
if (base.toLowerCase().includes(p.toLowerCase())) return cat.name
}
}
return 'Sonstiges'
}
async function recover() {
console.log('🚨 SYMBOL RECOVERY STARTED')
console.log('')
console.log('⚠️ WARNUNG: Bestehende Zeichnungen bleiben broken!')
console.log(' Die alten iconId-UUIDs in den Feature-Properties sind verloren.')
console.log(' Benutzer müssen Symbole in Zeichnungen manuell neu setzen.')
console.log('')
// ─── 1. Lade SVG-Dateien ───
if (!fs.existsSync(SVG_DIR)) {
console.error('❌ SVG-Verzeichnis nicht gefunden:', SVG_DIR)
process.exit(1)
}
const files = fs.readdirSync(SVG_DIR).filter(f => f.endsWith('.svg'))
console.log(`📁 ${files.length} SVG-Dateien gefunden`)
// ─── 2. Erstelle Icon-Categories ───
console.log('\n📂 Erstelle Icon-Categories...')
const categoryMap = {}
for (const def of catDefs) {
const cat = await prisma.iconCategory.upsert({
where: { name: def.name },
update: {},
create: {
name: def.name,
description: def.description,
sortOrder: def.sortOrder,
isGlobal: true,
},
})
categoryMap[def.name] = cat.id
console.log(`${cat.name}`)
}
// Sonstiges-Kategorie
const sonstiges = await prisma.iconCategory.upsert({
where: { name: 'Sonstiges' },
update: {},
create: { name: 'Sonstiges', sortOrder: 99, isGlobal: true },
})
categoryMap['Sonstiges'] = sonstiges.id
// ─── 3. Erstelle IconAssets ───
console.log('\n🖼 Erstelle IconAssets...')
const iconMap = {} // fileKey -> icon.id
for (const file of files) {
const filePath = path.join(SVG_DIR, file)
const stats = fs.statSync(filePath)
const fileKey = `lageplan-icons/symbols/${file}`
const name = file.replace(/\.svg$/i, '').replace(/_/g, ' ')
const catName = findCategory(file)
const categoryId = categoryMap[catName]
const icon = await prisma.iconAsset.upsert({
where: { fileKey },
update: {
name,
mimeType: 'image/svg+xml',
isSystem: true,
isActive: true,
iconType: 'STANDARD',
categoryId,
},
create: {
name,
fileKey,
mimeType: 'image/svg+xml',
isSystem: true,
isActive: true,
iconType: 'STANDARD',
categoryId,
},
})
iconMap[fileKey] = icon.id
}
console.log(`${files.length} IconAssets erstellt/aktualisiert`)
// ─── 4. Aktiviere alle Icons für alle Tenants ───
console.log('\n🏢 Aktiviere Symbole für alle Tenants...')
const tenants = await prisma.tenant.findMany({ select: { id: true, name: true } })
let tsCount = 0
for (const tenant of tenants) {
for (const [fileKey, iconId] of Object.entries(iconMap)) {
await prisma.tenantSymbol.upsert({
where: {
tenantId_iconId: {
tenantId: tenant.id,
iconId: iconId,
},
},
update: { isActive: true },
create: {
tenantId: tenant.id,
iconId: iconId,
isActive: true,
},
})
tsCount++
}
console.log(`${tenant.name}: ${Object.keys(iconMap).length} Symbole aktiviert`)
}
// ─── 5. Zeichnungen (Features) — Hinweis ───
console.log('\n⚠ FEATURES / ZEICHNUNGEN:')
const featureCount = await prisma.feature.count({ where: { type: 'symbol' } })
console.log(` ${featureCount} Symbol-Features in der Datenbank gefunden.`)
console.log(` Diese zeigen auf GELÖSCHTE iconId-UUIDs und bleiben BROKEN.`)
console.log(` Es gibt KEINE Möglichkeit, die alten UUIDs wiederherzustellen.`)
console.log(` Benutzer müssen Symbole in ihren Zeichnungen manuell neu setzen.`)
console.log('\n✅ RECOVERY ABGESCHLOSSEN')
console.log(' - Symbole sind jetzt in der Sidebar verfügbar')
console.log(' - Admin → Symbol-Manager zeigt alle Symbole an')
console.log(' - Zeichnungen müssen manuell repariert werden')
}
recover()
.then(async () => { await prisma.$disconnect() })
.catch(async (e) => {
console.error('Recovery error:', e)
await prisma.$disconnect()
process.exit(1)
})