mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 12:32:43 +00:00
fix(backup): carry the account's own cycle symptoms into the payload
The section builder returned them, the wire schema declared them, and the restore has read them since v1.33.1 — including the throw that replaced the silent drop. The payload assembly copied its keys out one by one and was never extended, so every backup written carried an empty list. An account with a custom symptom did not get the silent drop that release removed; it got a restore that failed outright on a key nothing had written back. Spread the cycle and records sections instead of hand-picking their keys, so a field added to either section interface reaches the wire by construction. Declare labelEncrypted on the cycle section, which the builder already emitted.
This commit is contained in:
1 parent
60c30e1e3a
commit
e5b958cd86
2 files changed
+38
-15
No files matched your search
@@ -108,6 +108,16 @@ export interface CycleBackupSection {
|
||||
icon: string | null;
|
||||
sortOrder: number;
|
||||
isActive: boolean;
|
||||
/**
|
||||
* The user's own words for the symptom, carried as ciphertext verbatim.
|
||||
*
|
||||
* The builder has always emitted it and this interface did not declare it,
|
||||
* which held while the payload was assembled key by key and stopped holding
|
||||
* the moment the section is spread onto the wire: an undeclared field is a
|
||||
* field the wire type says is not there. Declared, so the type describes
|
||||
* the file.
|
||||
*/
|
||||
labelEncrypted: string | null;
|
||||
}>;
|
||||
}
|
||||
|
||||
|
||||
@@ -15,11 +15,15 @@ import type { Prisma, PrismaClient } from "@/generated/prisma/client";
|
||||
import { readNote } from "@/lib/crypto/note-cipher";
|
||||
import { iterateMeasurementPages } from "@/lib/export/paged-measurements";
|
||||
import { BACKUP_SCHEMA_VERSION } from "@/lib/validations/backup";
|
||||
import { buildCycleBackupSection } from "@/lib/cycle/backup";
|
||||
import {
|
||||
buildCycleBackupSection,
|
||||
type CycleBackupSection,
|
||||
} from "@/lib/cycle/backup";
|
||||
import {
|
||||
buildRecordsBackupSection,
|
||||
countRecordsBackupSection,
|
||||
type RecordsBackupCounts,
|
||||
type RecordsBackupSection,
|
||||
} from "@/lib/export/records-backup";
|
||||
|
||||
export interface FullBackupCounts extends RecordsBackupCounts {
|
||||
@@ -210,6 +214,11 @@ export async function buildFullBackupPayload(
|
||||
}),
|
||||
]);
|
||||
|
||||
// Pinned to the section interfaces so the spreads below cannot quietly widen
|
||||
// (or narrow) what reaches the wire.
|
||||
const cycleSection: CycleBackupSection = cycle;
|
||||
const recordsSection: RecordsBackupSection = records;
|
||||
|
||||
const payload = {
|
||||
schemaVersion: BACKUP_SCHEMA_VERSION,
|
||||
exportedAt: (options.exportedAt ?? new Date()).toISOString(),
|
||||
@@ -335,20 +344,24 @@ export async function buildFullBackupPayload(
|
||||
scaleMax: tag.scaleMax,
|
||||
inverse: tag.inverse,
|
||||
})),
|
||||
cycleProfile: cycle.cycleProfile,
|
||||
cycles: cycle.cycles,
|
||||
cycleDayLogs: cycle.cycleDayLogs,
|
||||
// v1.28 backup-completeness — the domains the pre-existing shape never
|
||||
// covered. `manifest` discloses the two deliberate exclusions (document
|
||||
// binaries, workout GPS/sample time series) inline in the file itself,
|
||||
// not just in the export UI copy.
|
||||
labResults: records.labResults,
|
||||
biomarkers: records.biomarkers,
|
||||
illnessEpisodes: records.illnessEpisodes,
|
||||
allergies: records.allergies,
|
||||
familyHistory: records.familyHistory,
|
||||
workouts: records.workouts,
|
||||
documents: records.documents,
|
||||
// Both sections are SPREAD, not hand-picked key by key.
|
||||
//
|
||||
// Hand-picking is what lost the custom cycle symptoms. `buildCycleBackupSection`
|
||||
// returned them, the wire schema declared them, `restoreCycleData` read them
|
||||
// and — since v1.33.1 — THREW on a key it could not resolve. The list of keys
|
||||
// copied out here was simply never extended, so every backup written carried
|
||||
// `customSymptoms: []`. An account with one custom symptom did not get the
|
||||
// silent drop that release removed; it got a restore that failed outright on
|
||||
// a key nothing had written back, and the fix was inert in production.
|
||||
//
|
||||
// Spreading makes the section interface the single declaration of what rides
|
||||
// the wire: a field added to `CycleBackupSection` / `RecordsBackupSection`
|
||||
// reaches the payload by construction rather than by someone remembering.
|
||||
// `manifest` (from the records section) discloses the two deliberate
|
||||
// exclusions — document binaries, workout GPS/sample series — in the file
|
||||
// itself, not just in the export UI copy.
|
||||
...cycleSection,
|
||||
...recordsSection,
|
||||
nutrientDays: nutrientDays.map((n) => ({
|
||||
day: n.day,
|
||||
nutrient: n.nutrient,
|
||||
|
||||
Reference in new issue
Block a user