mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 20:42:43 +00:00
fix(backup): carry four columns the restore was dropping
A column-level round trip over every two-ended model found four values that left with the export and came back empty or re-stamped: - MenstrualCycle.absorbedIntoId: the disaster-recovery file carried the folded, soft-deleted start but not the start that absorbed it, so after a restore removing the absorbing start could no longer bring the folded one back. Carried in the disaster-recovery arm and written back only when the file carries the start it names. - Workout.externalSourceVersion: the source's version marker, stored so the sync can replay it unchanged, was in neither end. Carried beside metadata in the disaster-recovery arm. - NutrientIntakeDay.externalSourceVersion and createdAt: neither was selected, so a restored day lost its source marker and its first-arrival time. Carried in both arms. Files written before this change parse as before; every new field is optional on the way in.
This commit is contained in:
1 parent
f3bf65fb4e
commit
3f5bcbd441
5 files changed
+35
No files matched your search
@@ -64,6 +64,12 @@ export interface CycleBackupSection {
|
||||
isPredicted?: boolean;
|
||||
syncVersion?: number;
|
||||
deletedAt?: string | null;
|
||||
/**
|
||||
* Disaster recovery only: the start that folded this one in. A bare id with
|
||||
* no foreign key, meaningful only because the same file carries the ids of
|
||||
* the starts it names.
|
||||
*/
|
||||
absorbedIntoId?: string | null;
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
}>;
|
||||
@@ -214,6 +220,7 @@ export async function buildCycleBackupSection(
|
||||
isPredicted: c.isPredicted,
|
||||
syncVersion: c.syncVersion,
|
||||
deletedAt: c.deletedAt?.toISOString() ?? null,
|
||||
absorbedIntoId: c.absorbedIntoId,
|
||||
createdAt: c.createdAt.toISOString(),
|
||||
updatedAt: c.updatedAt.toISOString(),
|
||||
}
|
||||
@@ -432,6 +439,12 @@ export async function restoreCycleData(
|
||||
// re-attach to their owning span.
|
||||
const cycleIdByStart = new Map<string, string>();
|
||||
const restoredCycleIds = new Set<string>();
|
||||
// A folded start names the start that absorbed it. Kept only when that start
|
||||
// is in the same file under the same id; a pointer at nothing would stop the
|
||||
// folded row from ever coming back, which is what the column is for.
|
||||
const carriedCycleIds = new Set(
|
||||
payload.cycles.flatMap((c) => (c.id ? [c.id] : [])),
|
||||
);
|
||||
for (const c of payload.cycles) {
|
||||
const created = await tx.menstrualCycle.create({
|
||||
data: {
|
||||
@@ -447,6 +460,10 @@ export async function restoreCycleData(
|
||||
isPredicted: c.isPredicted ?? false,
|
||||
syncVersion: c.syncVersion ?? 0,
|
||||
deletedAt: c.deletedAt ? new Date(c.deletedAt) : null,
|
||||
absorbedIntoId:
|
||||
c.absorbedIntoId && carriedCycleIds.has(c.absorbedIntoId)
|
||||
? c.absorbedIntoId
|
||||
: null,
|
||||
...(c.createdAt ? { createdAt: new Date(c.createdAt) } : {}),
|
||||
...(c.updatedAt ? { updatedAt: new Date(c.updatedAt) } : {}),
|
||||
},
|
||||
|
||||
@@ -887,6 +887,9 @@ export async function buildFullBackupPayload(
|
||||
// `source` is part of the composite PK, so it has to ride along or a
|
||||
// restore cannot tell a manual water entry from a synced day total. The
|
||||
// table carries no tombstone, so both purposes read the same shape.
|
||||
// `externalSourceVersion` is the source's own version marker, stored so
|
||||
// the sync can replay it back unchanged; `createdAt` is when the day
|
||||
// first arrived. Neither can be rebuilt after a restore.
|
||||
prisma.nutrientIntakeDay.findMany({
|
||||
where: { userId },
|
||||
select: {
|
||||
@@ -895,6 +898,8 @@ export async function buildFullBackupPayload(
|
||||
amount: true,
|
||||
unit: true,
|
||||
source: true,
|
||||
externalSourceVersion: true,
|
||||
createdAt: true,
|
||||
},
|
||||
orderBy: [{ day: "desc" }, { nutrient: "asc" }],
|
||||
}),
|
||||
@@ -1293,6 +1298,8 @@ export async function buildFullBackupPayload(
|
||||
amount: n.amount,
|
||||
unit: n.unit,
|
||||
source: n.source,
|
||||
externalSourceVersion: n.externalSourceVersion,
|
||||
createdAt: n.createdAt.toISOString(),
|
||||
})),
|
||||
};
|
||||
|
||||
|
||||
@@ -142,6 +142,8 @@ export interface WorkoutBackupEntry {
|
||||
externalId: string | null;
|
||||
/** Canonical DR only; portable workout summaries keep their existing shape. */
|
||||
metadata?: Prisma.JsonValue;
|
||||
/** Canonical DR only, beside `metadata`: the source's own version marker. */
|
||||
externalSourceVersion?: string | null;
|
||||
}
|
||||
|
||||
export interface DocumentBackupEntry {
|
||||
@@ -516,6 +518,7 @@ export async function buildRecordsBackupSection(
|
||||
createdAt: w.createdAt.toISOString(),
|
||||
updatedAt: w.updatedAt.toISOString(),
|
||||
...(w.metadata == null ? {} : { metadata: w.metadata }),
|
||||
externalSourceVersion: w.externalSourceVersion,
|
||||
}
|
||||
: {}),
|
||||
sportType: w.sportType,
|
||||
|
||||
@@ -2139,6 +2139,7 @@ export async function restoreBackup(
|
||||
pauseDurationSec: workout.pauseDurationSec ?? null,
|
||||
source: workout.source as never,
|
||||
externalId: workout.externalId ?? null,
|
||||
externalSourceVersion: workout.externalSourceVersion ?? null,
|
||||
...(workout.metadata == null
|
||||
? {}
|
||||
: { metadata: toJson(workout.metadata) }),
|
||||
@@ -2161,6 +2162,8 @@ export async function restoreBackup(
|
||||
amount: n.amount,
|
||||
unit: n.unit,
|
||||
source: n.source,
|
||||
externalSourceVersion: n.externalSourceVersion ?? null,
|
||||
...(n.createdAt ? { createdAt: new Date(n.createdAt) } : {}),
|
||||
})),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -559,6 +559,7 @@ const cycleSpanSchema = z
|
||||
tz: z.string().nullable().optional(),
|
||||
syncVersion: z.number().int().optional(),
|
||||
deletedAt: isoDateTime.nullable().optional(),
|
||||
absorbedIntoId: z.string().min(1).nullable().optional(),
|
||||
createdAt: isoDateTime.optional(),
|
||||
updatedAt: isoDateTime.optional(),
|
||||
})
|
||||
@@ -691,6 +692,9 @@ const nutrientDaySchema = z
|
||||
// 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),
|
||||
// Absent from files written before v1.39.7.
|
||||
externalSourceVersion: z.string().nullable().optional(),
|
||||
createdAt: isoDateTime.optional(),
|
||||
})
|
||||
.passthrough();
|
||||
|
||||
@@ -1641,6 +1645,7 @@ const workoutBackupSchema = z
|
||||
pauseDurationSec: z.number().int().nullable().optional(),
|
||||
source: z.enum(MeasurementSource),
|
||||
externalId: z.string().nullable().optional(),
|
||||
externalSourceVersion: z.string().nullable().optional(),
|
||||
metadata: z.json().optional(),
|
||||
createdAt: isoDateTime.optional(),
|
||||
updatedAt: isoDateTime.optional(),
|
||||
|
||||
Reference in new issue
Block a user