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.
67 lines
2.4 KiB
JavaScript
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;
|
|
})();
|