fix(backup): restore the nutrient day totals the export has always carried

The export has written nutrient day totals since v1.29. The canonical schema
never declared the field, so `.passthrough()` carried it through parsing and
no reader ever looked at it: every water and vitamin total in the file was
discarded on restore, and the restore reported success.

Declared in the contract, cleared and re-created beside its siblings, and
counted in both places that count — what the file holds and what the restore
replaced. A count that stops at the domains someone remembered is how this
stayed quiet.
This commit is contained in:
Marc-André Bombeck committed 2026-07-27 12:02:01 +02:00
1 parent b2e19ce0b6
commit d9afb23067
2 files changed
+45

No files matched your search

@@ -58,6 +58,7 @@ interface RestoreResponse {
cycleProfile: number;
labResults: number;
biomarkers: number;
nutrientDays: number;
illnessEpisodes: number;
allergies: number;
familyHistory: number;
@@ -328,6 +329,12 @@ const handler = apiHandler(
const biomarkers = await tx.biomarker.deleteMany({
where: { userId: ownerId },
});
// The export has written nutrient day totals since v1.29 and nothing
// here ever read them back: water and vitamin history was carried in
// the file, discarded on restore, and the restore reported success.
const nutrientDays = await tx.nutrientIntakeDay.deleteMany({
where: { userId: ownerId },
});
if (payload.appSettings) {
const settings = payload.appSettings;
@@ -968,6 +975,19 @@ const handler = apiHandler(
});
}
if (payload.nutrientDays.length > 0) {
await tx.nutrientIntakeDay.createMany({
data: payload.nutrientDays.map((n) => ({
userId: ownerId,
day: n.day,
nutrient: n.nutrient,
amount: n.amount,
unit: n.unit,
source: n.source,
})),
});
}
if (payload.documents.length > 0) {
await tx.inboundDocument.createMany({
data: payload.documents.map((document) => ({
@@ -1018,6 +1038,7 @@ const handler = apiHandler(
cycleProfile: cycleCleared.cycleProfile,
labResults: labResults.count,
biomarkers: biomarkers.count,
nutrientDays: nutrientDays.count,
illnessEpisodes: illnessEpisodes.count,
allergies: allergies.count,
familyHistory: familyHistory.count,
@@ -1140,6 +1161,7 @@ const handler = apiHandler(
cycleDayLogs: summary.cycleDayLogs,
labResults: summary.labResults,
biomarkers: summary.biomarkers,
nutrientDays: summary.nutrientDays,
illnessEpisodes: summary.illnessEpisodes,
illnessDayLogs: summary.illnessDayLogs,
allergies: summary.allergies,
+23
View File
@@ -314,6 +314,26 @@ const cycleProfileSchema = z
})
.passthrough();
/**
* A day's total for one nutrient, from one source.
*
* The export has written these since v1.29 and the canonical schema never
* declared them, so `.passthrough()` carried the key through parsing and no
* reader ever looked at it. The restore therefore dropped every water and
* vitamin total on the floor while reporting success.
*/
const nutrientDaySchema = z
.object({
day: z.string().min(1),
nutrient: z.string().min(1),
amount: z.number(),
unit: z.string().min(1),
// Closed set in app code rather than a DB enum; kept as a string here so a
// future source does not make an old file unparseable.
source: z.string().min(1),
})
.passthrough();
/**
* A symptom the account created, as opposed to the seeded catalogue.
*
@@ -595,6 +615,7 @@ export const backupPayloadSchema = z
familyHistory: z.array(familyHistoryBackupSchema).default([]),
workouts: z.array(workoutBackupSchema).default([]),
documents: z.array(documentBackupSchema).default([]),
nutrientDays: z.array(nutrientDaySchema).default([]),
manifest: backupManifestSchema.nullable().default(null),
})
.passthrough()
@@ -632,6 +653,7 @@ export interface BackupSummary {
cycleDayLogs: number;
/** Lab results in the backup. */
labResults: number;
nutrientDays: number;
/** User-scoped biomarker catalog entries in the backup. */
biomarkers: number;
/** Illness episodes, including flares/exacerbations. */
@@ -659,6 +681,7 @@ export function summarizeBackup(payload: BackupPayload): BackupSummary {
moodEntries: payload.moodEntries.length,
cycles: payload.cycles.length,
cycleDayLogs: payload.cycleDayLogs.length,
nutrientDays: payload.nutrientDays.length,
labResults: payload.labResults.length,
biomarkers: payload.biomarkers.length,
illnessEpisodes: payload.illnessEpisodes.length,