Step 6 migration: add JSON backup and restore flows

This commit is contained in:
Ponte
2026-02-11 15:16:12 +01:00
parent e58ee18936
commit 110a6c4a1b
5 changed files with 376 additions and 0 deletions

92
app.js
View File

@@ -33,9 +33,15 @@ const consoleTabs = document.getElementById("consoleTabs");
const gameSectionTitle = document.getElementById("gameSectionTitle");
const dataModeInfo = document.getElementById("dataModeInfo");
const migrateBtn = document.getElementById("migrateBtn");
const backupControls = document.getElementById("backupControls");
const backupBtn = document.getElementById("backupBtn");
const restoreMergeBtn = document.getElementById("restoreMergeBtn");
const restoreReplaceBtn = document.getElementById("restoreReplaceBtn");
const restoreFileInput = document.getElementById("restoreFileInput");
const gamesList = document.getElementById("gamesList");
const gameCardTemplate = document.getElementById("gameCardTemplate");
let editingGameId = null;
let pendingRestoreMode = "merge";
platformForm.addEventListener("submit", async (event) => {
event.preventDefault();
@@ -292,6 +298,86 @@ migrateBtn.addEventListener("click", async () => {
}
});
backupBtn.addEventListener("click", async () => {
if (!apiReachable) {
alert("API indisponible. Sauvegarde JSON impossible.");
return;
}
try {
const dump = await apiRequest("/api/backup/export");
const blob = new Blob([JSON.stringify(dump, null, 2)], { type: "application/json" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
a.href = url;
a.download = `video-games-backup-${stamp}.json`;
document.body.append(a);
a.click();
a.remove();
URL.revokeObjectURL(url);
} catch (error) {
console.error(error);
alert("Echec de la sauvegarde JSON.");
}
});
restoreMergeBtn.addEventListener("click", () => {
pendingRestoreMode = "merge";
restoreFileInput.click();
});
restoreReplaceBtn.addEventListener("click", () => {
pendingRestoreMode = "replace";
restoreFileInput.click();
});
restoreFileInput.addEventListener("change", async (event) => {
const input = event.target;
const file = input.files && input.files[0] ? input.files[0] : null;
input.value = "";
if (!file) {
return;
}
if (!apiReachable) {
alert("API indisponible. Restauration impossible.");
return;
}
try {
const fileText = await file.text();
const dump = JSON.parse(fileText);
if (pendingRestoreMode === "replace") {
const confirmed = window.confirm(
"Mode remplacement: la base actuelle sera remplacee. Une sauvegarde pre-restore sera creee. Continuer ?",
);
if (!confirmed) {
return;
}
}
const result = await apiRequest("/api/backup/restore", {
method: "POST",
body: {
mode: pendingRestoreMode,
dump,
},
});
pendingLocalImport = null;
await refreshFromApi(state.selectedBrand, state.selectedConsole);
alert(
`Restauration terminee (${result.mode}): ${result.insertedGames || 0} jeu(x), ${result.insertedConsoles || 0} console(s).`,
);
} catch (error) {
console.error(error);
alert("Echec de la restauration JSON.");
}
});
function render() {
renderDataMode();
renderBrandTabs();
@@ -322,6 +408,12 @@ function renderDataMode() {
} else {
migrateBtn.classList.add("hidden");
}
if (apiReachable) {
backupControls.classList.remove("hidden");
} else {
backupControls.classList.add("hidden");
}
}
function renderBrandTabs() {