mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 04:50:59 +00:00
fix(backups): address restore review findings
This commit is contained in:
1 parent
7eb3b91925
commit
e9507a0c93
8 files changed
+29
-15
No files matched your search
+1
-1
@@ -6243,7 +6243,7 @@
|
||||
"restore": "Wiederherstellen",
|
||||
"restoreAria": "Backup für {username} wiederherstellen",
|
||||
"restoreTitle": "Aus Backup wiederherstellen",
|
||||
"restoreDescription": "Dies ersetzt ALLE aktuellen Daten von {username} mit dem Snapshot von {when}. Vorhandene Messungen, Medikamente, Einnahmen, Stimmungseinträge, Benachrichtigungs-Kanäle, Push-Abos und Telegram-Lösch-Aufträge werden entfernt.",
|
||||
"restoreDescription": "Dies ersetzt ALLE aktuellen Daten von {username} mit dem Snapshot von {when}. Vorhandene Messungen, Medikamente, Einnahmen, Stimmungseinträge, Benachrichtigungs-Kanäle, Push-Abos und Telegram-Lösch-Aufträge werden entfernt. Falls im Snapshot enthalten, überschreiben instanzweite Einstellungen die Konfiguration der gesamten Installation.",
|
||||
"restorePromptLabel": "Tippe RESTORE zum Fortfahren",
|
||||
"restoreConfirm": "Jetzt wiederherstellen",
|
||||
"restoreInProgress": "Wiederherstellung läuft…",
|
||||
|
||||
+1
-1
@@ -6243,7 +6243,7 @@
|
||||
"restore": "Restore",
|
||||
"restoreAria": "Restore from backup for {username}",
|
||||
"restoreTitle": "Restore from backup",
|
||||
"restoreDescription": "This will replace ALL current data for {username} with the snapshot from {when}. Existing measurements, medications, intake events, mood entries, notification channels, push subscriptions and queued Telegram deletions will be deleted.",
|
||||
"restoreDescription": "This will replace ALL current data for {username} with the snapshot from {when}. Existing measurements, medications, intake events, mood entries, notification channels, push subscriptions and queued Telegram deletions will be deleted. If included in the snapshot, instance-wide settings overwrite the configuration for the entire installation.",
|
||||
"restorePromptLabel": "Type RESTORE to continue",
|
||||
"restoreConfirm": "Restore now",
|
||||
"restoreInProgress": "Restoring…",
|
||||
|
||||
@@ -100,7 +100,7 @@ const handler = apiHandler(
|
||||
details: { reason: "missing_confirmation", backupId: id },
|
||||
});
|
||||
return apiError(
|
||||
"Confirmation token missing — body must include confirm: 'RESTORE'",
|
||||
"Confirmation token missing — restoring user data and included instance-wide settings requires confirm: 'RESTORE'",
|
||||
422,
|
||||
);
|
||||
}
|
||||
@@ -403,13 +403,11 @@ const handler = apiHandler(
|
||||
unit: measurement.unit,
|
||||
source: measurement.source ?? "MANUAL",
|
||||
measuredAt: new Date(measurement.measuredAt),
|
||||
notes: measurement.notes ?? null,
|
||||
notes: null,
|
||||
notesEncrypted:
|
||||
measurement.notesEncrypted !== undefined
|
||||
? measurement.notesEncrypted === null
|
||||
? null
|
||||
: decodeEncryptedBytes(measurement.notesEncrypted)
|
||||
: encryptNote(measurement.notes ?? null),
|
||||
measurement.notesEncrypted == null
|
||||
? encryptNote(measurement.notes ?? null)
|
||||
: decodeEncryptedBytes(measurement.notesEncrypted),
|
||||
externalId: measurement.externalId ?? null,
|
||||
externalSourceVersion: measurement.externalSourceVersion ?? null,
|
||||
glucoseContext: (measurement.glucoseContext ?? null) as never,
|
||||
|
||||
@@ -421,7 +421,7 @@ describe("GET /api/export/full-backup", () => {
|
||||
expect(res.headers.get("content-disposition")).toMatch(/\.json"/);
|
||||
const json = await res.json();
|
||||
expect(json).toMatchObject({
|
||||
schemaVersion: "1",
|
||||
schemaVersion: "2",
|
||||
userId: "user-1",
|
||||
measurements: [],
|
||||
medications: [],
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
import de from "../../../../messages/de.json";
|
||||
import en from "../../../../messages/en.json";
|
||||
|
||||
/**
|
||||
* v1.4.15 phase-C5 — `/admin/backups` empty state.
|
||||
@@ -76,4 +78,13 @@ describe("BackupsSection — empty state", () => {
|
||||
const matches = (html.match(/Backup now/g) ?? []).length;
|
||||
expect(matches).toBeGreaterThanOrEqual(2);
|
||||
});
|
||||
|
||||
it("warns in both catalogs that restore overwrites instance-wide settings", () => {
|
||||
expect(de.admin.section.backups.restoreDescription).toMatch(
|
||||
/instanzweite Einstellungen/i,
|
||||
);
|
||||
expect(en.admin.section.backups.restoreDescription).toMatch(
|
||||
/instance-wide settings/i,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -76,7 +76,7 @@ const appSettings = {
|
||||
assistantHealthScoreExplainerEnabled: false,
|
||||
moduleAvailabilityJson: { nutrients: false, insights: true },
|
||||
documentMaxFileBytes: 12_345_678,
|
||||
documentQuotaBytes: 9_876_543_210n,
|
||||
documentQuotaBytes: BigInt("9876543210"),
|
||||
};
|
||||
|
||||
function makePrisma() {
|
||||
|
||||
@@ -159,7 +159,7 @@ describe("canonical disaster-recovery backup round-trip", () => {
|
||||
assistantHealthScoreExplainerEnabled: false,
|
||||
moduleAvailabilityJson: { insights: true, nutrients: false },
|
||||
documentMaxFileBytes: 12_345_678,
|
||||
documentQuotaBytes: 9_876_543_210n,
|
||||
documentQuotaBytes: BigInt("9876543210"),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -917,12 +917,12 @@ describe("canonical disaster-recovery backup round-trip", () => {
|
||||
const originalCreateMany = tx.measurement.createMany.bind(
|
||||
tx.measurement,
|
||||
);
|
||||
vi.spyOn(tx.measurement, "upsert").mockImplementation(async (args) => {
|
||||
vi.spyOn(tx.measurement, "upsert").mockImplementation((args) => {
|
||||
stableWriteQueries += 1;
|
||||
return originalUpsert(args);
|
||||
});
|
||||
vi.spyOn(tx.measurement, "createMany").mockImplementation(
|
||||
async (args) => {
|
||||
(args) => {
|
||||
stableWriteQueries += 1;
|
||||
return originalCreateMany(args);
|
||||
},
|
||||
|
||||
@@ -100,7 +100,8 @@ describe("POST /api/admin/backups/[id]/restore", () => {
|
||||
unit: "kg",
|
||||
measuredAt: "2026-05-08T07:00:00.000Z",
|
||||
source: "MANUAL",
|
||||
notes: null,
|
||||
notes: "private legacy note",
|
||||
notesEncrypted: null,
|
||||
},
|
||||
{
|
||||
type: "PULSE",
|
||||
@@ -221,6 +222,8 @@ describe("POST /api/admin/backups/[id]/restore", () => {
|
||||
expect(measurements[0]!.type).toBe("WEIGHT");
|
||||
expect(measurements[0]!.value).toBe(80.5);
|
||||
expect(measurements[1]!.type).toBe("PULSE");
|
||||
expect(measurements[0]!.notes).toBeNull();
|
||||
expect(measurements[0]!.notesEncrypted).not.toBeNull();
|
||||
|
||||
const medications = await prisma.medication.findMany({
|
||||
where: { userId: admin.id },
|
||||
@@ -303,6 +306,8 @@ describe("POST /api/admin/backups/[id]/restore", () => {
|
||||
});
|
||||
|
||||
expect(res.status).toBe(422);
|
||||
const error = (await res.json()) as { error: string };
|
||||
expect(error.error).toMatch(/instance-wide settings/i);
|
||||
const denied = await prisma.auditLog.count({
|
||||
where: {
|
||||
action: "admin.backups.restore.denied",
|
||||
|
||||
Reference in new issue
Block a user