Merge remote-tracking branch 'origin/work/side-effect-backup' into work/sharing-write-integration

This commit is contained in:
Marc-André Bombeck committed 2026-08-03 21:18:13 +02:00
commit 7eed26eb01
10 files changed
+297 -12

No files matched your search

@@ -2,10 +2,11 @@
* POST /api/admin/backups/[id]/restore — admin-only disaster recovery.
*
* After owner and schema validation, one transaction replaces every
* serialized owner-scoped class: measurements, medication history, mood and
* rated factors, cycle data, labs/biomarkers, illness history, allergies,
* family history, workout summaries, and inbound documents. Document content
* and summary ciphertext are decoded from base64 and persisted verbatim.
* serialized owner-scoped class: measurements, medication history including the
* side effects recorded against a drug, mood and rated factors, cycle data,
* labs/biomarkers, illness history, allergies, family history, workout
* summaries, and inbound documents. Document content and summary ciphertext are
* decoded from base64 and persisted verbatim.
*
* Metadata-only portable document exports are rejected before mutation; the
* importer never fabricates content. Audit rows remain outside the wipe, and
@@ -584,6 +585,32 @@ const handler = apiHandler(
doseWindows: (s.doseWindows ?? null) as never,
})),
},
// Written in the same `create` as the drug, so the FK binds to
// the id this row actually got. A canonical DR file preserves
// the medication's id and a portable one mints a fresh cuid;
// neither case needs the old `medicationId` from the file,
// which is why the payload does not carry it. The note follows
// the measurement contract next to it: ciphertext verbatim when
// the file has it, legacy plaintext encrypted on the way in,
// and the plaintext column left null either way.
sideEffects: {
create: m.sideEffects.map((s) => ({
...(s.id ? { id: s.id } : {}),
userId: ownerId,
occurredAt: new Date(s.occurredAt),
category: s.category,
entry: s.entry,
severity: s.severity,
notes: null,
notesEncrypted:
s.notesEncrypted == null
? encryptNote(s.notes ?? null)
: decodeEncryptedBytes(s.notesEncrypted),
...(s.createdAt
? { createdAt: new Date(s.createdAt) }
: {}),
})),
},
},
});
restoredMedicationIds.add(created.id);
+1
View File
@@ -105,6 +105,7 @@ export const POST = apiHandler(async (request: NextRequest) => {
export_measurements_count: counts.measurements,
export_medications_count: counts.medications,
export_intake_count: counts.intakeEvents,
export_medication_side_effect_count: counts.medicationSideEffects,
export_mood_count: counts.moodEntries,
export_cycle_count: counts.cycles,
export_cycle_day_log_count: counts.cycleDayLogs,
+1
View File
@@ -64,6 +64,7 @@ export const GET = apiHandler(async (request: NextRequest) => {
export_measurements_count: counts.measurements,
export_medications_count: counts.medications,
export_intake_count: counts.intakeEvents,
export_medication_side_effect_count: counts.medicationSideEffects,
export_mood_count: counts.moodEntries,
export_cycle_count: counts.cycles,
export_cycle_day_log_count: counts.cycleDayLogs,