mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-11 13:52:20 +00:00
Merge managed-profile editing, sex at creation and module switches per record
This commit is contained in:
commit
06d2104808
36 files changed
+2795
-147
No files matched your search
+190
-20
@@ -18200,7 +18200,8 @@ paths:
|
||||
summary: The signed-in account, and what sharing lets it do
|
||||
description: "Keeps answering while the browser is acting on another record — the switcher, the banner naming whose
|
||||
record is open, and the way back out all read it, so a refusal here would strand a switched session. It is
|
||||
therefore an ACTOR surface: it always serves the caller's own rows, and a request that attaches
|
||||
therefore an ACTOR surface: it serves the caller's own rows — apart from `modules` and `cycleTrackingEnabled`,
|
||||
which answer for the record the session is inside, masked to what the grant opens — and a request that attaches
|
||||
`X-HealthLog-Account` is refused with 403 `sharing.not_permitted` rather than quietly answered. A client that
|
||||
wants a record's data sends the selector on the read that needs it, never on this call."
|
||||
responses:
|
||||
@@ -18504,6 +18505,89 @@ paths:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorEnvelope"
|
||||
/api/managed-profiles/{id}:
|
||||
get:
|
||||
tags:
|
||||
- Account sharing
|
||||
summary: Read a managed record's identity
|
||||
description: "What the record is right now — the five fields its creation took. The read an edit form fills itself from,
|
||||
and the reason it exists: `accountAccess.accounts` on the account payload carries the record's name and nothing
|
||||
else, so a form offered from the Guardian's own panel had no way to show the timezone, the language or the date
|
||||
of birth it was about to overwrite. Cookie transport only, an ACTOR surface (the profile is named in the path
|
||||
and the caller acts as themselves, so it answers without switching into the record), and NOT step-up gated: this
|
||||
is a read, it discloses to a Guardian only what they already administer, and the gate belongs to the acts."
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
schema:
|
||||
type: string
|
||||
required: true
|
||||
responses:
|
||||
"200":
|
||||
description: The record as it stands.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ManagedProfileEnvelope"
|
||||
"401": *a1
|
||||
"404":
|
||||
description: "No such managed profile, or the caller is not one of its Guardians (`meta.errorCode:
|
||||
managed_profile.not_found`). The two are byte-identical, so the refusal is not an enumeration oracle."
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorEnvelope"
|
||||
"422": *a4
|
||||
"429": *a5
|
||||
patch:
|
||||
tags:
|
||||
- Account sharing
|
||||
summary: Change a managed record's identity
|
||||
description: "Edit the record after it exists — the name that was a placeholder, the timezone that moved with the
|
||||
household, the date of birth somebody finally has, the sex that decides whether the cycle module is on. Any
|
||||
active Guardian may do it, from a cookie session with a fresh second factor: the same gate creation and deletion
|
||||
carry, because the three acts mint, change and end an account that can never prove anything about itself. Ten an
|
||||
hour per caller, the ceiling creation carries. Answers with the whole record, so a client re-renders from the
|
||||
server's version rather than from what it sent."
|
||||
parameters:
|
||||
- in: path
|
||||
name: id
|
||||
schema:
|
||||
type: string
|
||||
required: true
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/UpdateManagedProfileRequest"
|
||||
responses:
|
||||
"200":
|
||||
description: The record as it now stands.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ManagedProfileEnvelope"
|
||||
"401":
|
||||
description: No fresh second-factor proof, or none enrolled. Same unconditional gate as creation and deletion.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorEnvelope"
|
||||
"404":
|
||||
description: "No such managed profile, or the caller is not one of its Guardians (`meta.errorCode:
|
||||
managed_profile.not_found`). The two are byte-identical."
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorEnvelope"
|
||||
"422": *a4
|
||||
"429":
|
||||
description: More than ten edits from one caller within the hour — the ceiling creation carries. No error code; branch
|
||||
on the status. Nothing was changed, and the refusal lands before the record named in the path is looked at.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorEnvelope"
|
||||
delete:
|
||||
tags:
|
||||
- Account sharing
|
||||
@@ -28096,6 +28180,14 @@ components:
|
||||
type: string
|
||||
minLength: 1
|
||||
maxLength: 64
|
||||
gender:
|
||||
anyOf:
|
||||
- type: string
|
||||
enum:
|
||||
- MALE
|
||||
- FEMALE
|
||||
- OTHER
|
||||
- type: "null"
|
||||
required:
|
||||
- displayName
|
||||
- locale
|
||||
@@ -28105,12 +28197,58 @@ components:
|
||||
The caller becomes its first Guardian in the same transaction, through an ordinary MANAGE grant with the new
|
||||
record as grantor, so the profile appears in `accountAccess.accounts` on the next `GET /api/auth/me` and is
|
||||
switched into like any other shared record. `displayName` is the only required text; `dateOfBirth` is an
|
||||
optional `YYYY-MM-DD` and is never synthesised from a year. `locale` and `timezone` belong to the RECORD and not
|
||||
to the Guardian reading it: they decide how the profile's own reminders are worded and when its day starts.
|
||||
Strict — an unexpected field is a 422 rather than an ignored key. Cookie transport only and step-up gated
|
||||
optional `YYYY-MM-DD` and is never synthesised from a year. `gender` is optional and takes the same three values
|
||||
`PATCH /api/auth/me` takes for an ordinary account (`MALE`, `FEMALE`, `OTHER`) or null; absent and null both
|
||||
mean not recorded, and the value is what the cycle module derives its default from, so a record created for a
|
||||
child who does not need it starts with it off. `locale` and `timezone` belong to the RECORD and not to the
|
||||
Guardian reading it: they decide how the profile's own reminders are worded and when its day starts. Strict — an
|
||||
unexpected field is a 422 rather than an ignored key. Cookie transport only and step-up gated
|
||||
(`requireFreshMfa`, unconditional): a Bearer caller cannot mint a credential-less person and a permanent
|
||||
management relationship, and an account with no second factor enrolled is refused rather than waved through.
|
||||
Rate-limited to ten an hour per caller."
|
||||
UpdateManagedProfileRequest:
|
||||
type: object
|
||||
properties:
|
||||
displayName:
|
||||
type: string
|
||||
minLength: 1
|
||||
maxLength: 80
|
||||
dateOfBirth:
|
||||
anyOf:
|
||||
- type: string
|
||||
format: date
|
||||
pattern: ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))$
|
||||
- type: "null"
|
||||
locale:
|
||||
type: string
|
||||
enum:
|
||||
- de
|
||||
- en
|
||||
- es
|
||||
- fr
|
||||
- it
|
||||
- pl
|
||||
- ko
|
||||
timezone:
|
||||
type: string
|
||||
minLength: 1
|
||||
maxLength: 64
|
||||
gender:
|
||||
anyOf:
|
||||
- type: string
|
||||
enum:
|
||||
- MALE
|
||||
- FEMALE
|
||||
- OTHER
|
||||
- type: "null"
|
||||
additionalProperties: false
|
||||
description: "Change what a managed record is, from the Guardian's own panel. The same five fields creation takes, every
|
||||
one of them optional, and at least one required — an empty body is a 422 rather than a no-op that audits a
|
||||
change nobody made. Absence means leave the field alone; an explicit null on `dateOfBirth` or `gender` clears
|
||||
it, and the two are different answers. Nothing else on the record is reachable here: credentials, provider
|
||||
connections, notification routing and the health profile are not identity, and the wider configuration surface
|
||||
is `PATCH /api/record-settings/{family}`, which is guardian-fenced and answers only while switched into the
|
||||
record. Strict, cookie-only and step-up gated, like every act in this family."
|
||||
InviteManagedProfileGuardianRequest:
|
||||
type: object
|
||||
properties:
|
||||
@@ -28375,10 +28513,15 @@ components:
|
||||
vaccinations:
|
||||
type: boolean
|
||||
additionalProperties: false
|
||||
required:
|
||||
- modulePreferences
|
||||
cycleTrackingEnabled:
|
||||
type: boolean
|
||||
additionalProperties: false
|
||||
description: Partial module toggle. Merged onto the record's current preferences; omitted keys keep their value.
|
||||
description: "Partial module toggle. `modulePreferences` is merged onto the record's current preferences; omitted keys
|
||||
keep their value. `cycleTrackingEnabled` is separate because `cycle` is a DELEGATED module: its state lives in
|
||||
the record's own cycle profile and `modulePreferences.cycle` is refused, so this is the field that turns cycle
|
||||
tracking on or off for the record — an explicit boolean overrides the default the record's sex would derive.
|
||||
Both fields are optional and at least one is required; an empty body is a 422 rather than a save that changed
|
||||
nothing."
|
||||
RecordNotificationSettingsPatch:
|
||||
type: object
|
||||
properties:
|
||||
@@ -43832,9 +43975,13 @@ components:
|
||||
- nutrients
|
||||
- vaccinations
|
||||
additionalProperties: false
|
||||
description: Fully-resolved per-user module enable/disable map. Every toggleable module key is present. `false` means
|
||||
the surface should disappear end-to-end (nav, dashboard, insights, …). `cycle` mirrors `cycleTrackingEnabled`;
|
||||
`coach` mirrors the resolved per-user opt-out + operator master flag.
|
||||
description: Fully-resolved module enable/disable map. Every toggleable module key is present. `false` means the surface
|
||||
should disappear end-to-end (nav, dashboard, insights, …). `cycle` mirrors `cycleTrackingEnabled`; `coach`
|
||||
mirrors the resolved per-user opt-out + operator master flag. On `GET /api/auth/me` it answers for the RECORD
|
||||
the session is inside rather than for the caller, because every surface it gates shows the record's data, and it
|
||||
is masked to the sections the active grant opens — a module outside them reads `false` rather than the record's
|
||||
true state. With no switch — which is every native request, since the Bearer transport carries none — the two
|
||||
are the same account, nothing is masked and the payload is unchanged.
|
||||
PatchModulesResponse:
|
||||
type: object
|
||||
properties:
|
||||
@@ -65735,9 +65882,13 @@ components:
|
||||
- recordSession
|
||||
additionalProperties: {}
|
||||
description: "The signed-in account: its identity, its preferences, and (since v1.36.0) what account sharing lets it do.
|
||||
Additional properties are the preference fields this spec does not yet enumerate. Under an active switch this
|
||||
payload still describes the CALLER — their preferences, their modules, their identity — because display
|
||||
preferences belong to the person at the keyboard rather than to the record they are reading."
|
||||
Additional properties are the preference fields this spec does not yet enumerate. Under an active switch the
|
||||
identity and preference fields still describe the CALLER, because display preferences belong to the person at
|
||||
the keyboard rather than to the record they are reading. Two fields are the exception (v1.38.14): `modules` and
|
||||
`cycleTrackingEnabled` describe the ACTIVE RECORD, since every surface they gate shows the record's data — and
|
||||
they are masked to the sections the active grant opens, so a scoped grant reads `false` for a module outside it
|
||||
rather than the record's true state. With no switch — which is every native request, since the Bearer transport
|
||||
carries none — the two are the same account and nothing is masked."
|
||||
AccountAccess:
|
||||
type: object
|
||||
properties:
|
||||
@@ -66360,13 +66511,31 @@ components:
|
||||
description: The profile's account id — the value `POST /api/account/switch` and the `X-HealthLog-Account` selector
|
||||
take, and the `{id}` in this family's paths.
|
||||
displayName:
|
||||
type: string
|
||||
anyOf:
|
||||
- type: string
|
||||
- type: "null"
|
||||
dateOfBirth:
|
||||
anyOf:
|
||||
- type: string
|
||||
- type: "null"
|
||||
description: "`YYYY-MM-DD`, or null. The same date-only shape the request takes — this is a calendar date, not an
|
||||
instant, and publishing it with a time would invite a client to apply a zone to it."
|
||||
gender:
|
||||
anyOf:
|
||||
- type: string
|
||||
enum:
|
||||
- MALE
|
||||
- FEMALE
|
||||
- OTHER
|
||||
- type: "null"
|
||||
description: "`User.gender` for this record. Null means not recorded, which is a real answer and not a missing one."
|
||||
locale:
|
||||
type: string
|
||||
anyOf:
|
||||
- type: string
|
||||
- type: "null"
|
||||
description: The record's own language. Every profile this family creates carries one, because creation requires it; the
|
||||
column is nullable, so null means a row written before that and a client renders its own language rather
|
||||
than the word.
|
||||
timezone:
|
||||
type: string
|
||||
recordKind:
|
||||
@@ -66378,15 +66547,16 @@ components:
|
||||
- id
|
||||
- displayName
|
||||
- dateOfBirth
|
||||
- gender
|
||||
- locale
|
||||
- timezone
|
||||
- recordKind
|
||||
additionalProperties: false
|
||||
description: "A record somebody looks after, as its creation answers with it. There is deliberately no list endpoint:
|
||||
the profiles a Guardian looks after are the `managed` entries of `accountAccess.accounts` on GET /api/auth/me,
|
||||
because a Guardian's relationship to a profile IS a MANAGE grant. Re-read the account payload after any change
|
||||
here; a client that refreshed only its own panel would leave the new record out of the switcher and the banner
|
||||
until the next boot."
|
||||
description: "A record somebody looks after, as its creation, its read and its edit all answer with it. There is
|
||||
deliberately no list endpoint: the profiles a Guardian looks after are the `managed` entries of
|
||||
`accountAccess.accounts` on GET /api/auth/me, because a Guardian's relationship to a profile IS a MANAGE grant.
|
||||
Re-read the account payload after any change here; a client that refreshed only its own panel would leave the
|
||||
new record out of the switcher and the banner until the next boot."
|
||||
DeletedManagedProfileEnvelope:
|
||||
type: object
|
||||
properties:
|
||||
|
||||
Reference in new issue
Block a user