Files
HealthLog/src/lib/export/allergies-backup.ts
T
Marc-André Bombeck 322652a86e refactor(backup): build the export from per-domain slices
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.
2026-10-22 07:46:02 +02:00

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) }
: {}),
},
});
}
}