mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 14:16:12 +00:00
feat(db): add the v1.43 schema and drop the readable location columns
Migrations 0402 to 0410: AI usage levels, biomarker aliases and merges, medication ingredients (with the supplement label run kind), life circumstance fact kinds, AI sections and AUDIT-C, Dawarich context, the pulse day-mean method, intraday hour buckets, the readable location drop and five relation link tables. Every new column carries its reader or a pending-consumer marker. 0409 refuses to run while any home, location period or stored day still holds a readable location, and names the counts. Locations are read from the sealed copy only; the encryption backfill no longer handles them, and the demo seed writes sealed locations only. The new tables are backed up and wiped, the new sealed columns are in the rotation registry with labels of their own, the AI usage level and the life-circumstance confirmations travel with the backup, and the wellbeing score reads the mental-health screeners only.
This commit is contained in:
1 parent
0b64b80343
commit
6e72de1c35
45 files changed
+1386
-917
No files matched your search
@@ -394,17 +394,13 @@ describe("the environment home (v1.42, sealed at rest)", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("seals a readable home from a file and writes the readable columns empty", () => {
|
||||
it("seals a readable home from a file", () => {
|
||||
const { data, refused } = admitAccountSettings(
|
||||
{ homeLat: 48.137, homeLon: 11.575, homeLabel: "Munich" },
|
||||
ctx,
|
||||
);
|
||||
expect(refused).toEqual([]);
|
||||
expect(data).toMatchObject({
|
||||
homeLat: null,
|
||||
homeLon: null,
|
||||
homeLabel: null,
|
||||
});
|
||||
expect(data).not.toHaveProperty("homeLat");
|
||||
expect(openLocation(data.homeLocationEncrypted as Uint8Array)).toEqual({
|
||||
lat: 48.1,
|
||||
lon: 11.6,
|
||||
@@ -421,11 +417,6 @@ describe("the environment home (v1.42, sealed at rest)", () => {
|
||||
expect(Buffer.from(restored).equals(Buffer.from(sealed))).toBe(true);
|
||||
expect(
|
||||
admitAccountSettings({ homeLat: null, homeLon: null }, ctx).data,
|
||||
).toEqual({
|
||||
homeLocationEncrypted: null,
|
||||
homeLat: null,
|
||||
homeLon: null,
|
||||
homeLabel: null,
|
||||
});
|
||||
).toEqual({ homeLocationEncrypted: null });
|
||||
});
|
||||
});
|
||||
@@ -41,6 +41,7 @@ import { Buffer } from "node:buffer";
|
||||
|
||||
import { Prisma, type PrismaClient } from "@/generated/prisma/client";
|
||||
import {
|
||||
AiUsageLevel,
|
||||
DateFormatPreference,
|
||||
InjectionSite,
|
||||
TimeFormatPreference,
|
||||
@@ -384,21 +385,19 @@ const insuranceNumber: SettingCodec = {
|
||||
|
||||
/**
|
||||
* v1.42 (#615) — the environment home, sealed at rest as one value
|
||||
* (`homeLocationEncrypted`, see `src/lib/environment/location-cipher.ts`)
|
||||
* beside the readable `homeLat` / `homeLon` / `homeLabel` that the
|
||||
* encryption backfill empties. The four columns travel as one, the way the
|
||||
* avatar's three do:
|
||||
* (`homeLocationEncrypted`, see `src/lib/environment/location-cipher.ts`).
|
||||
* The readable `homeLat` / `homeLon` / `homeLabel` columns were dropped in
|
||||
* v1.43; the three keys live on in the file:
|
||||
*
|
||||
* - a portable file carries the home readable, opened from the sealed copy
|
||||
* when there is one, and no sealed value;
|
||||
* - a disaster-recovery file carries the sealed value verbatim (base64)
|
||||
* beside whatever the readable columns still hold.
|
||||
* - a portable file carries the home readable (`homeLat` / `homeLon` /
|
||||
* `homeLabel`), opened from the sealed copy, and no sealed value;
|
||||
* - a disaster-recovery file carries the sealed value verbatim (base64).
|
||||
*
|
||||
* The restore writes the sealed value as it came, or seals a readable home
|
||||
* under this host's key, and writes the readable columns empty either way. A
|
||||
* file whose home is null clears the account's home. A home without a label
|
||||
* (allowed since v1.25) is sealed with an empty label, which every reader
|
||||
* shows as no label.
|
||||
* (a portable file, or a disaster-recovery file written before v1.43) under
|
||||
* this host's key. A file whose home is null clears the account's home. A
|
||||
* home without a label (allowed since v1.25) is sealed with an empty label,
|
||||
* which every reader shows as no label.
|
||||
*/
|
||||
const homeLocation: SettingCodec = {
|
||||
write: (row, disasterRecovery) => {
|
||||
@@ -408,21 +407,13 @@ const homeLocation: SettingCodec = {
|
||||
: null;
|
||||
if (disasterRecovery) {
|
||||
return {
|
||||
homeLat: row.homeLat,
|
||||
homeLon: row.homeLon,
|
||||
homeLabel: row.homeLabel,
|
||||
homeLocationEncrypted:
|
||||
sealed && sealed.byteLength > 0
|
||||
? Buffer.from(sealed).toString("base64")
|
||||
: null,
|
||||
};
|
||||
}
|
||||
const home = readLocation({
|
||||
sealed,
|
||||
lat: row.homeLat as number | null,
|
||||
lon: row.homeLon as number | null,
|
||||
label: (row.homeLabel as string | null) ?? "",
|
||||
});
|
||||
const home = readLocation(sealed);
|
||||
return home
|
||||
? {
|
||||
homeLat: home.lat,
|
||||
@@ -432,12 +423,7 @@ const homeLocation: SettingCodec = {
|
||||
: { homeLat: null, homeLon: null, homeLabel: null };
|
||||
},
|
||||
read: (entry) => {
|
||||
const cleared = {
|
||||
homeLocationEncrypted: null,
|
||||
homeLat: null,
|
||||
homeLon: null,
|
||||
homeLabel: null,
|
||||
};
|
||||
const cleared = { homeLocationEncrypted: null };
|
||||
const sealedValue = entry.homeLocationEncrypted;
|
||||
if (has(entry, "homeLocationEncrypted") && sealedValue !== null) {
|
||||
if (typeof sealedValue !== "string") {
|
||||
@@ -471,12 +457,6 @@ const homeLocation: SettingCodec = {
|
||||
},
|
||||
};
|
||||
|
||||
/** Carried by the home codec above. */
|
||||
const ridesWithHome: SettingCodec = {
|
||||
write: () => ({}),
|
||||
read: () => undefined,
|
||||
};
|
||||
|
||||
const ACCOUNT_SETTING_CODECS: Readonly<
|
||||
Record<AccountSettingColumn, SettingCodec>
|
||||
> = {
|
||||
@@ -493,9 +473,6 @@ const ACCOUNT_SETTING_CODECS: Readonly<
|
||||
avatarBytes: avatar,
|
||||
avatarContentType: ridesWithAvatar,
|
||||
avatarUpdatedAt: ridesWithAvatar,
|
||||
homeLat: ridesWithHome,
|
||||
homeLon: ridesWithHome,
|
||||
homeLabel: ridesWithHome,
|
||||
homeTimezone: timeZone("homeTimezone", true),
|
||||
homeSince: instant("homeSince"),
|
||||
homeLocationEncrypted: homeLocation,
|
||||
@@ -545,6 +522,7 @@ const ACCOUNT_SETTING_CODECS: Readonly<
|
||||
),
|
||||
insightsExcludeMetrics: plain("insightsExcludeMetrics"),
|
||||
disableCoach: plain("disableCoach"),
|
||||
aiUsageLevel: oneOf("aiUsageLevel", Object.values(AiUsageLevel), true),
|
||||
coachPrefsJson: json("coachPrefsJson"),
|
||||
documentsAutoAiRead: plain("documentsAutoAiRead"),
|
||||
labsLocalOcrEnabled: plain("labsLocalOcrEnabled"),
|
||||
|
||||
@@ -205,6 +205,21 @@ export const BACKED_UP_MODELS = [
|
||||
// restore without the row opens a blank questionnaire for somebody who has
|
||||
// already answered it.
|
||||
"OnboardingRecord",
|
||||
|
||||
// ── v1.43 ─────────────────────────────────────────────────────────────────
|
||||
// A marker's other spellings and its merge ledger, a supplement's
|
||||
// composition, the Dawarich daily values, and the five relation links that
|
||||
// close the relation matrix. Each is owned data nothing recomputes; each is
|
||||
// on COVERAGE_PENDING until its section carries it.
|
||||
"BiomarkerAlias",
|
||||
"BiomarkerMerge",
|
||||
"MedicationIngredient",
|
||||
"DawarichDay",
|
||||
"LabResultDocumentLink",
|
||||
"EncounterMedicationLink",
|
||||
"EncounterVaccinationLink",
|
||||
"MedicationDocumentLink",
|
||||
"MedicationConditionLink",
|
||||
] as const;
|
||||
|
||||
/**
|
||||
@@ -597,6 +612,26 @@ export const STRUCTURALLY_UNATTRIBUTABLE: Readonly<Record<string, string>> = {
|
||||
* rather than by what is cheapest to write.
|
||||
*/
|
||||
export const COVERAGE_PENDING: Readonly<Record<string, string>> = {
|
||||
// v1.43 — the new tables are declared here with the schema and move to
|
||||
// TWO_ENDED_MODELS when their section writer and restore branch land.
|
||||
BiomarkerAlias:
|
||||
"Every other spelling a marker answers to: the names folded in by a merge, the choices made at a scan or a document import, the ones typed on the marker. Without them a restored account asks again at the next scan which marker each spelling belongs to.",
|
||||
BiomarkerMerge:
|
||||
"The ledger of which markers were merged into which. Without it a link or a cached screen that still names a merged marker reads as a plain 404 instead of being sent to the marker it went into.",
|
||||
MedicationIngredient:
|
||||
"What one tablet or capsule of a supplement contains. Without it a restored account's supplements stop counting toward the day's micronutrients, and the composition has to be typed in again from the label.",
|
||||
DawarichDay:
|
||||
"The daily movement values fetched from Dawarich: distances, places visited, time away from home, travel days. Days the Dawarich instance has since pruned cannot be fetched again, so they are lost for good.",
|
||||
LabResultDocumentLink:
|
||||
"Which stored report a lab result was read from. Without it the result and the report stop pointing at each other, and the result loses its source.",
|
||||
EncounterMedicationLink:
|
||||
"Which medications a visit prescribed, changed or reviewed. Without it the visit and the medication stop pointing at each other.",
|
||||
EncounterVaccinationLink:
|
||||
"Which doses were given at a visit. Without it the visit and the dose stop pointing at each other.",
|
||||
MedicationDocumentLink:
|
||||
"Which documents belong to a medication: the prescription, the plan, the package insert. Without it they stop pointing at each other.",
|
||||
MedicationConditionLink:
|
||||
"Which condition a medication treats (its indication). Without it the medication and the condition stop pointing at each other.",
|
||||
WorkoutRoute:
|
||||
"GPS traces. Deliberately absent from the payload today and DISCLOSED as absent in the file's own manifest, which is why this is a documented exclusion rather than a silent one — but it is still a loss for a self-hoster with no other copy.",
|
||||
WorkoutSamples:
|
||||
@@ -879,6 +914,13 @@ export const USER_COLUMN_BACKUP_CLASS = {
|
||||
nightscoutUrlEncrypted: "CREDENTIAL",
|
||||
nightscoutTokenEncrypted: "CREDENTIAL",
|
||||
nightscoutAllowPrivateHost: "CREDENTIAL",
|
||||
// v1.43 — the Dawarich connection, credentials like the Nightscout pair. The
|
||||
// sealed preferences name a place id on that instance, which means nothing
|
||||
// without the key, so they stay behind with it.
|
||||
dawarichUrlEncrypted: "CREDENTIAL",
|
||||
dawarichApiKeyEncrypted: "CREDENTIAL",
|
||||
dawarichPrefsEncrypted: "CREDENTIAL",
|
||||
dawarichAllowPrivateHost: "CREDENTIAL",
|
||||
polarAccessTokenEncrypted: "CREDENTIAL",
|
||||
polarUserIdEncrypted: "CREDENTIAL",
|
||||
polarClientIdEncrypted: "CREDENTIAL",
|
||||
@@ -935,13 +977,10 @@ export const USER_COLUMN_BACKUP_CLASS = {
|
||||
avatarContentType: "SETTING",
|
||||
avatarUpdatedAt: "SETTING",
|
||||
// Home, which the environment history is resolved against.
|
||||
homeLat: "SETTING",
|
||||
homeLon: "SETTING",
|
||||
homeLabel: "SETTING",
|
||||
homeTimezone: "SETTING",
|
||||
homeSince: "SETTING",
|
||||
// v1.42 (#615) — the sealed copy of the same home location, and the
|
||||
// account's switch for the air-quality part of the module.
|
||||
// v1.42 (#615) — the sealed home location (the only copy since v1.43),
|
||||
// and the account's switch for the air-quality part of the module.
|
||||
homeLocationEncrypted: "SETTING",
|
||||
environmentAirQualityEnabled: "SETTING",
|
||||
// How the interface reads.
|
||||
@@ -983,6 +1022,10 @@ export const USER_COLUMN_BACKUP_CLASS = {
|
||||
insightsPrivacyMode: "SETTING",
|
||||
insightsExcludeMetrics: "SETTING",
|
||||
disableCoach: "SETTING",
|
||||
// v1.43 (#1217) — the account's own AI usage level; NULL follows the
|
||||
// instance default, and a restored level above this host's ceiling is
|
||||
// capped at read time like any stored choice.
|
||||
aiUsageLevel: "SETTING",
|
||||
coachPrefsJson: "SETTING",
|
||||
documentsAutoAiRead: "SETTING",
|
||||
labsLocalOcrEnabled: "SETTING",
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
* the environmental readings mean anything", and the second line is the one
|
||||
* that decides the design.
|
||||
*
|
||||
* A reading carries the coarse location it was fetched for (`lat`, `lon`,
|
||||
* `locationLabel`) and the precedence rule that chose it (`source`). A
|
||||
* A reading carries the coarse location it was fetched for (sealed; `lat`,
|
||||
* `lon`, `locationLabel` in a portable file) and the precedence rule that chose it (`source`). A
|
||||
* `TRAVEL` reading exists because an explicit dated location period covered
|
||||
* that day. `resolveLocationForDay` in `src/lib/environment/service.ts` reads
|
||||
* those periods live, every time; nothing else can produce a `TRAVEL`
|
||||
@@ -60,14 +60,13 @@
|
||||
* - a portable export opens the sealed location and writes the readable
|
||||
* `lat` / `lon` / label, and no `locationEncrypted` at all, so the file
|
||||
* reads anywhere;
|
||||
* - a disaster-recovery file carries `locationEncrypted` verbatim (base64)
|
||||
* beside whatever readable columns the row still holds, for a host with
|
||||
* the same keys.
|
||||
* - a disaster-recovery file carries `locationEncrypted` verbatim (base64),
|
||||
* for a host with the same keys; its readable fields are null.
|
||||
*
|
||||
* The restore takes either: a sealed value is written as it came, a readable
|
||||
* one is sealed under this host's key, and the readable columns are written
|
||||
* empty in both cases. A row that holds neither restores without a location;
|
||||
* it is never invented.
|
||||
* one (a portable file, or a disaster-recovery file written before v1.43) is
|
||||
* sealed under this host's key. A row that holds neither restores without a
|
||||
* location; it is never invented.
|
||||
*
|
||||
* The air-quality columns (v1.42) are plain values and ride both purposes
|
||||
* alike. Every one is optional on the way in: a file written before them
|
||||
@@ -129,8 +128,8 @@ export interface EnvironmentContextBackupEntry extends AirQualityBackupColumns {
|
||||
/** `YYYY-MM-DD`, anchored to the resolved location's timezone. */
|
||||
date: string;
|
||||
/**
|
||||
* Plaintext coarse location. Nullable since v1.42 (migration 0382): the
|
||||
* sealed copy replaces it once the encryption backfill has run.
|
||||
* Readable coarse location: set in a portable file, null in a
|
||||
* disaster-recovery file (which carries `locationEncrypted`).
|
||||
*/
|
||||
lat: number | null;
|
||||
lon: number | null;
|
||||
@@ -169,7 +168,7 @@ export interface EnvironmentTravelLocationBackupEntry {
|
||||
/** Inclusive `YYYY-MM-DD` bounds. Strings end to end; see the file header. */
|
||||
startDate: string;
|
||||
endDate: string;
|
||||
/** Plaintext coarse location; nullable since v1.42 (migration 0382). */
|
||||
/** Readable coarse location; set in a portable file, null otherwise. */
|
||||
lat: number | null;
|
||||
lon: number | null;
|
||||
label: string | null;
|
||||
@@ -198,9 +197,6 @@ export interface EnvironmentBackupCounts {
|
||||
*/
|
||||
const ENVIRONMENT_CONTEXT_BACKUP_SELECT = {
|
||||
date: true,
|
||||
lat: true,
|
||||
lon: true,
|
||||
locationLabel: true,
|
||||
locationEncrypted: true,
|
||||
source: true,
|
||||
tempMin: true,
|
||||
@@ -245,9 +241,6 @@ const ENVIRONMENT_CONTEXT_BACKUP_SELECT = {
|
||||
const ENVIRONMENT_TRAVEL_LOCATION_BACKUP_SELECT = {
|
||||
startDate: true,
|
||||
endDate: true,
|
||||
lat: true,
|
||||
lon: true,
|
||||
label: true,
|
||||
locationEncrypted: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
@@ -261,16 +254,12 @@ function toBase64(bytes: Uint8Array | null): string | null {
|
||||
}
|
||||
|
||||
/**
|
||||
* The location columns of one entry: opened and readable for a portable
|
||||
* file, as stored plus the sealed value for a disaster-recovery file.
|
||||
* The location fields of one entry: opened and readable for a portable file;
|
||||
* the sealed value for a disaster-recovery file, whose readable fields stay
|
||||
* null (the database holds no readable copy since v1.43).
|
||||
*/
|
||||
function locationFields(
|
||||
row: {
|
||||
lat: number | null;
|
||||
lon: number | null;
|
||||
label: string | null;
|
||||
locationEncrypted: Uint8Array | null;
|
||||
},
|
||||
sealed: Uint8Array | null,
|
||||
disasterRecovery: boolean,
|
||||
): {
|
||||
lat: number | null;
|
||||
@@ -280,19 +269,13 @@ function locationFields(
|
||||
} {
|
||||
if (disasterRecovery) {
|
||||
return {
|
||||
lat: row.lat,
|
||||
lon: row.lon,
|
||||
label: row.label,
|
||||
locationEncrypted: toBase64(row.locationEncrypted),
|
||||
lat: null,
|
||||
lon: null,
|
||||
label: null,
|
||||
locationEncrypted: toBase64(sealed),
|
||||
};
|
||||
}
|
||||
const location = readLocation({
|
||||
sealed: row.locationEncrypted,
|
||||
lat: row.lat,
|
||||
lon: row.lon,
|
||||
label: row.label,
|
||||
});
|
||||
return location ?? { lat: null, lon: null, label: null };
|
||||
return readLocation(sealed) ?? { lat: null, lon: null, label: null };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -326,12 +309,7 @@ export async function buildEnvironmentBackupSection(
|
||||
return {
|
||||
environmentContexts: contextRows.map((row) => {
|
||||
const { label, ...location } = locationFields(
|
||||
{
|
||||
lat: row.lat,
|
||||
lon: row.lon,
|
||||
label: row.locationLabel,
|
||||
locationEncrypted: row.locationEncrypted,
|
||||
},
|
||||
row.locationEncrypted,
|
||||
disasterRecovery,
|
||||
);
|
||||
return {
|
||||
@@ -381,7 +359,7 @@ export async function buildEnvironmentBackupSection(
|
||||
environmentTravelLocations: travelRows.map((row) => ({
|
||||
startDate: row.startDate,
|
||||
endDate: row.endDate,
|
||||
...locationFields(row, disasterRecovery),
|
||||
...locationFields(row.locationEncrypted, disasterRecovery),
|
||||
createdAt: row.createdAt.toISOString(),
|
||||
updatedAt: row.updatedAt.toISOString(),
|
||||
})),
|
||||
@@ -543,9 +521,6 @@ export async function restoreEnvironmentData(
|
||||
`environmentTravelLocations.${entry.startDate}..${entry.endDate}`,
|
||||
unopened,
|
||||
),
|
||||
lat: null,
|
||||
lon: null,
|
||||
label: null,
|
||||
...(entry.createdAt ? { createdAt: new Date(entry.createdAt) } : {}),
|
||||
...(entry.updatedAt ? { updatedAt: new Date(entry.updatedAt) } : {}),
|
||||
})),
|
||||
@@ -567,9 +542,6 @@ export async function restoreEnvironmentData(
|
||||
`environmentContexts.${entry.date}`,
|
||||
unopened,
|
||||
),
|
||||
lat: null,
|
||||
lon: null,
|
||||
locationLabel: null,
|
||||
source: entry.source,
|
||||
tempMin: entry.tempMin ?? null,
|
||||
tempMax: entry.tempMax ?? null,
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
*/
|
||||
import { Buffer } from "node:buffer";
|
||||
|
||||
import type { Prisma, PrismaClient } from "@/generated/prisma/client";
|
||||
import { Prisma, type PrismaClient } from "@/generated/prisma/client";
|
||||
import { decryptFromBytes, encryptToBytes } from "@/lib/ai/coach/bytes-codec";
|
||||
import { encryptNote } from "@/lib/crypto/note-cipher";
|
||||
import { getEvent } from "@/lib/logging/context";
|
||||
@@ -47,6 +47,19 @@ export interface ProfileBackupOptions {
|
||||
* The account's durable self-context. One row per user, so there is no
|
||||
* reference to resolve on the way back in — the owner is the key.
|
||||
*/
|
||||
/**
|
||||
* The stored section confirmations as the file carries them: an object of
|
||||
* strings, anything else read as none.
|
||||
*/
|
||||
function lifeConfirmed(value: unknown): Record<string, string> | null {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
||||
const out: Record<string, string> = {};
|
||||
for (const [key, stamp] of Object.entries(value)) {
|
||||
if (typeof stamp === "string") out[key] = stamp;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
export interface HealthProfileBackupEntry {
|
||||
/** Present in canonical DR payloads so the row keeps a stable identity. */
|
||||
id?: string;
|
||||
@@ -91,6 +104,12 @@ export interface HealthProfileBackupEntry {
|
||||
* state it was in, pending prompts included.
|
||||
*/
|
||||
pendingQuestionsEncrypted?: string | null;
|
||||
/**
|
||||
* v1.43 (#614) — when each life-circumstance section was last confirmed as
|
||||
* still true: section key to ISO instant. Plaintext keys and stamps only,
|
||||
* carried by value in both purposes. Absent in files written before v1.43.
|
||||
*/
|
||||
lifeConfirmedJson?: Record<string, string> | null;
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
}
|
||||
@@ -329,6 +348,7 @@ export async function buildProfileBackupSection(
|
||||
HealthProfileAiSection[] | undefined) ?? [
|
||||
...DEFAULT_HEALTH_PROFILE_AI_SECTIONS,
|
||||
],
|
||||
lifeConfirmedJson: lifeConfirmed(profileRow.lifeConfirmedJson),
|
||||
emergencyBloodType: profileRow.emergencyBloodType ?? null,
|
||||
organDonorStatus: profileRow.organDonorStatus ?? null,
|
||||
advanceDirectiveStatus: profileRow.advanceDirectiveStatus ?? null,
|
||||
@@ -377,6 +397,7 @@ export async function buildProfileBackupSection(
|
||||
HealthProfileAiSection[] | undefined) ?? [
|
||||
...DEFAULT_HEALTH_PROFILE_AI_SECTIONS,
|
||||
],
|
||||
lifeConfirmedJson: lifeConfirmed(profileRow.lifeConfirmedJson),
|
||||
emergencyBloodType: profileRow.emergencyBloodType ?? null,
|
||||
organDonorStatus: profileRow.organDonorStatus ?? null,
|
||||
advanceDirectiveStatus: profileRow.advanceDirectiveStatus ?? null,
|
||||
@@ -788,6 +809,9 @@ export async function restoreProfileData(
|
||||
// resolution as the self-context columns above.
|
||||
emergencyBloodType: p.emergencyBloodType,
|
||||
organDonorStatus: p.organDonorStatus,
|
||||
// A file written before v1.43 says nothing about confirmations; the
|
||||
// account then has none, which only re-offers the yearly check.
|
||||
lifeConfirmedJson: p.lifeConfirmedJson ?? Prisma.DbNull,
|
||||
advanceDirectiveStatus: p.advanceDirectiveStatus,
|
||||
emergencyContactsEncrypted: resolveProfileColumn(
|
||||
p.emergencyContactsEncrypted,
|
||||
|
||||
Reference in new issue
Block a user