Files
dapelza 39321d680f Phase 7: KI-JSON-Import und Backup-Export/Import
Neues Modul js/import/ mit reiner Validierungslogik
(importValidator.js: parseAndValidate(), findDuplicate()). Jeder
Ausflugsziel-Eintrag im importierten KI-JSON wird einzeln bewertet statt
alles-oder-nichts: fehlender Name/Kategorie oder ungueltige Koordinaten
fuehren zur Ablehnung, weiche Probleme (unbekannte Kategorie, unsichere
website/image-URL, ungueltige Bewertung, falscher Feldtyp) erzeugen nur
eine Warnung. sanitizeEntry() setzt sicherheitskritische Felder vor der
Uebernahme zurueck (z.B. javascript:-URLs), da normalizePlace() selbst
keine Sicherheitspruefung vornimmt. findDuplicate() erkennt
wahrscheinliche Duplikate gegen den bestehenden Datenbestand.

UI (js/ui/import.js): Modal mit zwei Tabs - KI-Ausflugsziele importieren
(Text einfuegen oder Datei hochladen, Vorschau mit
gueltig/Warnung/abgelehnt, Duplikat-Toggle, Bestaetigen) und Backup
sichern/wiederherstellen (js/import/backup.js: Export als JSON-Datei,
Wiederherstellen mit Ersetzen/Ergaenzen-Wahl). Vorschau-Rendering
ausschliesslich ueber el()/textContent, da es sich um echte, ungepruefte
Fremddaten handelt.

Damit sind alle Kernfunktionen aus anforderung.md umgesetzt.
docs/plan.md entsprechend fortgeschrieben.
2026-07-24 12:50:05 +02:00

67 lines
2.4 KiB
JavaScript

// Backup-Export/Import des Gesamtdatenbestands (Phase 7). Eigenständige,
// bewusst einfachere Funktion als der KI-JSON-Import (js/import/
// importValidator.js): Backup-Daten stammen aus der eigenen Anwendung, nicht
// von einer externen KI, daher ist kein Vorschau-/Validierungs-Workflow pro
// Eintrag nötig - lediglich die Grundstruktur ({places: [...]}) wird
// geprüft, jeder Eintrag läuft danach durch normalizePlace() (übernimmt
// dabei vorhandene id/createdAt/updatedAt aus dem Backup unverändert).
(function () {
const { getState } = EventMap.store;
const { normalizePlace } = EventMap.models;
const { downloadTextFile } = EventMap.utils;
/** Exportiert state.places als herunterladbare JSON-Backup-Datei. */
function exportBackup() {
const { places } = getState();
const now = new Date();
const data = {
version: 1,
exportedAt: now.toISOString(),
places,
};
const dateStamp = now.toISOString().slice(0, 10);
downloadTextFile(`eventmap-backup-${dateStamp}.json`, JSON.stringify(data, null, 2));
}
/**
* Parst den Text einer Backup-Datei.
* @param {string} jsonText
* @returns {{success: boolean, error: string|null, places: Array}}
*/
function parseBackup(jsonText) {
let data;
try {
data = JSON.parse(jsonText);
} catch (err) {
return { success: false, error: 'Ungültiges JSON: ' + err.message, places: [] };
}
if (!data || typeof data !== 'object' || Array.isArray(data) || !Array.isArray(data.places)) {
return { success: false, error: 'Das Backup muss ein Objekt mit einem "places"-Array sein.', places: [] };
}
const places = data.places.map((raw) => normalizePlace(raw));
return { success: true, error: null, places };
}
/**
* Führt bestehende und importierte Places zusammen ("Ergänzen"): bei
* gleicher id gewinnt der importierte Eintrag (überschreibt den
* bestehenden an derselben Position), neue ids werden angehängt.
*/
function mergeBackupPlaces(existingPlaces, incomingPlaces) {
const merged = new Map(existingPlaces.map((place) => [place.id, place]));
for (const place of incomingPlaces) {
merged.set(place.id, place);
}
return Array.from(merged.values());
}
EventMap.import.exportBackup = exportBackup;
EventMap.import.parseBackup = parseBackup;
EventMap.import.mergeBackupPlaces = mergeBackupPlaces;
})();