mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-14 22:50:51 +00:00
reconcile: fold in the outstanding hardening and data-integrity fixes
Merge the eight reviewed audit fixes that were cut a week ago and never landed. Main has moved on since, so each fix was re-checked against the current tree and each of the five conflicts resolved by understanding both sides. Kept, still open on main: - Proxy early exits now carry the transport security header subset via a shared helper. Reconciled with main's /api/health worker-only carve-out. - Batch measurement ingest bounds the entry instant so a future-dated row can no longer win "latest reading" or poison the rollup buckets. - Web Push dials through the pinned safeFetch dispatcher with requirePublicHost, closing the last user-controlled-host egress. - Mood bulk upsert and its tag/factor links run in one transaction, so a rejected rating rolls the mood row back instead of leaving a tagless row. - Full backup carries NutrientIntakeDay with its unit and source. Folded into main's portable-export / disaster-recovery split. - Live daily-series fallback restores the min/max spread band the tiered read already emitted. - Coach stream aborts its in-flight reader on unmount and resets send state on a conversation switch. - Manual water writes share the day-bound helper, nutrient add clamps after the increment, and the thiamin cap drops to a real supplement ceiling. - Compliance point window widens symmetrically instead of wrapping overnight. - Oura, Polar and Fitbit token helpers classify an empty or non-JSON 2xx body as a handled transient error. - WebAuthn relying-party fallback to localhost is gated to development. - Chart-token strip list is derived from the allowlist and pinned by a test. - Regression tests pin the snapshot-cache tenant key, the GlitchTip path-secret redaction and the Withings credentials delete. Superseded by main, tests re-pointed at the current shape: - The intake CSV import was rewritten on main to run through a background job with a single-active-job admission gate. The future-date bound is re-applied at request validation and the per-user limiter is kept for parity with the bulk twin; the old synchronous-write test is replaced.
This commit is contained in:
commit
b1e36d9fb0
51 files changed
+2066
-228
No files matched your search
@@ -176,6 +176,17 @@ function makePrisma() {
|
||||
},
|
||||
]),
|
||||
},
|
||||
nutrientIntakeDay: {
|
||||
findMany: vi.fn().mockResolvedValue([
|
||||
{
|
||||
day: "2026-07-19",
|
||||
nutrient: "water",
|
||||
amount: 1500,
|
||||
unit: "ml",
|
||||
source: "MANUAL",
|
||||
},
|
||||
]),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -334,6 +345,15 @@ describe("buildFullBackupPayload disaster-recovery mode", () => {
|
||||
factors: [{ key: "sleep_quality", rating: 4 }],
|
||||
},
|
||||
],
|
||||
nutrientDays: [
|
||||
{
|
||||
day: "2026-07-19",
|
||||
nutrient: "water",
|
||||
amount: 1500,
|
||||
unit: "ml",
|
||||
source: "MANUAL",
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ export interface FullBackupCounts extends RecordsBackupCounts {
|
||||
moodEntries: number;
|
||||
cycles: number;
|
||||
cycleDayLogs: number;
|
||||
nutrientDays: number;
|
||||
}
|
||||
|
||||
export interface FullBackupResult {
|
||||
@@ -59,6 +60,7 @@ export async function buildFullBackupPayload(
|
||||
moodEntries,
|
||||
cycle,
|
||||
records,
|
||||
nutrientDays,
|
||||
] = await Promise.all([
|
||||
disasterRecovery
|
||||
? prisma.appSettings.findUnique({ where: { id: "singleton" } })
|
||||
@@ -165,6 +167,23 @@ export async function buildFullBackupPayload(
|
||||
buildRecordsBackupSection(prisma, userId, {
|
||||
purpose: disasterRecovery ? "disaster-recovery" : "portable-export",
|
||||
}),
|
||||
// Nutrient day totals were absent from every export path, which
|
||||
// contradicted the schema's own reason for denormalising the unit column
|
||||
// ("rows stay self-describing in exports even if the catalog ever drifts").
|
||||
// `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.
|
||||
prisma.nutrientIntakeDay.findMany({
|
||||
where: { userId },
|
||||
select: {
|
||||
day: true,
|
||||
nutrient: true,
|
||||
amount: true,
|
||||
unit: true,
|
||||
source: true,
|
||||
},
|
||||
orderBy: [{ day: "desc" }, { nutrient: "asc" }],
|
||||
}),
|
||||
]);
|
||||
|
||||
const payload = {
|
||||
@@ -290,6 +309,13 @@ export async function buildFullBackupPayload(
|
||||
familyHistory: records.familyHistory,
|
||||
workouts: records.workouts,
|
||||
documents: records.documents,
|
||||
nutrientDays: nutrientDays.map((n) => ({
|
||||
day: n.day,
|
||||
nutrient: n.nutrient,
|
||||
amount: n.amount,
|
||||
unit: n.unit,
|
||||
source: n.source,
|
||||
})),
|
||||
manifest: records.manifest,
|
||||
};
|
||||
|
||||
@@ -302,6 +328,7 @@ export async function buildFullBackupPayload(
|
||||
moodEntries: moodEntries.length,
|
||||
cycles: cycle.cycles.length,
|
||||
cycleDayLogs: cycle.cycleDayLogs.length,
|
||||
nutrientDays: nutrientDays.length,
|
||||
...countRecordsBackupSection(records),
|
||||
},
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user