fix(db): comprehensive symbol recovery + safety fixes
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 22m1s
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 22m1s
This commit is contained in:
151
prisma/recover-features.js
Normal file
151
prisma/recover-features.js
Normal file
@@ -0,0 +1,151 @@
|
||||
/**
|
||||
* Recovery-Skript für Zeichnungen (Features) nach Symbol-Verlust
|
||||
*
|
||||
* Problem: Features verweisen auf gelöschte iconId-UUIDs.
|
||||
* Die imageUrl ist "/api/icons/{alte-uuid}/image" → 404.
|
||||
*
|
||||
* Lösung:
|
||||
* Die alte UUID→SVG-Zuordnung ist verloren. ABER:
|
||||
* - Alle bestehenden IconAssets wurden per Upsert neu erstellt (gleiche fileKeys)
|
||||
* - Jedes Feature hat noch die alte iconId in properties
|
||||
* - Die Sidebar liefert jetzt NEUE iconIds
|
||||
*
|
||||
* Dieses Skript:
|
||||
* 1. Listet alle broken Symbol-Features (iconId zeigt auf gelöschtes Icon)
|
||||
* 2. Für Features wo imageUrl auf /signaturen/ zeigt: direkt fixbar
|
||||
* 3. Für Features mit /api/icons/{uuid}/image: UUID ist verloren → Renderer zeigt ⚠️
|
||||
* 4. Zählt und listet betroffene Projekte
|
||||
*
|
||||
* Der Renderer wurde angepasst (map-view.tsx): broken Symbole zeigen jetzt
|
||||
* ein ⚠️-Platzhalter statt nichts. User können das Symbol manuell ersetzen.
|
||||
*
|
||||
* Ausführung:
|
||||
* DATABASE_URL=... node prisma/recover-features.js [--dry-run]
|
||||
*/
|
||||
|
||||
const { PrismaClient } = require('@prisma/client')
|
||||
const prisma = new PrismaClient()
|
||||
|
||||
const DRY_RUN = process.argv.includes('--dry-run')
|
||||
|
||||
async function main() {
|
||||
console.log('🔧 FEATURE RECOVERY — Analyse & Reparatur')
|
||||
console.log(` Mode: ${DRY_RUN ? '🔍 DRY RUN' : '⚡ LIVE'}`)
|
||||
console.log('')
|
||||
|
||||
// ─── 1. Lade alle aktuellen IconAssets ───
|
||||
const allIcons = await prisma.iconAsset.findMany({
|
||||
select: { id: true, name: true, fileKey: true },
|
||||
})
|
||||
console.log(`📦 ${allIcons.length} IconAssets in der DB`)
|
||||
|
||||
const iconIdSet = new Set(allIcons.map(i => i.id))
|
||||
const fileKeyToIcon = {}
|
||||
for (const icon of allIcons) {
|
||||
fileKeyToIcon[icon.fileKey] = icon
|
||||
}
|
||||
|
||||
// ─── 2. Lade alle Symbol-Features ───
|
||||
const features = await prisma.feature.findMany({
|
||||
where: { type: 'symbol' },
|
||||
include: { project: { select: { id: true, name: true, tenantId: true } } },
|
||||
})
|
||||
console.log(`🖼️ ${features.length} Symbol-Features gefunden`)
|
||||
console.log('')
|
||||
|
||||
let ok = 0
|
||||
let fixedViaFileKey = 0
|
||||
let broken = 0
|
||||
const brokenByProject = {}
|
||||
|
||||
for (const feature of features) {
|
||||
const props = feature.properties || {}
|
||||
const iconId = props.iconId || ''
|
||||
const imageUrl = props.imageUrl || ''
|
||||
|
||||
// ─── Check 1: iconId still exists? ───
|
||||
if (iconId && iconIdSet.has(iconId)) {
|
||||
ok++
|
||||
continue
|
||||
}
|
||||
|
||||
// ─── Check 2: imageUrl points to static /signaturen/ file? ───
|
||||
const sigMatch = imageUrl.match(/\/signaturen\/(.+\.svg)/)
|
||||
if (sigMatch) {
|
||||
const fileKey = `signaturen/${sigMatch[1]}`
|
||||
const matchingIcon = fileKeyToIcon[fileKey]
|
||||
if (matchingIcon) {
|
||||
// Fix: Update iconId to new valid ID
|
||||
const newProps = {
|
||||
...props,
|
||||
iconId: matchingIcon.id,
|
||||
imageUrl: `/api/icons/${matchingIcon.id}/image`,
|
||||
_recovered: true,
|
||||
_oldIconId: iconId,
|
||||
}
|
||||
if (!DRY_RUN) {
|
||||
await prisma.feature.update({
|
||||
where: { id: feature.id },
|
||||
data: { properties: newProps },
|
||||
})
|
||||
}
|
||||
fixedViaFileKey++
|
||||
console.log(` ✅ ${feature.id} → ${matchingIcon.name} (via fileKey)`)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Check 3: imageUrl is /api/icons/{uuid}/image? ───
|
||||
// The uuid in the URL = old iconId = deleted → can't resolve
|
||||
// Mark as broken
|
||||
broken++
|
||||
const projName = feature.project?.name || feature.projectId
|
||||
if (!brokenByProject[projName]) brokenByProject[projName] = []
|
||||
brokenByProject[projName].push({
|
||||
featureId: feature.id,
|
||||
iconId,
|
||||
imageUrl,
|
||||
})
|
||||
}
|
||||
|
||||
console.log('')
|
||||
console.log('═══════════════════════════════════════════')
|
||||
console.log(` ✅ OK (iconId existiert): ${ok}`)
|
||||
console.log(` 🔧 Gefixt (via fileKey): ${fixedViaFileKey}`)
|
||||
console.log(` ❌ Broken (UUID verloren): ${broken}`)
|
||||
console.log(` 📊 Total: ${features.length}`)
|
||||
console.log('═══════════════════════════════════════════')
|
||||
|
||||
if (broken > 0) {
|
||||
console.log('')
|
||||
console.log('❌ BROKEN FEATURES pro Projekt:')
|
||||
for (const [projName, items] of Object.entries(brokenByProject)) {
|
||||
console.log(` 📄 "${projName}": ${items.length} Symbole`)
|
||||
for (const item of items) {
|
||||
console.log(` - Feature ${item.featureId} (iconId: ${item.iconId.substring(0, 8)}...)`)
|
||||
}
|
||||
}
|
||||
console.log('')
|
||||
console.log('💡 Diese Symbole zeigen jetzt ein ⚠️ auf der Karte.')
|
||||
console.log(' User müssen das Symbol manuell löschen und neu platzieren.')
|
||||
console.log(' (Select-Mode → Symbol anklicken → DEL → neues Symbol aus Sidebar ziehen)')
|
||||
}
|
||||
|
||||
if (broken === 0 && fixedViaFileKey === 0) {
|
||||
console.log('')
|
||||
console.log('🎉 Alle Symbol-Features sind OK! Keine Reparatur nötig.')
|
||||
}
|
||||
|
||||
if (DRY_RUN) {
|
||||
console.log('')
|
||||
console.log('🔍 DRY RUN — Keine Änderungen geschrieben.')
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
.then(async () => { await prisma.$disconnect() })
|
||||
.catch(async (e) => {
|
||||
console.error('❌ Fehler:', e)
|
||||
await prisma.$disconnect()
|
||||
process.exit(1)
|
||||
})
|
||||
Reference in New Issue
Block a user