mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-15 06:52:04 +00:00
buildFullBackupPayload now asks every domain for its slice at once and lays the slices out in the file's key order (backup-export-sections.ts), merging the manifest where it first appears. The measurement, medication, mood, instance-settings and nutrient readers and serialisers move out of the payload builder into their domain modules; the records section keeps its composition and manifest, while labs, illness, allergies, family history, workouts and documents each export from their own module. The measurement restore half moves to measurements-restore.ts so the export graph does not pull in the restore's dependencies. The file, its counts and their order are unchanged; the golden copies still match. The measurement completeness guard now reads the domain module and also checks that every carried column is written back.
108 lines
3.9 KiB
TypeScript
108 lines
3.9 KiB
TypeScript
/**
|
|
* The allergy record, with both backup ends in one file.
|
|
*/
|
|
import { Buffer } from "node:buffer";
|
|
|
|
import type { PrismaClient } from "@/generated/prisma/client";
|
|
import { encryptToBytes } from "@/lib/ai/coach/bytes-codec";
|
|
import { toAllergyDTO, type AllergyDTO } from "@/lib/records/dto";
|
|
import {
|
|
decodeEncryptedBytes,
|
|
type RestoreContext,
|
|
} from "@/lib/export/restore-context";
|
|
|
|
// ── Export ───────────────────────────────────────────────────────────────────
|
|
|
|
export type CanonicalAllergy = AllergyDTO & {
|
|
reactionEncrypted?: string | null;
|
|
notesEncrypted?: string | null;
|
|
deletedAt?: string | null;
|
|
};
|
|
|
|
/** The allergy slice of the records section. */
|
|
export async function buildAllergiesBackupSlice(
|
|
prisma: Pick<PrismaClient, "allergy">,
|
|
userId: string,
|
|
disasterRecovery: boolean,
|
|
): Promise<{ allergies: CanonicalAllergy[] }> {
|
|
const allergyRows = await prisma.allergy.findMany({
|
|
where: disasterRecovery ? { userId } : { userId, deletedAt: null },
|
|
orderBy: { createdAt: "desc" },
|
|
});
|
|
const allergies: CanonicalAllergy[] = disasterRecovery
|
|
? allergyRows.map((row) => ({
|
|
id: row.id,
|
|
substance: row.substance,
|
|
category: row.category,
|
|
type: row.type,
|
|
severity: row.severity,
|
|
status: row.status,
|
|
onsetAt: row.onsetAt?.toISOString() ?? null,
|
|
reaction: null,
|
|
note: null,
|
|
reactionEncrypted: row.reactionEncrypted
|
|
? Buffer.from(row.reactionEncrypted).toString("base64")
|
|
: null,
|
|
notesEncrypted: row.notesEncrypted
|
|
? Buffer.from(row.notesEncrypted).toString("base64")
|
|
: null,
|
|
createdAt: row.createdAt.toISOString(),
|
|
updatedAt: row.updatedAt.toISOString(),
|
|
deletedAt: row.deletedAt?.toISOString() ?? null,
|
|
}))
|
|
: allergyRows.map(toAllergyDTO);
|
|
return { allergies };
|
|
}
|
|
|
|
// ── Restore ──────────────────────────────────────────────────────────────────
|
|
|
|
export async function clearAllergies(ctx: RestoreContext): Promise<void> {
|
|
const allergies = await ctx.tx.allergy.deleteMany({
|
|
where: { userId: ctx.ownerId },
|
|
});
|
|
ctx.cleared.allergies = allergies.count;
|
|
}
|
|
|
|
/** Each free-text column: ciphertext verbatim, or plaintext sealed here. */
|
|
export async function restoreAllergies(ctx: RestoreContext): Promise<void> {
|
|
const { tx, ownerId, payload } = ctx;
|
|
ctx.reportSection("allergies");
|
|
for (const allergy of payload.allergies) {
|
|
await tx.allergy.create({
|
|
data: {
|
|
id: allergy.id,
|
|
userId: ownerId,
|
|
substance: allergy.substance,
|
|
category: allergy.category as never,
|
|
type: allergy.type as never,
|
|
severity: (allergy.severity ?? null) as never,
|
|
status: allergy.status as never,
|
|
onsetAt: allergy.onsetAt ? new Date(allergy.onsetAt) : null,
|
|
reactionEncrypted:
|
|
allergy.reactionEncrypted !== undefined
|
|
? allergy.reactionEncrypted === null
|
|
? null
|
|
: decodeEncryptedBytes(allergy.reactionEncrypted)
|
|
: allergy.reaction == null
|
|
? null
|
|
: encryptToBytes(allergy.reaction),
|
|
notesEncrypted:
|
|
allergy.notesEncrypted !== undefined
|
|
? allergy.notesEncrypted === null
|
|
? null
|
|
: decodeEncryptedBytes(allergy.notesEncrypted)
|
|
: allergy.note == null
|
|
? null
|
|
: encryptToBytes(allergy.note),
|
|
deletedAt: allergy.deletedAt ? new Date(allergy.deletedAt) : null,
|
|
...(allergy.createdAt
|
|
? { createdAt: new Date(allergy.createdAt) }
|
|
: {}),
|
|
...(allergy.updatedAt
|
|
? { updatedAt: new Date(allergy.updatedAt) }
|
|
: {}),
|
|
},
|
|
});
|
|
}
|
|
}
|